swift: add NWConnection-inspired Swift 6 compatible Tailscale.framework

updates tailscale/tailscale#13937

Builds a TailscaleKit.framework which wraps libtailscale.a in an actor,
providing IncomingConnection, Listener, and OutgoingConnection
types which are more usable in pure async Swift code.  The
method signatures are intended to be similar in form to NWConnection.

We also provide an extension to URLSession so you can make URL
requests via the user-space proxy.

Adds a static library mirroring much of the test control utilities in go, minus
the dependency on testing, so that we can export the signatures and
link it to tests that cannot run in go.

Added functionality to get both the local interface IPs as well as
the remote IP of  incoming connections accepted by a listener.

Fixed a bug in the log writer so we append a newline.

This also updates to the latest go toolchain and tailscale version.

Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
This commit is contained in:
Jonathan Nobels
2025-01-02 14:32:59 -05:00
committed by Jonathan Nobels
parent 9095651d2d
commit 2948fe1136
34 changed files with 3139 additions and 260 deletions

10
.gitignore vendored
View File

@@ -2,6 +2,12 @@ libtailscale.so
libtailscale.a
libtailscale.h
libtailscale.tar*
/tstestcontrol/libtstestcontrol.a
/tstestcontrol/libtstestcontrol.h
/swift/build
/ruby/tmp/
/ruby/pkg/
/ruby/doc/
@@ -9,6 +15,8 @@ libtailscale.tar*
/ruby/ext/libtailscale/go.mod
/ruby/ext/libtailscale/go.sum
/ruby/LICENSE
/sourcepkg/libtailscale
/sourcepkg/libtailscale.tar*
/vendor/
/vendor/

135
go.mod
View File

@@ -1,86 +1,99 @@
module github.com/tailscale/libtailscale
go 1.21
go 1.23.1
require tailscale.com v1.48.0
require tailscale.com v1.76.6
require (
filippo.io/edwards25519 v1.0.0 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aws/aws-sdk-go-v2 v1.18.0 // indirect
github.com/aws/aws-sdk-go-v2/config v1.18.22 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.21 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.10 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/coreos/go-iptables v0.6.0 // indirect
github.com/creack/pty v1.1.18 // indirect
github.com/dblohm7/wingoes v0.0.0-20230803162905-5c6286bb8c6e // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
github.com/aws/aws-sdk-go-v2/config v1.26.5 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/coder/websocket v1.8.12 // indirect
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
github.com/creack/pty v1.1.23 // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
github.com/djherbis/times v1.6.0 // indirect
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
github.com/gaissmai/bart v0.11.1 // indirect
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c // indirect
github.com/hdevalence/ed25519consensus v0.1.0 // indirect
github.com/illarion/gonotify v1.0.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/csrf v1.7.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/illarion/gonotify/v2 v2.0.3 // indirect
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect
github.com/jellydator/ttlcache/v3 v3.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect
github.com/jsimonetti/rtnetlink v1.3.2 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/sdnotify v1.0.0 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/miekg/dns v1.1.55 // indirect
github.com/mdlayher/socket v0.5.0 // indirect
github.com/miekg/dns v1.1.58 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/sftp v1.13.5 // indirect
github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d // indirect
github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pkg/sftp v1.13.6 // indirect
github.com/prometheus-community/pro-bing v0.4.0 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 // indirect
github.com/tailscale/wireguard-go v0.0.0-20230710185534-bb2c8f22eccf // indirect
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 // indirect
github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc // indirect
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e // indirect
github.com/tcnksm/go-httpstat v0.2.0 // indirect
github.com/u-root/u-root v0.11.0 // indirect
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
github.com/u-root/u-root v0.12.0 // indirect
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516 // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.9.1 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.23.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f // indirect
honnef.co/go/tools v0.4.3 // indirect
inet.af/peercred v0.0.0-20210906144145-0893ea02156a // indirect
inet.af/wf v0.0.0-20221017222439-36129f591884 // indirect
nhooyr.io/websocket v1.8.7 // indirect
gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 // indirect
honnef.co/go/tools v0.5.1 // indirect
)

413
go.sum
View File

@@ -1,104 +1,107 @@
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/aws/aws-sdk-go-v2 v1.18.0 h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY=
github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/config v1.18.22 h1:7vkUEmjjv+giht4wIROqLs+49VWmiQMMHSduxmoNKLU=
github.com/aws/aws-sdk-go-v2/config v1.18.22/go.mod h1:mN7Li1wxaPxSSy4Xkr6stFuinJGf3VZW3ZSNvO0q6sI=
github.com/aws/aws-sdk-go-v2/credentials v1.13.21 h1:VRiXnPEaaPeGeoFcXvMZOB5K/yfIXOYE3q97Kgb0zbU=
github.com/aws/aws-sdk-go-v2/credentials v1.13.21/go.mod h1:90Dk1lJoMyspa/EDUrldTxsPns0wn6+KpRKpdAWc0uA=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 h1:jJPgroehGvjrde3XufFIJUZVK5A2L9a3KwSFgKy9n8w=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 h1:vFQlirhuM8lLlpI7imKOMsjdQLuN9CPi+k44F/OFVsk=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 h1:gGLG7yKaXG02/jBlg210R7VgQIotiQntNhsCFejawx8=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 h1:0iKliEXAcCa2qVtRs7Ot5hItA2MsufrphbRFlz1Owxo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw=
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.3 h1:TQZH0Djie8VVgTBDOQ02M4zVHJFrNzLMsYMbNfRitVM=
github.com/aws/aws-sdk-go-v2/service/ssm v1.36.3/go.mod h1:p6MaesK9061w6NTiFmZpUzEkKUY5blKlwD2zYyErxKA=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.9 h1:GAiaQWuQhQQui76KjuXeShmyXqECwQ0mGRMc/rwsL+c=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.9/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9 h1:TraLwncRJkWqtIBVKI/UqBymq4+hL+3MzUOtUATuzkA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk=
github.com/aws/aws-sdk-go-v2/service/sts v1.18.10 h1:6UbNM/KJhMBfOI5+lpVcJ/8OA7cBSz0O6OX37SRKlSw=
github.com/aws/aws-sdk-go-v2/service/sts v1.18.10/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8=
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ=
github.com/cilium/ebpf v0.10.0/go.mod h1:DPiVdY/kT534dgc9ERmvP8mWA+9gvwgKfRvk4nNWnoE=
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw=
github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU=
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE=
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bramvdbogaerde/go-scp v1.4.0 h1:jKMwpwCbcX1KyvDbm/PDJuXcMuNVlLGi0Q0reuzjyKY=
github.com/bramvdbogaerde/go-scp v1.4.0/go.mod h1:on2aH5AxaFb2G0N5Vsdy6B0Ml7k9HuHSwfo1y0QzAbQ=
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dblohm7/wingoes v0.0.0-20230803162905-5c6286bb8c6e h1:tTRuQNnXKO6Ffu62nk9bnnPx/m+IyNMdFFfzsETyRO8=
github.com/dblohm7/wingoes v0.0.0-20230803162905-5c6286bb8c6e/go.mod h1:6NCrWM5jRefaG7iN0iMShPalLsljHWBh9v1zxM2f8Xs=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI=
github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc=
github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg=
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c h1:06RMfw+TMMHtRuUOroMeatRCCgSMWXCJQeABvHU69YQ=
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c/go.mod h1:BVIYo3cdnT4qSylnYqcd5YtmXhr51cJPGtnLBe/uLBU=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU=
github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio=
github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE=
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 h1:+aAGyK41KRn8jbF2Q7PLL0Sxwg6dShGcQSeCC7nZQ8E=
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f h1:ov45/OzrJG8EKbGjn7jJZQJTN7Z1t73sFYNIRd64YlI=
github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f/go.mod h1:JoDrYMZpDPYo6uH9/f6Peqms3zNNWT2XiGgioMOIGuI=
github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A=
github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -106,14 +109,10 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
github.com/jsimonetti/rtnetlink v1.3.2 h1:dcn0uWkfxycEEyNy0IGfx3GrhQ38LH7odjxAghimsVI=
github.com/jsimonetti/rtnetlink v1.3.2/go.mod h1:BBu4jZCpTjP6Gk0/wfrO8qcqymnN3g0hoFqObRmUo6U=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
@@ -122,149 +121,169 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c=
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go=
github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d h1:K3j02b5j2Iw1xoggN9B2DIEkhWGheqFOeDkdJdBrJI8=
github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d/go.mod h1:2P+hpOwd53e7JMX/L4f3VXkv1G+33ES6IWZSrkIeWNs=
github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e h1:JyeJF/HuSwvxWtsR1c0oKX1lzaSH5Wh4aX+MgiStaGQ=
github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e/go.mod h1:DjoeCULdP6vTJ/xY+nzzR9LaUHprkbZEpNidX0aqEEk=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/studio-b12/gowebdav v0.9.0 h1:1j1sc9gQnNxbXXM4M/CebPOX4aXYtr7MojAVcN4dHjU=
github.com/studio-b12/gowebdav v0.9.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw=
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk=
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
github.com/tailscale/wireguard-go v0.0.0-20230710185534-bb2c8f22eccf h1:bHQHwIHId353jAF2Lm0cGDjJpse/PYS0I0DTtihL9Ls=
github.com/tailscale/wireguard-go v0.0.0-20230710185534-bb2c8f22eccf/go.mod h1:QRIcq2+DbdIC5sKh/gcAZhuqu6WT6L6G8/ALPN5wqYw=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 h1:Gz0rz40FvFVLTBk/K8UNAenb36EbDSnh+q7Z9ldcC8w=
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4/go.mod h1:phI29ccmHQBc+wvroosENp1IF9195449VDnFDhJ4rJU=
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g=
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc h1:cezaQN9pvKVaw56Ma5qr/G646uKIYP0yQf+OyWN/okc=
github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
github.com/u-root/gobusybox/src v0.0.0-20221229083637-46b2883a7f90 h1:zTk5683I9K62wtZ6eUa6vu6IWwVHXPnoKK5n2unAwv0=
github.com/u-root/gobusybox/src v0.0.0-20221229083637-46b2883a7f90/go.mod h1:lYt+LVfZBBwDZ3+PHk4k/c/TnKOkjJXiJO73E32Mmpc=
github.com/u-root/u-root v0.11.0 h1:6gCZLOeRyevw7gbTwMj3fKxnr9+yHFlgF3N7udUVNO8=
github.com/u-root/u-root v0.11.0/go.mod h1:DBkDtiZyONk9hzVEdB/PWI9B4TxDkElWlVTHseglrZY=
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa h1:unMPGGK/CRzfg923allsikmvk2l7beBeFPUNC4RVX/8=
github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa/go.mod h1:Zj4Tt22fJVn/nz/y6Ergm1SahR9dio1Zm/D2/S0TmXM=
github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs=
github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI=
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8=
go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516 h1:X66ZEoMN2SuaoI/dfZVYobB6E5zjZyyHUMWlCA7MgGE=
go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 h1:w/MOPdQ1IoYoDou3L55ZbTx2Nhn7JAhX1BBZor8qChU=
golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f h1:8GE2MRjGiFmfpon8dekPI08jEuNMQzSffVHgdupcO4E=
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q=
honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw=
honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA=
gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 h1:TU8z2Lh3Bbq77w0t1eG8yRlLcNHzZu3x6mhoH2Mk0c8=
gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU=
honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I=
honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
inet.af/peercred v0.0.0-20210906144145-0893ea02156a h1:qdkS8Q5/i10xU2ArJMKYhVa1DORzBfYS/qA2UK2jheg=
inet.af/peercred v0.0.0-20210906144145-0893ea02156a/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
inet.af/wf v0.0.0-20221017222439-36129f591884 h1:zg9snq3Cpy50lWuVqDYM7AIRVTtU50y5WXETMFohW/Q=
inet.af/wf v0.0.0-20221017222439-36129f591884/go.mod h1:bSAQ38BYbY68uwpasXOTZo22dKGy9SNvI6PZFeKomZE=
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE=
software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ=
tailscale.com v1.48.0 h1:jpc6Fu/dBddptXw1VJ9Euny8+xB00YV91dSwcfuoxw4=
tailscale.com v1.48.0/go.mod h1:RWW4emjviEEAIqr6P6bbZZGXr19BdAdtwtUVfW9SBvU=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
tailscale.com v1.76.6 h1:qxRVe/ljIVWixIiCLOHrakbsoXcw/dKaKCZt25tJ7gc=
tailscale.com v1.76.6/go.mod h1:myCwmhYBvMCF/5OgBYuIW42zscuEo30bAml7wABVZLk=

20
swift/Makefile Normal file
View File

@@ -0,0 +1,20 @@
# Copyright (c) Tailscale Inc & AUTHORS
# SPDX-License-Identifier: BSD-3-Clause
## Prettifies xcode output for xcode tests using xcpretty, if it is installed
XCPRETTIFIER := xcpretty
ifeq (, $(shell which $(XCPRETTIFIER)))
XCPRETTIFIER := cat
endif
OUTPUT_DIR=build
build:
mkdir -p $(OUTPUT_DIR)
xcodebuild build -scheme TailscaleKit -derivedDataPath $(OUTPUT_DIR) -configuration Release -destination 'generic/platform=macOS,arch=arm64' -destination 'generic/platform=iOS' | $(XCPRETTIFIER)
test:
xcodebuild test -scheme TailscaleKitXCTests -derivedDataPath $(OUTPUT_DIR) -configuration Debug | $(XCPRETTIFIER)
clean:
rm -rf $(OUTPUT_DIR)

91
swift/README.md Normal file
View File

@@ -0,0 +1,91 @@
# TailscaleKit
The TailscaleKit Swift package provides an embedded network interface that can be
used to listen for and dial connections to other [Tailscale](https://tailscale.com) nodes in addition
to an extension to URLSession which allows you to make URL requests to nodes on you Tailnet directly.
The interfaces are similar in design to NWConnection, but are Swift 6 compliant and
designed to be used in modern async/await style code.
## Build and Install
Build Requirements:
- XCode 16.1 or newer
Building Tailscale.framework:
From /swift
```
$ make build
```
Will build TailscaleKit.framework into /swift/build/Build/Products.
Separate frameworks will be built for macOS and iOS. All dependencies (libtailscale.a)
are built automatically. Swift 6 is supported.
Alternatively, you may build from xCode using the Tailscale scheme.
Non-apple builds are not supported (yet). We do use URLSession and Combine though
it is possible to purge both.
## Tests
From /swift
```
$ make test
```
## Usage
Nodes need to be authorized in order to function. Set an auth key via
the config.authKey parameter, or watch the log stream and respond to the printed
authorization URL.
Here's a working example using an auth key:
```Swift
// Configures a Tailscale node and starts it up. The node here (and the key we would use to
// authenticate it) are marked as 'ephemeral' - meaning that the node will be disposed of as
// soon as it goes offline.
func start() -> TailscaleNode {
let dataDir = getDocumentDirectoryPath().absoluteString + "tailscale"
let authKey = "tsnet-auth-put-your-auth-key-key-here"
let config = Configuration(hostName: "TSNet-Test",
path: dataDir,
authKey: authKey,
controlURL: Configuration.defaultControlURL,
ephemeral: true)
// The logger is configurable. The default will just print.
let node = try TailscaleNode(config: config, logger: DefaultLogger())
// Bring the node up
try await node.up()
return node
}
// Do a URL request via the loopback proxy
// Where url is a node on your tailnet such as https://server.fiesty-pangolin.ts.net/thing
func fetchURL(_ url: URL, tailscale: TailscaleNode) async throws -> Data {
// You can cache this. It will not change once the node is up.
let sessionConfig = try await URLSessionConfiguration.tailscaleSession(tailscale)
let session = URLSession(configuration: sessionConfig)
// Make the request
let req = URLRequest(url: url)
let (data, _) = try await session.data(for: req)
return data
}
```
See the [TailscaleKitTests](./Tests/TailscaleKitTests/TailscaleKitTests.swift) for more examples.
## Contributing
Pull requests are welcome on GitHub at https://github.com/tailscale/libtailscale
Please file any issues about this code or the hosted service on
[the issue tracker](https://github.com/tailscale/tailscale/issues).

View File

@@ -0,0 +1,858 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXAggregateTarget section */
C2BED0552CCF3031004A2544 /* libtstestcontrol */ = {
isa = PBXAggregateTarget;
buildConfigurationList = C2BED0572CCF3031004A2544 /* Build configuration list for PBXAggregateTarget "libtstestcontrol" */;
buildPhases = (
C2BED0562CCF3031004A2544 /* ShellScript */,
);
dependencies = (
);
name = libtstestcontrol;
packageProductDependencies = (
);
productName = libtailscale;
};
C2EE3B622CCBE88400CF5BE0 /* libtailscale */ = {
isa = PBXAggregateTarget;
buildConfigurationList = C2EE3B632CCBE88400CF5BE0 /* Build configuration list for PBXAggregateTarget "libtailscale" */;
buildPhases = (
C2EE3B662CCBE88E00CF5BE0 /* ShellScript */,
);
dependencies = (
);
name = libtailscale;
packageProductDependencies = (
);
productName = libtailscale;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
C2BED05D2CCFC68D004A2544 /* libtstestcontrol.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C2BED05C2CCFC68D004A2544 /* libtstestcontrol.a */; };
C2E1C30B2CC9EF1A00ADC565 /* libtailscale.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C2E1C2FC2CC9B9E300ADC565 /* libtailscale.a */; };
C2E1C3142CCA8B7C00ADC565 /* TailscaleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2E1C2DA2CC9B5A400ADC565 /* TailscaleKit.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
C286408D2CCA8CB600CD5EBC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = C2E1C2D12CC9B5A400ADC565 /* Project object */;
proxyType = 1;
remoteGlobalIDString = C28640612CCA8C9D00CD5EBC;
remoteInfo = TailscaleTestHost;
};
C2BED05A2CCF308D004A2544 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = C2E1C2D12CC9B5A400ADC565 /* Project object */;
proxyType = 1;
remoteGlobalIDString = C2BED0552CCF3031004A2544;
remoteInfo = tsnetintegration;
};
C2E1C3152CCA8B7C00ADC565 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = C2E1C2D12CC9B5A400ADC565 /* Project object */;
proxyType = 1;
remoteGlobalIDString = C2E1C2D92CC9B5A400ADC565;
remoteInfo = Tailscale;
};
C2EE3B692CCBED1E00CF5BE0 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = C2E1C2D12CC9B5A400ADC565 /* Project object */;
proxyType = 1;
remoteGlobalIDString = C2EE3B622CCBE88400CF5BE0;
remoteInfo = libtailscale;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
C28640622CCA8C9D00CD5EBC /* TailscaleKitTestHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TailscaleKitTestHost.app; sourceTree = BUILT_PRODUCTS_DIR; };
C2BED05C2CCFC68D004A2544 /* libtstestcontrol.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtstestcontrol.a; path = ../tstestconrol/libtstestcontrol.a; sourceTree = "<group>"; };
C2E1C2DA2CC9B5A400ADC565 /* TailscaleKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TailscaleKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C2E1C2FC2CC9B9E300ADC565 /* libtailscale.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtailscale.a; path = ../libtailscale.a; sourceTree = "<group>"; };
C2E1C3102CCA8B7C00ADC565 /* TailscaleKitXCTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TailscaleKitXCTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
C2E3E87F2D2718D0004992A2 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
C2E3E8802D2718D6004992A2 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
C2E1C2EC2CC9B5A400ADC565 /* Exceptions for "TailscaleKit" folder in "TailscaleKit" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
publicHeaders = (
TailscaleKit.h,
);
target = C2E1C2D92CC9B5A400ADC565 /* TailscaleKit */;
};
C2E3E87E2D2711BF004992A2 /* Exceptions for "TailscaleKitTestHost" folder in "TailscaleKitTestHost" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = C28640612CCA8C9D00CD5EBC /* TailscaleKitTestHost */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
C2EE3B702CCC179300CF5BE0 /* Exceptions for "TailscaleKitXCTests" folder in "Compile Sources" phase from "TailscaleKitXCTests" target */ = {
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
buildPhase = C2E1C30C2CCA8B7C00ADC565 /* Sources */;
membershipExceptions = (
"Test-Bridging-Header.h",
);
};
/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
C28640632CCA8C9D00CD5EBC /* TailscaleKitTestHost */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
C2E3E87E2D2711BF004992A2 /* Exceptions for "TailscaleKitTestHost" folder in "TailscaleKitTestHost" target */,
);
path = TailscaleKitTestHost;
sourceTree = "<group>";
};
C2E1C2DC2CC9B5A400ADC565 /* TailscaleKit */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
C2E1C2EC2CC9B5A400ADC565 /* Exceptions for "TailscaleKit" folder in "TailscaleKit" target */,
);
path = TailscaleKit;
sourceTree = "<group>";
};
C2E1C3112CCA8B7C00ADC565 /* TailscaleKitXCTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
C2EE3B702CCC179300CF5BE0 /* Exceptions for "TailscaleKitXCTests" folder in "Compile Sources" phase from "TailscaleKitXCTests" target */,
);
path = TailscaleKitXCTests;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
C286405F2CCA8C9D00CD5EBC /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
C2E1C2D72CC9B5A400ADC565 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C2E1C30B2CC9EF1A00ADC565 /* libtailscale.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C2E1C30D2CCA8B7C00ADC565 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C2BED05D2CCFC68D004A2544 /* libtstestcontrol.a in Frameworks */,
C2E1C3142CCA8B7C00ADC565 /* TailscaleKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
C2E1C2D02CC9B5A400ADC565 = {
isa = PBXGroup;
children = (
C2E3E8802D2718D6004992A2 /* Makefile */,
C2E3E87F2D2718D0004992A2 /* README.md */,
C2E1C2DC2CC9B5A400ADC565 /* TailscaleKit */,
C2E1C3112CCA8B7C00ADC565 /* TailscaleKitXCTests */,
C28640632CCA8C9D00CD5EBC /* TailscaleKitTestHost */,
C2E1C2FB2CC9B9E300ADC565 /* Frameworks */,
C2E1C2DB2CC9B5A400ADC565 /* Products */,
);
sourceTree = "<group>";
};
C2E1C2DB2CC9B5A400ADC565 /* Products */ = {
isa = PBXGroup;
children = (
C2E1C2DA2CC9B5A400ADC565 /* TailscaleKit.framework */,
C2E1C3102CCA8B7C00ADC565 /* TailscaleKitXCTests.xctest */,
C28640622CCA8C9D00CD5EBC /* TailscaleKitTestHost.app */,
);
name = Products;
sourceTree = "<group>";
};
C2E1C2FB2CC9B9E300ADC565 /* Frameworks */ = {
isa = PBXGroup;
children = (
C2BED05C2CCFC68D004A2544 /* libtstestcontrol.a */,
C2E1C2FC2CC9B9E300ADC565 /* libtailscale.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
C2E1C2D52CC9B5A400ADC565 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
C28640612CCA8C9D00CD5EBC /* TailscaleKitTestHost */ = {
isa = PBXNativeTarget;
buildConfigurationList = C28640842CCA8C9E00CD5EBC /* Build configuration list for PBXNativeTarget "TailscaleKitTestHost" */;
buildPhases = (
C286405E2CCA8C9D00CD5EBC /* Sources */,
C286405F2CCA8C9D00CD5EBC /* Frameworks */,
C28640602CCA8C9D00CD5EBC /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
C28640632CCA8C9D00CD5EBC /* TailscaleKitTestHost */,
);
name = TailscaleKitTestHost;
packageProductDependencies = (
);
productName = TailscaleTestHost;
productReference = C28640622CCA8C9D00CD5EBC /* TailscaleKitTestHost.app */;
productType = "com.apple.product-type.application";
};
C2E1C2D92CC9B5A400ADC565 /* TailscaleKit */ = {
isa = PBXNativeTarget;
buildConfigurationList = C2E1C2ED2CC9B5A400ADC565 /* Build configuration list for PBXNativeTarget "TailscaleKit" */;
buildPhases = (
C2E1C2D52CC9B5A400ADC565 /* Headers */,
C2E1C2D62CC9B5A400ADC565 /* Sources */,
C2E1C2D72CC9B5A400ADC565 /* Frameworks */,
C2E1C2D82CC9B5A400ADC565 /* Resources */,
);
buildRules = (
);
dependencies = (
C2EE3B6A2CCBED1E00CF5BE0 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
C2E1C2DC2CC9B5A400ADC565 /* TailscaleKit */,
);
name = TailscaleKit;
packageProductDependencies = (
);
productName = Tailscale;
productReference = C2E1C2DA2CC9B5A400ADC565 /* TailscaleKit.framework */;
productType = "com.apple.product-type.framework";
};
C2E1C30F2CCA8B7C00ADC565 /* TailscaleKitXCTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = C2E1C3172CCA8B7C00ADC565 /* Build configuration list for PBXNativeTarget "TailscaleKitXCTests" */;
buildPhases = (
C2E1C30C2CCA8B7C00ADC565 /* Sources */,
C2E1C30D2CCA8B7C00ADC565 /* Frameworks */,
C2E1C30E2CCA8B7C00ADC565 /* Resources */,
);
buildRules = (
);
dependencies = (
C2BED05B2CCF308D004A2544 /* PBXTargetDependency */,
C2E1C3162CCA8B7C00ADC565 /* PBXTargetDependency */,
C286408E2CCA8CB600CD5EBC /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
C2E1C3112CCA8B7C00ADC565 /* TailscaleKitXCTests */,
);
name = TailscaleKitXCTests;
packageProductDependencies = (
);
productName = TailscaleXCTests;
productReference = C2E1C3102CCA8B7C00ADC565 /* TailscaleKitXCTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
C2E1C2D12CC9B5A400ADC565 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1610;
LastUpgradeCheck = 1610;
TargetAttributes = {
C28640612CCA8C9D00CD5EBC = {
CreatedOnToolsVersion = 16.1;
};
C2E1C2D92CC9B5A400ADC565 = {
CreatedOnToolsVersion = 16.1;
};
C2E1C30F2CCA8B7C00ADC565 = {
CreatedOnToolsVersion = 16.1;
TestTargetID = C28640612CCA8C9D00CD5EBC;
};
C2EE3B622CCBE88400CF5BE0 = {
CreatedOnToolsVersion = 16.1;
};
};
};
buildConfigurationList = C2E1C2D42CC9B5A400ADC565 /* Build configuration list for PBXProject "TailscaleKit" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = C2E1C2D02CC9B5A400ADC565;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = C2E1C2DB2CC9B5A400ADC565 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
C2E1C2D92CC9B5A400ADC565 /* TailscaleKit */,
C2E1C30F2CCA8B7C00ADC565 /* TailscaleKitXCTests */,
C28640612CCA8C9D00CD5EBC /* TailscaleKitTestHost */,
C2EE3B622CCBE88400CF5BE0 /* libtailscale */,
C2BED0552CCF3031004A2544 /* libtstestcontrol */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
C28640602CCA8C9D00CD5EBC /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
C2E1C2D82CC9B5A400ADC565 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
C2E1C30E2CCA8B7C00ADC565 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
C2BED0562CCF3031004A2544 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
"$(SRCROOT)/../tstestcontrol/libtstestcontrol.a",
"$(SRCROOT)/../tstestcontrol/libtstestcontrol.h",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Type a script or drag a script file from your workspace to insert its path.\npushd .\ncd $(SCROOT)/../tstestcontrol\nmake all\npopd\n";
};
C2EE3B662CCBE88E00CF5BE0 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
"$(SRCROOT)/../libtailscale.a",
"$(SRCROOT)/../libtailscale.h",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Type a script or drag a script file from your workspace to insert its path.\npushd .\ncd $(SCROOT)/..\nmake libtailscale\npopd\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
C286405E2CCA8C9D00CD5EBC /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
C2E1C2D62CC9B5A400ADC565 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
C2E1C30C2CCA8B7C00ADC565 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
C286408E2CCA8CB600CD5EBC /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = C28640612CCA8C9D00CD5EBC /* TailscaleKitTestHost */;
targetProxy = C286408D2CCA8CB600CD5EBC /* PBXContainerItemProxy */;
};
C2BED05B2CCF308D004A2544 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = C2BED0552CCF3031004A2544 /* libtstestcontrol */;
targetProxy = C2BED05A2CCF308D004A2544 /* PBXContainerItemProxy */;
};
C2E1C3162CCA8B7C00ADC565 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = C2E1C2D92CC9B5A400ADC565 /* TailscaleKit */;
targetProxy = C2E1C3152CCA8B7C00ADC565 /* PBXContainerItemProxy */;
};
C2EE3B6A2CCBED1E00CF5BE0 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = C2EE3B622CCBE88400CF5BE0 /* libtailscale */;
targetProxy = C2EE3B692CCBED1E00CF5BE0 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
C28640852CCA8C9E00CD5EBC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = TailscaleKitTestHost/TailscaleKitTestHost.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = W5364U7YZB;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = TailscaleKitTestHost/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = io.tailscale.TailscaleTestHost;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
C28640862CCA8C9E00CD5EBC /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = TailscaleKitTestHost/TailscaleKitTestHost.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = W5364U7YZB;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = TailscaleKitTestHost/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = io.tailscale.TailscaleTestHost;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
C2BED0582CCF3031004A2544 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = W5364U7YZB;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
C2BED0592CCF3031004A2544 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = W5364U7YZB;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
C2E1C2EE2CC9B5A400ADC565 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = W5364U7YZB;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_MODULE_VERIFIER = YES;
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/..";
GENERATE_INFOPLIST_FILE = YES;
HEADER_SEARCH_PATHS = (
"$(SRCROOT)/..",
"$(SRCROOT)",
);
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
LD_RUNPATH_SEARCH_PATHS = (
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SRCROOT)/..";
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
PRODUCT_BUNDLE_IDENTIFIER = io.tailscale.Tailscale;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = auto;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_INSTALL_OBJC_HEADER = NO;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
XROS_DEPLOYMENT_TARGET = 2.1;
};
name = Debug;
};
C2E1C2EF2CC9B5A400ADC565 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = W5364U7YZB;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_MODULE_VERIFIER = YES;
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/..";
GENERATE_INFOPLIST_FILE = YES;
HEADER_SEARCH_PATHS = (
"$(SRCROOT)/..",
"$(SRCROOT)",
);
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
LD_RUNPATH_SEARCH_PATHS = (
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SRCROOT)/..";
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
PRODUCT_BUNDLE_IDENTIFIER = io.tailscale.Tailscale;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = auto;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_INSTALL_OBJC_HEADER = NO;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
XROS_DEPLOYMENT_TARGET = 2.1;
};
name = Release;
};
C2E1C2F02CC9B5A400ADC565 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
C2E1C2F12CC9B5A400ADC565 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SWIFT_COMPILATION_MODE = wholemodule;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
C2E1C3182CCA8B7C00ADC565 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = W5364U7YZB;
GENERATE_INFOPLIST_FILE = YES;
HEADER_SEARCH_PATHS = "$(SRCROOT)/../tstestcontrol";
LIBRARY_SEARCH_PATHS = "$(SRCROOT)/../tstestcontrol";
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = io.tailscale.TailscaleXCTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/TailscaleKitXCTests/Test-Bridging-Header.h";
SWIFT_VERSION = 6.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TailscaleKitTestHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TailscaleKitTestHost";
};
name = Debug;
};
C2E1C3192CCA8B7C00ADC565 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = W5364U7YZB;
GENERATE_INFOPLIST_FILE = YES;
HEADER_SEARCH_PATHS = "$(SRCROOT)/../tstestcontrol";
LIBRARY_SEARCH_PATHS = "$(SRCROOT)/../tstestcontrol";
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = io.tailscale.TailscaleXCTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/TailscaleKitXCTests/Test-Bridging-Header.h";
SWIFT_VERSION = 6.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TailscaleKitTestHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TailscaleKitTestHost";
};
name = Release;
};
C2EE3B642CCBE88400CF5BE0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = W5364U7YZB;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
C2EE3B652CCBE88400CF5BE0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = W5364U7YZB;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
C28640842CCA8C9E00CD5EBC /* Build configuration list for PBXNativeTarget "TailscaleKitTestHost" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C28640852CCA8C9E00CD5EBC /* Debug */,
C28640862CCA8C9E00CD5EBC /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C2BED0572CCF3031004A2544 /* Build configuration list for PBXAggregateTarget "libtstestcontrol" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C2BED0582CCF3031004A2544 /* Debug */,
C2BED0592CCF3031004A2544 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C2E1C2D42CC9B5A400ADC565 /* Build configuration list for PBXProject "TailscaleKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C2E1C2F02CC9B5A400ADC565 /* Debug */,
C2E1C2F12CC9B5A400ADC565 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C2E1C2ED2CC9B5A400ADC565 /* Build configuration list for PBXNativeTarget "TailscaleKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C2E1C2EE2CC9B5A400ADC565 /* Debug */,
C2E1C2EF2CC9B5A400ADC565 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C2E1C3172CCA8B7C00ADC565 /* Build configuration list for PBXNativeTarget "TailscaleKitXCTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C2E1C3182CCA8B7C00ADC565 /* Debug */,
C2E1C3192CCA8B7C00ADC565 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C2EE3B632CCBE88400CF5BE0 /* Build configuration list for PBXAggregateTarget "libtailscale" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C2EE3B642CCBE88400CF5BE0 /* Debug */,
C2EE3B652CCBE88400CF5BE0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = C2E1C2D12CC9B5A400ADC565 /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C2E1C2D92CC9B5A400ADC565"
BuildableName = "TailscaleKit.framework"
BlueprintName = "TailscaleKit"
ReferencedContainer = "container:TailscaleKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C2E1C2D92CC9B5A400ADC565"
BuildableName = "TailscaleKit.framework"
BlueprintName = "TailscaleKit"
ReferencedContainer = "container:TailscaleKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C28640612CCA8C9D00CD5EBC"
BuildableName = "TailscaleKitTestHost.app"
BlueprintName = "TailscaleKitTestHost"
ReferencedContainer = "container:TailscaleKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:TailscaleTestHost.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C2E1C30F2CCA8B7C00ADC565"
BuildableName = "TailscaleKitXCTests.xctest"
BlueprintName = "TailscaleKitXCTests"
ReferencedContainer = "container:TailscaleKit.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C28640612CCA8C9D00CD5EBC"
BuildableName = "TailscaleKitTestHost.app"
BlueprintName = "TailscaleKitTestHost"
ReferencedContainer = "container:TailscaleKit.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C28640612CCA8C9D00CD5EBC"
BuildableName = "TailscaleKitTestHost.app"
BlueprintName = "TailscaleKitTestHost"
ReferencedContainer = "container:TailscaleKit.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C2E1C30F2CCA8B7C00ADC565"
BuildableName = "TailscaleKitXCTests.xctest"
BlueprintName = "TailscaleKitXCTests"
ReferencedContainer = "container:TailscaleKit.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C2BED0552CCF3031004A2544"
BuildableName = "libtstestcontrol"
BlueprintName = "libtstestcontrol"
ReferencedContainer = "container:TailscaleKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C2BED0552CCF3031004A2544"
BuildableName = "libtstestcontrol"
BlueprintName = "libtstestcontrol"
ReferencedContainer = "container:TailscaleKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,103 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
import Combine
/// IncomingConnection is use to read incoming message from an inbound
/// connection. IncomingConnections are not instantiated directly,
/// they are returned by Listener.accept
public actor IncomingConnection {
private let logger: LogSink?
private var conn: TailscaleConnection = 0
private let reader: SocketReader
public let remoteAddress: String?
@Published public var state: ConnectionState = .idle
init(conn: TailscaleConnection, remoteAddress: String?, logger: LogSink? = nil) async {
self.logger = logger
self.conn = conn
self.state = .connected
self.remoteAddress = remoteAddress
reader = SocketReader(conn: conn)
}
deinit {
if conn != 0 {
unistd.close(conn)
}
}
public func close() {
if conn != 0 {
unistd.close(conn)
conn = 0
}
state = .closed
}
/// Returns up to size bytes from the connection. Blocks until
/// data is available
public func receive(maximumLength: Int = 4096, timeout: Int32) async throws -> Data {
guard state == .connected else {
throw TailscaleError.connectionClosed
}
return try await reader.read(timeout: timeout, len: maximumLength)
}
/// Reads a complete message from the connection
public func receiveMessage( timeout: Int32) async throws -> Data {
guard state == .connected else {
throw TailscaleError.connectionClosed
}
return try await reader.readAll(timeout: timeout)
}
}
/// Serializes read operations from an IncomingConnection
private actor SocketReader {
// We'll read in 2048 byte chunks which should be sufficient to hold the payload
// of a single packet
private static let maxBufferSize = 2048
private let conn: TailscaleConnection
private var buffer = [UInt8](repeating:0, count: maxBufferSize)
init(conn: TailscaleConnection) {
self.conn = conn
}
func read(timeout: Int32, len: Int) throws -> Data {
var p: pollfd = .init(fd: conn, events: Int16(POLLIN), revents: 0)
let res = poll(&p, 1, timeout)
guard res > 0 else {
throw TailscaleError.readFailed
}
let bytesToRead = min(len, Self.maxBufferSize)
var bytesRead = 0
buffer.withUnsafeMutableBufferPointer { ptr in
bytesRead = unistd.read(conn, ptr.baseAddress, bytesToRead)
}
if bytesRead < 0 {
throw TailscaleError.readFailed
}
return Data(buffer[0..<bytesRead])
}
func readAll(timeout: Int32) throws -> Data {
var data: Data = .init()
while true {
let read = try read(timeout: timeout, len: Self.maxBufferSize)
data.append(read)
if read.count < Self.maxBufferSize {
break
}
}
return data
}
}

View File

@@ -0,0 +1,120 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
import Combine
/// A Listener is used to await incoming connections from another
/// Tailnet node.
public actor Listener {
private var tailscale: TailscaleHandle
private var listener: TailscaleListener = 0
private var proto: NetProtocol
private var address: String
private let logger: LogSink?
@Published public var state: ListenterState = .idle
/// Initializes and readies a new listener
///
/// @param tailscale A handle to a Tailscale server
/// @param proto The ip protocol to listen for
/// @param address The adderss (ip:port or port) to listen on
/// @param logger An optional LogSink
init(tailscale: TailscaleHandle,
proto: NetProtocol,
address: String,
logger: LogSink? = nil) async throws {
self.logger = logger
self.tailscale = tailscale
self.address = address
self.proto = proto
let res = tailscale_listen(tailscale, proto.rawValue, address, &listener)
guard res == 0 else {
state = .failed
let msg = tailscale.getErrorMessage()
let err = TailscaleError.fromPosixErrCode(res, msg)
logger?.log("Listener failed to initialize: \(msg) (\(err.localizedDescription))")
throw err
}
state = .listening
}
deinit {
if listener != 0 {
unistd.close(listener)
}
}
/// Closes the listener. It cannot be restarted
/// Listeners will be closed automatically on deallocation
public func close() {
if listener != 0 {
unistd.close(listener)
listener = 0
}
state = .closed
}
/// Blocks and awaits a new incoming connection
///
/// @See tailscale_accept in Tailscale.h
/// @See tailscale_getremoteaddr in Tailscale.h
///
/// @param timeout The timeout for the underlying poll(2) in seconds. This has a maximum
/// value of Int32.max ms and supports millisecond precision per poll(2)
/// @throws TailscaleError on failure or timeout
/// @returns An incoming connection from which you can receive() Data
func accept(timeout: TimeInterval = 60) async throws -> IncomingConnection {
if timeout * 1000 > Double(Int32.max) || timeout < 0 {
throw TailscaleError.invalidTimeout
}
logger?.log("Listening for \(proto.rawValue) on \(address)")
var p: pollfd = .init(fd: listener, events: Int16(POLLIN), revents: 0)
let ret = poll(&p, 1, Int32(timeout * 1000))
guard ret > 0 else {
close()
throw TailscaleError.fromPosixErrCode(errno, "Poll failed")
}
logger?.log("Accepting \(proto.rawValue) connection via \(address)")
guard listener != 0 else {
close()
throw TailscaleError.listenerClosed
}
var connfd: Int32 = 0
let res = tailscale_accept(listener, &connfd)
guard res == 0 else {
close()
let msg = tailscale.getErrorMessage()
throw TailscaleError.fromPosixErrCode(res, msg)
}
/// We extract the remove address here for utility so you know
/// who's calling, so you can dial back.
var remoteAddress: String?
var buffer = [Int8](repeating:0, count: 64)
buffer.withUnsafeMutableBufferPointer { buf in
let err = tailscale_getremoteaddr(listener, connfd, buf.baseAddress, 64)
if err == 0 {
remoteAddress = String(cString: buf.baseAddress!)
} else {
let msg = tailscale.getErrorMessage()
let err = TailscaleError.fromPosixErrCode(err, msg)
logger?.log("Failed to get remote address: \(msg) \(err.localizedDescription)")
// Do not throw here. Lack of a remote address is not fatal
// The caller can directly invoke server.addrs() if required.
}
}
logger?.log("Accepted \(proto.rawValue) fd:\(connfd) from:\(remoteAddress ?? "unknown")")
return await IncomingConnection(conn: connfd,
remoteAddress: remoteAddress,
logger: logger)
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
/// A generic interface for sinking log messages from the Swift wrapper
/// and go
public protocol LogSink: Sendable {
/// An optional file handle. The go backend will write all internal logs
/// to this. STDOUT_FILENO or a handle to a writable file.
var logFileHandle: Int32? { get }
/// Called for swfit interal logs.
func log(_ message: String)
}
/// Dumps all internal logs to NSLog and go logs to stdout
struct DefaultLogger: LogSink {
var logFileHandle: Int32? = STDOUT_FILENO
func log(_ message: String) {
NSLog(message)
}
}
/// Discards all logs
struct BlackholeLogger: LogSink {
var logFileHandle: Int32?
func log(_ message: String) {
// Go back to the Shadow!
}
}

View File

@@ -0,0 +1,110 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
import Combine
/// ConnectionState indicates the state of individual TSConnection instances
public enum ConnectionState {
case idle ///< Reads and writes are not possible. Connections will transition to connected automatically
case connected ///< Connected and ready to read/write
case closed ///< Closed and ready to be disposed of. Closed connections cannot be reconnected.
case failed ///< The attempt to dial the connection failed
}
/// ListnerState indicates the state of individual TSListener instances
public enum ListenterState {
case idle ///< Waiting.
case listening ///< Listening
case closed ///< Closed and ready to be disposed of.
case failed ///< The attempt to start the listener failed
}
typealias TailscaleHandle = Int32
typealias TailscaleConnection = Int32
typealias TailscaleListener = Int32
/// Outgoing connections are used to send data to other endpoints
/// on the tailnet.
///
/// For HTTP(s), consider using URLSession.tailscaleSession
public actor OutgoingConnection {
private var tailscale: TailscaleHandle
private var proto: NetProtocol
private var address: String
private var conn: TailscaleConnection = 0
private let logger: LogSink
/// The state of the connection. Listen for transitions to determine
/// if the connection may be used for send/receive operations.
public var state: ConnectionState = .idle
/// Creates a new outgoing connection
///
/// @param tailscale The tailscale Server to use
/// @param address The remote address and port
/// @param proto The ip protocol
/// @param logger
///
/// @throws TailscaleError on failure
init(tailscale: TailscaleHandle,
to address: String,
proto: NetProtocol,
logger: LogSink) async throws {
self.logger = logger
self.proto = proto
self.address = address
self.tailscale = tailscale
}
/// Connects the outgoing connection to the remote. On success, the
/// connection state will be .connected.
///
/// @See tailscale_dial in Tailscale.h
///
/// @throws TailscaleError on failure
func connect() async throws {
let res = tailscale_dial(tailscale, proto.rawValue, address, &conn)
guard res == 0 else {
self.state = .failed
throw TailscaleError.fromPosixErrCode(res, tailscale.getErrorMessage())
}
self.state = .connected
}
deinit {
if conn != 0 {
unistd.close(conn)
}
}
/// Closes the outgoing connection. Further sends are not possible.
/// Connections will be closed on deallocation. Sets the connection
/// state to .closed
public func close() {
if conn != 0 {
unistd.close(conn)
conn = 0
}
state = .closed
}
/// Sends the given data to the connection
///
/// @throws TailscaleError on failure
public func send(_ data: Data) throws {
guard state == .connected else {
throw TailscaleError.connectionClosed
}
let bytesWritten = unistd.write(conn, data.withUnsafeBytes { $0.baseAddress! }, data.count)
if bytesWritten != data.count {
throw TailscaleError.shortWrite
}
}
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
public enum TailscaleError: Error {
case badInterfaceHandle ///< The tailscale handle is bad.
case listenerClosed ///< The listener was closed and cannot accept new connections
case invalidTimeout ///< The provided listener timeout is invalid
case connectionClosed ///< The underlying connection is closed
case readFailed ///< Read failed
case shortWrite ///< Some data was not written to the connection
case invalidProxyAddress ///< Some data was not written to the connection
case invalidControlURL ///< The provided control URL is invalid
case cannotFetchIps(_ details: String? = nil) ///< The IPs for the Tailscale server could not be read
case posixError(_ err: POSIXError, _ details: String? = nil) ///< A posix error was thrown with the given err code and details
case unknownPosixError(_ err: Int32, _ details: String? = nil) ///< An unknown posix error occurred
case internalError(_ details: String? = nil) ///< A generic internal error occurred
/// Create a Tailscale error from an underlying posix error code
static func fromPosixErrCode(_ code: Int32, _ details: String? = nil) -> TailscaleError {
if code == -1 {
return .internalError(details)
}
if let code = POSIXErrorCode(rawValue: code){
return .posixError( POSIXError(code))
}
return unknownPosixError(code, details)
}
}
extension TailscaleHandle {
static let kMaxErrorMessageLength: Int = 256
/// Returns the last error message in the Tailscale server as a string.
/// Handles messages up to kMaxErrorMessageLength bytes only.
internal func getErrorMessage() -> String {
let buf = UnsafeMutablePointer<Int8>.allocate(capacity: Self.kMaxErrorMessageLength)
defer {
buf.deallocate()
}
let res = tailscale_errmsg(self, buf, 256)
if res != 0 {
return "Error fetch failure: \(res)"
}
return String(cString: buf)
}
}

View File

@@ -0,0 +1,3 @@
# ``TailscaleKit``
Swift 6 wrapper for libtailscale

View File

@@ -0,0 +1,201 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
#import <Foundation/Foundation.h>
//! Project version number for Tailscale.
FOUNDATION_EXPORT double TailscaleKitVersionNumber;
//! Project version string for Tailscale.
FOUNDATION_EXPORT const unsigned char TailscaleKitVersionString[];
// TODO: Is there away to avoid the header duplication here?
// WARNING: Adding/changing the libtailscale functions must be replicated here
#include <stddef.h>
#ifndef TAILSCALEKIT_H
#define TAILSCALEKIT_H
#ifdef __cplusplus
extern "C" {
#endif
//
// Use this library to compile Tailscale into your program and get
// an entirely userspace IP address on a tailnet.
//
// From here you can listen for other programs on your tailnet dialing
// you, or connect directly to other services.
//
// tailscale is a handle onto a Tailscale server.
typedef int tailscale;
// tailscale_new creates a tailscale server object.
//
// No network connection is initialized until tailscale_start is called.
extern tailscale tailscale_new();
// tailscale_start connects the server to the tailnet.
//
// Calling this function is optional as it will be called by the first use
// of tailscale_listen or tailscale_dial on a server.
//
// See also: tailscale_up.
//
// Returns zero on success or -1 on error, call tailscale_errmsg for details.
extern int tailscale_start(tailscale sd);
// tailscale_up connects the server to the tailnet and waits for it to be usable.
//
// To cancel an in-progress call to tailscale_up, use tailscale_close.
//
// Returns zero on success or -1 on error, call tailscale_errmsg for details.
extern int tailscale_up(tailscale sd);
// tailscale_close shuts down the server.
//
// Returns:
// 0 - success
// EBADF - sd is not a valid tailscale
// -1 - other error, details printed to the tsnet logger
extern int tailscale_close(tailscale sd);
// The following set tailscale configuration options.
//
// Configure these options before any explicit or implicit call to tailscale_start.
//
// For details of each value see the godoc for the fields of tsnet.Server.
//
// Returns zero on success or -1 on error, call tailscale_errmsg for details.
extern int tailscale_set_dir(tailscale sd, const char* dir);
extern int tailscale_set_hostname(tailscale sd, const char* hostname);
extern int tailscale_set_authkey(tailscale sd, const char* authkey);
extern int tailscale_set_control_url(tailscale sd, const char* control_url);
extern int tailscale_set_ephemeral(tailscale sd, int ephemeral);
// tailscale_set_logfd instructs the tailscale instance to write logs to fd.
//
// An fd value of -1 means discard all logging.
//
// Returns zero on success or -1 on error, call tailscale_errmsg for details.
extern int tailscale_set_logfd(tailscale sd, int fd);
// A tailscale_conn is a connection to an address on the tailnet.
//
// It is a pipe(2) on which you can use read(2), write(2), and close(2).
// For extra control over the connection, see the tailscale_conn_* functions.
typedef int tailscale_conn;
// Returns the IP addresses of the the Tailscale server as
// a comma separated list.
//
// The provided buffer must be of sufficient size to hold the concatenated
// IPs as strings. This is typically <ipv4>,<ipv6> but maybe empty, or
// contain any number of ips. The caller is responsible for parsing
// the output. You may assume the output is a list of well-formed IPs.
//
// Returns:
// 0 - Success
// EBADF - sd is not a valid tailscale, or l or conn are not valid listeneras or connections
// ERANGE - insufficient storage for buf
extern int tailscale_getips(tailscale sd, char* buf, size_t buflen);
// tailscale_dial connects to the address on the tailnet.
//
// The newly allocated connection is written to conn_out.
//
// network is a NUL-terminated string of the form "tcp", "udp", etc.
// addr is a NUL-terminated string of an IP address or domain name.
//
// It will start the server if it has not been started yet.
//
// Returns zero on success or -1 on error, call tailscale_errmsg for details.
extern int tailscale_dial(tailscale sd, const char* network, const char* addr, tailscale_conn* conn_out);
// A tailscale_listener is a socket on the tailnet listening for connections.
//
// It is much like allocating a system socket(2) and calling listen(2).
// Accept connections with tailscale_accept and close the listener with close.
//
// Under the hood, a tailscale_listener is one half of a socketpair itself,
// used to move the connection fd from Go to C. This means you can use epoll
// or its equivalent on a tailscale_listener to know if there is a connection
// read to accept.
typedef int tailscale_listener;
// tailscale_listen listens for a connection on the tailnet.
//
// It is the spiritual equivalent to listen(2).
// The newly allocated listener is written to listener_out.
//
// network is a NUL-terminated string of the form "tcp", "udp", etc.
// addr is a NUL-terminated string of an IP address or domain name.
//
// It will start the server if it has not been started yet.
//
// Returns zero on success or -1 on error, call tailscale_errmsg for details.
extern int tailscale_listen(tailscale sd, const char* network, const char* addr, tailscale_listener* listener_out);
// Returns the remote address for an incoming connection for a particular listener. The address (eitehr ip4 or ip6)
// will ge written to buf on on success.
// Returns:
// 0 - Success
// EBADF - sd is not a valid tailscale, or l or conn are not valid listeneras or connections
// ERANGE - insufficient storage for buf
extern int tailscale_getremoteaddr(tailscale_listener l, tailscale_conn conn, char* buf, size_t buflen);
// tailscale_accept accepts a connection on a tailscale_listener.
//
// It is the spiritual equivalent to accept(2).
//
// The newly allocated connection is written to conn_out.
//
// Returns:
// 0 - success
// EBADF - listener is not a valid tailscale
// -1 - call tailscale_errmsg for details
extern int tailscale_accept(tailscale_listener listener, tailscale_conn* conn_out);
// tailscale_loopback starts a loopback address server.
//
// The server has multiple functions.
//
// It can be used as a SOCKS5 proxy onto the tailnet.
// Authentication is required with the username "tsnet" and
// the value of proxy_cred used as the password.
//
// The HTTP server also serves out the "LocalAPI" on /localapi.
// As the LocalAPI is powerful, access to endpoints requires BOTH passing a
// "Sec-Tailscale: localapi" HTTP header and passing local_api_cred as
// the basic auth password.
//
// The pointers proxy_cred_out and local_api_cred_out must be non-NIL
// and point to arrays that can hold 33 bytes. The first 32 bytes are
// the credential and the final byte is a NUL terminator.
//
// If tailscale_loopback returns, then addr_our, proxy_cred_out,
// and local_api_cred_out are all NUL-terminated.
//
// Returns zero on success or -1 on error, call tailscale_errmsg for details.
extern int tailscale_loopback(tailscale sd, char* addr_out, size_t addrlen, char* proxy_cred_out, char* local_api_cred_out);
// tailscale_errmsg writes the details of the last error to buf.
//
// After returning, buf is always NUL-terminated.
//
// Returns:
// 0 - success
// EBADF - sd is not a valid tailscale
// ERANGE - insufficient storage for buf
extern int tailscale_errmsg(tailscale sd, char* buf, size_t buflen);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,247 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
/// Configuration for a tailscale application node
public struct Configuration: Sendable {
let hostName: String ///< The hostname of the node/application instance
let path: String
let authKey: String? ///< An auth key. Leave empty to use web auth
let controlURL: String ///< URL for Tailscale control
let ephemeral: Bool
static let defaultControlURL = "https://controlplane.tailscale.com"
}
/// The layer 3 protocol to use
public enum NetProtocol: String {
case tcp = "tcp"
case udp = "udp"
}
public typealias IPAddresses = (ip4: String?, ip6: String?)
/// TSInterface creates and manages a single userspace Tailscale application
/// node. You may instantiate several "nodes" in a single application. Each
/// will get a unique IP address on the Tailnet.
///
/// The provided wrapper abstract away the C code and allow the writing of proper,
/// compiler checked thread-safe Swift 6.
public actor TailscaleNode {
/// Handle to the underlying Tailscale server. Use this when instantiating
/// new IncomingConnections or OutgoingConnections
let tailscale: TailscaleHandle?
private let logger: LogSink?
/// Instantiate a new TailscaleNode with the given configuration and
/// and optional LogSink. If no LogSink is provided, logs will be
/// discarded.
///
/// @See tailscale_set_* in Tailscale.h
/// @See tailscale_start in Tailscale.h
///
/// @throws TailscaleError on failure
public init(config: Configuration, logger: LogSink?) throws {
self.logger = logger ?? BlackholeLogger()
tailscale = tailscale_new()
guard let tailscale else {
throw TailscaleError.badInterfaceHandle
}
logger?.log("Tailscale starting: \(tailscale)")
if let fd = logger?.logFileHandle {
tailscale_set_logfd(tailscale, fd)
}
if let authKey = config.authKey {
tailscale_set_authkey(tailscale, authKey)
}
tailscale_set_hostname(tailscale, config.hostName)
tailscale_set_dir(tailscale, config.path)
tailscale_set_control_url(tailscale, config.controlURL)
tailscale_set_ephemeral(tailscale, config.ephemeral ? 1 : 0)
let res = tailscale_start(tailscale)
guard res == 0 else {
throw TailscaleError.fromPosixErrCode(res, tailscale.getErrorMessage())
}
logger?.log("Tailscale started... \(tailscale)")
}
deinit {
if let tailscale {
tailscale_close(tailscale)
}
}
/// Closes/stops the Tailscale server
///
/// @See tailscale_close in Tailscale.h
///
/// @Throws TailscaleError on failure
public func close() async throws {
guard let tailscale else {
throw TailscaleError.badInterfaceHandle
}
logger?.log("Closing Tailscale: \(tailscale)")
let res = tailscale_close(tailscale)
guard res == 0 else {
throw TailscaleError.fromPosixErrCode(res, tailscale.getErrorMessage())
}
logger?.log("Closed Tailscale:\(tailscale)")
}
/// Brings up the Tailscale server
///
/// @See tailscale_up in Tailscale.h
///
/// @throws TailscaleError on failure
public func up() async throws {
guard let tailscale else {
throw TailscaleError.badInterfaceHandle
}
logger?.log("Bringing Tailscale up :\(tailscale)")
let res = tailscale_up(tailscale)
guard res == 0 else {
throw TailscaleError.fromPosixErrCode(res, tailscale.getErrorMessage())
}
logger?.log("Brought Tailscale up:\(tailscale)")
}
/// Tears down the Tailscale server.
///
/// @See tailscale_down in Tailscale.h
///
/// @throws TailscaleError on failure
public func down() throws {
guard let tailscale else {
throw TailscaleError.badInterfaceHandle
}
logger?.log("Taking Tailscale down :\(tailscale)")
let res = tailscale_up(tailscale)
guard res == 0 else {
throw TailscaleError.fromPosixErrCode(res, tailscale.getErrorMessage())
}
logger?.log("Took Tailscale down:\(tailscale)")
}
/// Returns the addresses on the Tailscale server
///
/// @See tailscale_getips in Tailscale.h
///
/// @returns An ipV4 and ipV5 address tuple
/// @throws TailscaleError on failure
public func addrs() async throws -> IPAddresses {
guard let tailscale else {
throw TailscaleError.badInterfaceHandle
}
let buf = UnsafeMutablePointer<Int8>.allocate(capacity: 128)
defer {
buf.deallocate()
}
let res = tailscale_getips(tailscale, buf, 128)
guard res == 0 else {
throw TailscaleError.fromPosixErrCode(res, tailscale.getErrorMessage())
}
let ipList = String(cString: buf)
return ipList.toIPPair()
}
public struct LoopbackConfig: Sendable {
let address: String
let proxyCredential: String
let localAPIKey: String
}
private var loopbackConfig: LoopbackConfig?
/// Starts and returns the address and credentials of a SOCKS5 proxy which can also
/// be used to query the localAPI
public func loopback() throws -> LoopbackConfig {
guard let tailscale else {
throw TailscaleError.badInterfaceHandle
}
if let loopbackConfig = loopbackConfig {
return loopbackConfig
}
let addrBuf = UnsafeMutablePointer<Int8>.allocate(capacity: 64)
let proxyCredBuf = UnsafeMutablePointer<Int8>.allocate(capacity: 33)
let apiCredBuf = UnsafeMutablePointer<Int8>.allocate(capacity: 33)
defer {
addrBuf.deallocate()
proxyCredBuf.deallocate()
apiCredBuf.deallocate()
}
let res = tailscale_loopback(tailscale, addrBuf, 64, proxyCredBuf, apiCredBuf)
guard res == 0 else {
throw TailscaleError.fromPosixErrCode(res, tailscale.getErrorMessage())
}
loopbackConfig = LoopbackConfig(address: String(cString: addrBuf),
proxyCredential: String(cString: proxyCredBuf),
localAPIKey: String(cString: apiCredBuf))
return loopbackConfig!
}
}
// MARK: - IP String list to IPAddresses tuple
enum IPAddrType {
case v4
case v6
case none
}
extension String {
// tailscale.go sends us the tailnetIPs as a comma separated list. This will
// turn them into an IPAddresses tuple
func toIPPair() -> IPAddresses {
let ips = self.split(separator: ",").map { String($0) }
var result: IPAddresses = (nil, nil)
for ip in ips {
let type = ip.tsNetIPAddrType()
switch type {
case .v4:
result.ip4 = ip
case .v6:
result.ip6 = ip
case .none:
break
}
}
return result
}
// This can be naive since the backend is only vending well
// formed IPs to us.
func tsNetIPAddrType() -> IPAddrType {
if self.contains(".") {
return .v4
} else if self.contains(":") {
return .v6
}
return .none
}
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
extension URLSessionConfiguration {
/// Adds the a connectionProxyDictionary to a URLSessionConfiguration to
/// proxy all requests through the given TailscaleNode.
///
/// This can also be use to make requests to LocalAPI
func proxyVia(_ node: TailscaleNode) async throws {
let proxyConfig = try await node.loopback()
// The address is always v4 and it's always <ip>:<port>
let parts = proxyConfig.address.split(separator: ":")
let addr = parts.first
let port = parts.last
guard parts.count == 2, let addr, let port else {
throw TailscaleError.invalidProxyAddress
}
self.connectionProxyDictionary = [
kCFProxyTypeKey: kCFProxyTypeSOCKS,
kCFProxyUsernameKey: "tsnet",
kCFProxyPasswordKey: proxyConfig.proxyCredential,
kCFNetworkProxiesSOCKSEnable: true,
kCFNetworkProxiesSOCKSProxy: addr,
kCFNetworkProxiesSOCKSPort: port
]
}
static func tailscaleSession(_ node: TailscaleNode) async throws -> URLSessionConfiguration {
let config = URLSessionConfiguration.default
try await config.proxyVia(node)
return config
}
}

View File

@@ -0,0 +1,15 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
import SwiftUI
/// We need a proper host process to run the unit tests
struct ContentView: View {
var body: some View {
VStack {
Text("Tailscale Test Host")
}
.padding()
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
import SwiftUI
@main
struct TailscaleKitTestHostApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

View File

@@ -0,0 +1,199 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
import XCTest
@testable import TailscaleKit
final class TailscaleKitTests: XCTestCase {
var controlURL: String = ""
override func setUp() async throws {
if controlURL == "" {
var buf = [CChar](repeating:0, count: 1024)
let res = buf.withUnsafeMutableBufferPointer { ptr in
return run_control(ptr.baseAddress!, 1024)
}
controlURL = String(validatingCString: buf) ?? ""
guard !controlURL.isEmpty else {
throw TailscaleError.invalidControlURL
}
if res == 0 {
print("Started control with url \(controlURL)")
}
}
}
override func tearDown() async throws {
stop_control()
}
func testV4() async throws {
try await runConnectionTests(for: .v4)
}
func testV6() async throws {
try await runConnectionTests(for: .v6)
}
func runConnectionTests(for netType: IPAddrType) async throws {
let logger = BlackholeLogger()
let want = "Hello Tailscale".data(using: .utf8)!
do {
let ts1 = try TailscaleNode(config: mockConfig(), logger: logger)
try await ts1.up()
let ts2 = try TailscaleNode(config: mockConfig(), logger: logger)
try await ts2.up()
let ts1_addr = try await ts1.addrs()
let ts2_addr = try await ts2.addrs()
print("ts1 addresses are \(ts1_addr)")
print("ts2_adddreses are \(ts2_addr)")
let msgReceived = expectation(description: "ex")
let lisetnerUp = expectation(description: "lisetnerUp")
var listenerAddr: String?
var writerAddr: String?
switch netType {
case .v4:
listenerAddr = ts1_addr.ip4
writerAddr = ts2_addr.ip4
case .v6:
// barnstar: Validity of listener IPs is loadbearing. accept fails
// in the C code if you listen on an invalid addr.
listenerAddr = if let a = ts1_addr.ip6 { "[\(a)]"} else { nil }
writerAddr = if let a = ts2_addr.ip6 { "[\(a)]"} else { nil }
case .none:
XCTFail("Invalid IP Type")
}
guard let ts1Handle = await ts1.tailscale,
let ts2Handle = await ts2.tailscale,
let listenerAddr else {
XCTFail()
return
}
// Run a listener in a separate task, wait for the inbound
// connection and read the data
Task {
let listener = try await Listener(tailscale: ts1Handle,
proto: .tcp,
address: ":8081",
logger: logger)
lisetnerUp.fulfill()
let inbound = try await listener.accept()
await listener.close()
// We can trust the backend here but this is slightly flaky since remoteAddress can be
// nil for legitimate reasons.
// let inboundIP = await inbound.remoteAddress
// XCTAssertEqual(inboundIP, writerAddr)
let got = try await inbound.receiveMessage(timeout: 2)
print("got \(got)")
XCTAssert(got == want)
msgReceived.fulfill()
}
//Make sure somebody is listening
await fulfillment(of: [lisetnerUp], timeout: 5.0)
let outgoing = try await OutgoingConnection(tailscale: ts2Handle,
to: "\(listenerAddr):8081",
proto: .tcp,
logger: logger)
try await outgoing.connect()
print("sending \(want)")
try await outgoing.send(want)
await fulfillment(of: [msgReceived], timeout: 5.0)
print("closing conn")
await outgoing.close()
try await ts1.down()
try await ts2.down()
} catch {
XCTFail("Init Failed: \(error)")
}
}
/// The hostCount here is load bearing. Each mock host must have a unique
/// path and hostname.
var hostCount = 0
func mockConfig() -> Configuration {
let temp = getDocumentDirectoryPath().absoluteString + "tailscale\(hostCount)"
hostCount += 1
return Configuration(
hostName: "testHost-\(hostCount)",
path: temp,
authKey: nil,
controlURL: controlURL,
ephemeral: false)
}
func testProxy() async throws {
let config = mockConfig()
let logger = BlackholeLogger()
do {
let ts1 = try TailscaleNode(config: config, logger: logger)
try await ts1.up()
let sessionConfig = try await URLSessionConfiguration.tailscaleSession(ts1)
let session = URLSession(configuration: sessionConfig)
let url = URL(string: "https://tailscale.com")!
let req = URLRequest(url: url)
let (data, _) = try await session.data(for: req)
print("Got proxied data \(data.count)")
XCTAssert(data.count > 0)
}
}
func exampleProxiedTailnetRequest() async throws {
let logger = DefaultLogger()
do {
let temp = getDocumentDirectoryPath().absoluteString + "tailscale\(hostCount)"
let authKey = "put-you-auth-key-key-here"
let config = Configuration(hostName: "TSNet-Test",
path: temp,
authKey: authKey,
controlURL: Configuration.defaultControlURL,
ephemeral: true)
let ts1 = try TailscaleNode(config: config, logger: logger)
try await ts1.up()
let sessionConfig = try await URLSessionConfiguration.tailscaleSession(ts1)
let session = URLSession(configuration: sessionConfig)
// Replace this with the IP or fqdn of a service running on your tailnet
let url = URL(string: "https://myservice.my-tailnet.ts.net")!
let req = URLRequest(url: url)
let (data, _) = try await session.data(for: req)
print("Got proxied data \(data.count)")
XCTAssert(data.count > 0)
}
}
}
func getDocumentDirectoryPath() -> URL {
let arrayPaths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let docDirectoryPath = arrayPaths[0]
return docDirectoryPath
}

View File

@@ -0,0 +1,15 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
#include <stddef.h>
// External definitions for libtstestcontrol
// TODO: Is there away to avoid the header duplication here?
// WARNING: Adding/changing the libtstestcontrol functions must be replicated here
// Runs a new control. Returns the URL in the buffer
extern int run_control(char* buf, size_t buflen);
// Stops the running control
extern void stop_control();

View File

@@ -19,6 +19,8 @@ extern int TsnetSetAuthKey(int sd, char* str);
extern int TsnetSetControlURL(int sd, char* str);
extern int TsnetSetEphemeral(int sd, int ephemeral);
extern int TsnetSetLogFD(int sd, int fd);
extern int TsnetGetIps(int sd, char *buf, size_t buflen);
extern int TsnetGetRemoteAddr(int listener, int conn, char *buf, size_t buflen);
extern int TsnetListen(int sd, char* net, char* addr, int* listenerOut);
extern int TsnetLoopback(int sd, char* addrOut, size_t addrLen, char* proxyOut, char* localOut);
@@ -70,6 +72,14 @@ int tailscale_accept(tailscale_listener ld, tailscale_conn* conn_out) {
return 0;
}
int tailscale_getremoteaddr(tailscale_listener l, tailscale_conn conn, char* buf, size_t buflen) {
return TsnetGetRemoteAddr(l, conn, buf, buflen);
}
int tailscale_getips(tailscale sd, char* buf, size_t buflen) {
return TsnetGetIps(sd, buf, buflen);
}
int tailscale_set_dir(tailscale sd, const char* dir) {
return TsnetSetDir(sd, (char*)dir);
}

View File

@@ -13,6 +13,8 @@ import (
"io"
"net"
"os"
"regexp"
"strings"
"sync"
"syscall"
"unsafe"
@@ -57,6 +59,8 @@ type listener struct {
s *server
ln net.Listener
fd int // go side fd of socketpair sent to C
mu sync.Mutex
m map[C.int]net.Addr //maps fds to remote addresses for lookup
}
// conns tracks all the pipe(2)s allocated via tsnet_dial.
@@ -141,6 +145,36 @@ func TsnetClose(sd C.int) C.int {
return 0
}
//export TsnetGetIps
func TsnetGetIps(sd C.int, buf *C.char, buflen C.size_t) C.int {
if buf == nil {
panic("errmsg passed nil buf")
} else if buflen == 0 {
panic("errmsg passed buflen of 0")
}
servers.mu.Lock()
s := servers.m[sd]
servers.mu.Unlock()
out := unsafe.Slice((*byte)(unsafe.Pointer(buf)), buflen)
if s == nil {
out[0] = '\x00'
return C.EBADF
}
ip4, ip6 := s.s.TailscaleIPs()
joined := strings.Join([]string{ip4.String(), ip6.String()}, ",")
n := copy(out, joined)
if len(out) < len(joined)-1 {
out[len(out)-1] = '\x00' // always NUL-terminate
return C.ERANGE
}
out[n] = '\x00'
return 0
}
//export TsnetErrmsg
func TsnetErrmsg(sd C.int, buf *C.char, buflen C.size_t) C.int {
if buf == nil {
@@ -195,7 +229,8 @@ func TsnetListen(sd C.int, network, addr *C.char, listenerOut *C.int) C.int {
if listeners.m == nil {
listeners.m = map[C.int]*listener{}
}
listeners.m[fdC] = &listener{s: s, ln: ln, fd: sp}
listener := &listener{s: s, ln: ln, fd: sp, m: map[C.int]net.Addr{}}
listeners.m[fdC] = listener
listeners.mu.Unlock()
cleanup := func() {
@@ -246,6 +281,12 @@ func TsnetListen(sd C.int, network, addr *C.char, listenerOut *C.int) C.int {
netConn.Close()
// fallthrough to close connFd, then continue Accept()ing
}
// map the connection to the remote address
listener.mu.Lock()
listener.m[connFd] = netConn.RemoteAddr()
listener.mu.Unlock()
syscall.Close(int(connFd)) // now owned by recvmsg
}
}()
@@ -309,6 +350,49 @@ func newConn(s *server, netConn net.Conn, connOut *C.int) error {
return nil
}
//export TsnetGetRemoteAddr
func TsnetGetRemoteAddr(listener C.int, conn C.int, buf *C.char, buflen C.size_t) C.int {
if buf == nil {
panic("errmsg passed nil buf")
} else if buflen == 0 {
panic("errmsg passed buflen of 0")
}
out := unsafe.Slice((*byte)(unsafe.Pointer(buf)), buflen)
listeners.mu.Lock()
defer listeners.mu.Unlock()
l := listeners.m[listener]
if l == nil {
out[0] = '\x00'
return C.EBADF
}
l.mu.Lock()
defer l.mu.Unlock()
addr, ok := l.m[conn]
if !ok {
out[0] = '\x00'
return C.EBADF
}
ip := extractIP(addr.String())
n := copy(out, ip)
if len(out) < len(ip)-1 {
out[len(out)-1] = '\x00' // always NUL-terminate
return C.ERANGE
}
out[n] = '\x00'
return 0
}
// Strips the port from connection IPs
func extractIP(ipWithPort string) string {
re := regexp.MustCompile(`(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|\[([0-9a-fA-F:]+)\]`)
match := re.FindString(ipWithPort)
return match
}
//export TsnetDial
func TsnetDial(sd C.int, network, addr *C.char, connOut *C.int) C.int {
s, err := getServer(sd)
@@ -392,6 +476,7 @@ func TsnetSetLogFD(sd, fd C.int) C.int {
f := os.NewFile(uintptr(fd), "logfd")
s.s.Logf = func(format string, args ...any) {
fmt.Fprintf(f, format, args...)
fmt.Fprintf(f, "\n")
}
return 0
}

View File

@@ -67,6 +67,7 @@ extern int tailscale_set_hostname(tailscale sd, const char* hostname);
extern int tailscale_set_authkey(tailscale sd, const char* authkey);
extern int tailscale_set_control_url(tailscale sd, const char* control_url);
extern int tailscale_set_ephemeral(tailscale sd, int ephemeral);
// tailscale_set_logfd instructs the tailscale instance to write logs to fd.
//
// An fd value of -1 means discard all logging.
@@ -80,6 +81,20 @@ extern int tailscale_set_logfd(tailscale sd, int fd);
// For extra control over the connection, see the tailscale_conn_* functions.
typedef int tailscale_conn;
// Returns the IP addresses of the the Tailscale server as
// a comma separated list.
//
// The provided buffer must be of sufficient size to hold the concatenated
// IPs as strings. This is typically <ipv4>,<ipv6> but maybe empty, or
// contain any number of ips. The caller is responsible for parsing
// the output. You may assume the output is a list of well-formed IPs.
//
// Returns:
// 0 - Success
// EBADF - sd is not a valid tailscale, or l or conn are not valid listeneras or connections
// ERANGE - insufficient storage for buf
extern int tailscale_getips(tailscale sd, char* buf, size_t buflen);
// tailscale_dial connects to the address on the tailnet.
//
// The newly allocated connection is written to conn_out.
@@ -116,6 +131,15 @@ typedef int tailscale_listener;
// Returns zero on success or -1 on error, call tailscale_errmsg for details.
extern int tailscale_listen(tailscale sd, const char* network, const char* addr, tailscale_listener* listener_out);
// Returns the remote address for an incoming connection for a particular listener. The address (eitehr ip4 or ip6)
// will ge written to buf on on success.
// Returns:
// 0 - Success
// EBADF - sd is not a valid tailscale, or l or conn are not valid listeneras or connections
// ERANGE - insufficient storage for buf
extern int tailscale_getremoteaddr(tailscale_listener l, tailscale_conn conn, char* buf, size_t buflen);
// tailscale_accept accepts a connection on a tailscale_listener.
//
// It is the spiritual equivalent to accept(2).

View File

@@ -58,3 +58,22 @@ func TestConn(t *testing.T) {
t.Errorf("want no remaining tsnet_listener objects, got %d", remLns)
}
}
func TestExtractIP(t *testing.T) {
ipv4 := "1.23.33.4:12343"
ipv6 := "[1::2234::34fc::44]:56576"
got4 := extractIP(ipv4)
got6 := extractIP(ipv6)
want4 := "1.23.33.4"
want6 := "[1::2234::34fc::44]"
if got4 != want4 {
t.Errorf("ipv4 port stripping failed")
}
if got6 != want6 {
t.Errorf("ipv6 port stripping failed %s != %s", got6, want6)
}
}

10
tstestcontrol/Makefile Normal file
View File

@@ -0,0 +1,10 @@
# Copyright (c) Tailscale Inc & AUTHORS
# SPDX-License-Identifier: BSD-3-Clause
all:
go build -buildmode=c-archive -o libtstestcontrol.a
clean:
rm libtstestcontrol.a
rm libtstestcontrol.h

View File

@@ -0,0 +1,23 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
#include "tstestcontrol.h"
#include <stdio.h>
// Functions exported by go
extern long long RunControl(char* buf, size_t buflen);
extern void StopControl();
// run_control starts an ephemeral control test server on localhost.
// buf must be a char* of sufficient size to hold the resulting URL
// stop_control must be called when you are finished with the instance
//
// returns -1 on failure, 0 on success
int run_control(char* buf, size_t buflen) {
return RunControl(buf, buflen);
}
// stop_control() stops the e
void stop_control() {
StopControl();
}

View File

@@ -0,0 +1,212 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// A Go c-archive of tsnet integration/control test utilities
// This mirrors athe functionality in tstest without the depenency
// on go tests so it can be bundled as a static library and used to drive
// integration tests on other platforms
package main
import "C"
//#include "errno.h"
import (
"context"
"crypto/tls"
"errors"
"net"
"net/http"
"net/http/httptest"
"sync"
"unsafe"
"tailscale.com/net/netaddr"
"tailscale.com/net/netns"
"tailscale.com/net/stun"
"tailscale.com/tstest/integration/testcontrol"
"tailscale.com/types/logger"
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/nettype"
)
func main() {}
//export StopControl
func StopControl() {
netns.SetEnabled(true)
if control != nil {
control.HTTPTestServer.Close()
}
cleanup()
control = nil
}
var control *testcontrol.Server
var cleanup func()
// TODO(barnstar): by purging this of the go testing dependency, we lost some logging and things fail silently.
// that needs to be plumbed back in.
//export RunControl
func RunControl(buf *C.char, buflen C.size_t) C.int {
if control != nil {
return -1
}
if buf == nil {
return -1
} else if buflen == 0 {
return -1
}
out := unsafe.Slice((*byte)(unsafe.Pointer(buf)), buflen)
// Corp#4520: don't use netns for tests.
netns.SetEnabled(false)
derpLogf := logger.Discard
derpMap, c, err := runDERPAndSTUN(derpLogf, "127.0.0.1")
if err != nil {
out[0] = '\x00'
return -1
}
control := &testcontrol.Server{
DERPMap: derpMap,
}
control.HTTPTestServer = httptest.NewUnstartedServer(control)
control.HTTPTestServer.Start()
controlURL := control.HTTPTestServer.URL
cleanup = c
n := copy(out, controlURL)
out[n] = '\x00'
return 0
}
// RunDERPAndSTUN runs a local DERP and STUN server for tests, returning the derpMap
// that clients should use. This creates resources that must be cleaned up with the
// returned cleanup function.
func runDERPAndSTUN(logf logger.Logf, ipAddress string) (derpMap *tailcfg.DERPMap, cleanup func(), err error) {
d := derp.NewServer(key.NewNode(), logf)
ln, err := net.Listen("tcp", net.JoinHostPort(ipAddress, "0"))
if err != nil {
return nil, nil, err
}
httpsrv := httptest.NewUnstartedServer(derphttp.Handler(d))
httpsrv.Listener.Close()
httpsrv.Listener = ln
httpsrv.Config.ErrorLog = logger.StdLogger(logf)
httpsrv.Config.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
httpsrv.StartTLS()
stunAddr, stunCleanup, err := serveWithPacketListener(nettype.Std{})
if err != nil {
return nil, nil, err
}
m := &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
1: {
RegionID: 1,
RegionCode: "test",
Nodes: []*tailcfg.DERPNode{
{
Name: "t1",
RegionID: 1,
HostName: ipAddress,
IPv4: ipAddress,
IPv6: "none",
STUNPort: stunAddr.Port,
DERPPort: httpsrv.Listener.Addr().(*net.TCPAddr).Port,
InsecureForTests: true,
STUNTestIP: ipAddress,
},
},
},
},
}
logf("DERP httpsrv listener: %v", httpsrv.Listener.Addr())
cleanupfn := func() {
httpsrv.CloseClientConnections()
httpsrv.Close()
d.Close()
stunCleanup()
ln.Close()
}
return m, cleanupfn, nil
}
type stunStats struct {
mu sync.Mutex
// +checklocks:mu
readIPv4 int
// +checklocks:mu
readIPv6 int
}
func serveWithPacketListener(ln nettype.PacketListener) (addr *net.UDPAddr, cleanupFn func(), err error) {
// TODO(crawshaw): use stats to test re-STUN logic
var stats stunStats
pc, err := ln.ListenPacket(context.Background(), "udp4", ":0")
if err != nil {
return nil, nil, err
}
addr = pc.LocalAddr().(*net.UDPAddr)
if len(addr.IP) == 0 || addr.IP.IsUnspecified() {
addr.IP = net.ParseIP("127.0.0.1")
}
doneCh := make(chan struct{})
go runSTUN(pc.(nettype.PacketConn), &stats, doneCh)
return addr, func() {
pc.Close()
<-doneCh
}, nil
}
func runSTUN(pc nettype.PacketConn, stats *stunStats, done chan<- struct{}) {
defer close(done)
var buf [64 << 10]byte
for {
n, src, err := pc.ReadFromUDPAddrPort(buf[:])
if err != nil {
if errors.Is(err, net.ErrClosed) {
return
}
continue
}
src = netaddr.Unmap(src)
pkt := buf[:n]
if !stun.Is(pkt) {
continue
}
txid, err := stun.ParseBindingRequest(pkt)
if err != nil {
continue
}
stats.mu.Lock()
if src.Addr().Is4() {
stats.readIPv4++
} else {
stats.readIPv6++
}
stats.mu.Unlock()
res := stun.Response(txid, src)
if _, err := pc.WriteToUDPAddrPort(res, src); err != nil {
// TODO(barnstar): inject logging from C
}
}
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
#include <stddef.h>
#ifndef TAILSCALE_H
#define TAILSCALE_H
#ifdef __cplusplus
extern "C" {
#endif
// External definitions for libtstestcontrol.h
// Runs a new control. Returns the URL in the buffer
// returns 0 on success, an error code on failure
extern int run_control(char* buf, size_t buflen);
// Stops the running control
extern void stop_control();
#ifdef __cplusplus
}
#endif
#endif