From 2948fe1136ef0d7b5be400d705b1ecbb2fcdcba1 Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Thu, 2 Jan 2025 14:32:59 -0500 Subject: [PATCH] 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 --- .gitignore | 10 +- go.mod | 135 +-- go.sum | 413 +++++---- swift/Makefile | 20 + swift/README.md | 91 ++ swift/TailscaleKit.xcodeproj/project.pbxproj | 858 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcschemes/TailscaleKit.xcscheme | 67 ++ .../xcschemes/TailscaleKitTestHost.xcscheme | 96 ++ .../xcschemes/TailscaleKitXCTests.xcscheme | 55 ++ .../xcschemes/libtstestcontrol.xcscheme | 67 ++ swift/TailscaleKit/IncomingConnection.swift | 103 +++ swift/TailscaleKit/Listener.swift | 120 +++ swift/TailscaleKit/LogSink.swift | 31 + swift/TailscaleKit/OutgoingConnection.swift | 110 +++ swift/TailscaleKit/TailscaleError.swift | 50 + .../TailscaleKit.docc/Tailscale.md | 3 + swift/TailscaleKit/TailscaleKit.h | 201 ++++ swift/TailscaleKit/TailscaleNode.swift | 247 +++++ swift/TailscaleKit/URLSession+Tailscale.swift | 37 + swift/TailscaleKitTestHost/ContentView.swift | 15 + swift/TailscaleKitTestHost/Info.plist | 11 + .../TailscaleKitTestHost.entitlements | 14 + .../TailscaleKitTestHostApp.swift | 13 + .../TailscaleKitTests.swift | 199 ++++ .../Test-Bridging-Header.h | 15 + tailscale.c | 10 + tailscale.go | 87 +- tailscale.h | 24 + tailscale_test.go | 19 + tstestcontrol/Makefile | 10 + tstestcontrol/tstestcontrol.c | 23 + tstestcontrol/tstestcontrol.go | 212 +++++ tstestcontrol/tstestcontrol.h | 26 + 34 files changed, 3139 insertions(+), 260 deletions(-) create mode 100644 swift/Makefile create mode 100644 swift/README.md create mode 100644 swift/TailscaleKit.xcodeproj/project.pbxproj create mode 100644 swift/TailscaleKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/TailscaleKit.xcscheme create mode 100644 swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/TailscaleKitTestHost.xcscheme create mode 100644 swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/TailscaleKitXCTests.xcscheme create mode 100644 swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/libtstestcontrol.xcscheme create mode 100644 swift/TailscaleKit/IncomingConnection.swift create mode 100644 swift/TailscaleKit/Listener.swift create mode 100644 swift/TailscaleKit/LogSink.swift create mode 100644 swift/TailscaleKit/OutgoingConnection.swift create mode 100644 swift/TailscaleKit/TailscaleError.swift create mode 100644 swift/TailscaleKit/TailscaleKit.docc/Tailscale.md create mode 100644 swift/TailscaleKit/TailscaleKit.h create mode 100644 swift/TailscaleKit/TailscaleNode.swift create mode 100644 swift/TailscaleKit/URLSession+Tailscale.swift create mode 100644 swift/TailscaleKitTestHost/ContentView.swift create mode 100644 swift/TailscaleKitTestHost/Info.plist create mode 100644 swift/TailscaleKitTestHost/TailscaleKitTestHost.entitlements create mode 100644 swift/TailscaleKitTestHost/TailscaleKitTestHostApp.swift create mode 100644 swift/TailscaleKitXCTests/TailscaleKitTests.swift create mode 100644 swift/TailscaleKitXCTests/Test-Bridging-Header.h create mode 100644 tstestcontrol/Makefile create mode 100644 tstestcontrol/tstestcontrol.c create mode 100644 tstestcontrol/tstestcontrol.go create mode 100644 tstestcontrol/tstestcontrol.h diff --git a/.gitignore b/.gitignore index 86050ae..d36329c 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ \ No newline at end of file + +/vendor/ diff --git a/go.mod b/go.mod index 8a9036b..0e80699 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 5e9e10e..e702a3d 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/swift/Makefile b/swift/Makefile new file mode 100644 index 0000000..375ceb2 --- /dev/null +++ b/swift/Makefile @@ -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) \ No newline at end of file diff --git a/swift/README.md b/swift/README.md new file mode 100644 index 0000000..1ca7199 --- /dev/null +++ b/swift/README.md @@ -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). diff --git a/swift/TailscaleKit.xcodeproj/project.pbxproj b/swift/TailscaleKit.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a132530 --- /dev/null +++ b/swift/TailscaleKit.xcodeproj/project.pbxproj @@ -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 = ""; }; + 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 = ""; }; + 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 = ""; }; + C2E3E8802D2718D6004992A2 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; +/* 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 = ""; + }; + C2E1C2DC2CC9B5A400ADC565 /* TailscaleKit */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + C2E1C2EC2CC9B5A400ADC565 /* Exceptions for "TailscaleKit" folder in "TailscaleKit" target */, + ); + path = TailscaleKit; + sourceTree = ""; + }; + C2E1C3112CCA8B7C00ADC565 /* TailscaleKitXCTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + C2EE3B702CCC179300CF5BE0 /* Exceptions for "TailscaleKitXCTests" folder in "Compile Sources" phase from "TailscaleKitXCTests" target */, + ); + path = TailscaleKitXCTests; + sourceTree = ""; + }; +/* 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 = ""; + }; + C2E1C2DB2CC9B5A400ADC565 /* Products */ = { + isa = PBXGroup; + children = ( + C2E1C2DA2CC9B5A400ADC565 /* TailscaleKit.framework */, + C2E1C3102CCA8B7C00ADC565 /* TailscaleKitXCTests.xctest */, + C28640622CCA8C9D00CD5EBC /* TailscaleKitTestHost.app */, + ); + name = Products; + sourceTree = ""; + }; + C2E1C2FB2CC9B9E300ADC565 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C2BED05C2CCFC68D004A2544 /* libtstestcontrol.a */, + C2E1C2FC2CC9B9E300ADC565 /* libtailscale.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* 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 */; +} diff --git a/swift/TailscaleKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/swift/TailscaleKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/swift/TailscaleKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/TailscaleKit.xcscheme b/swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/TailscaleKit.xcscheme new file mode 100644 index 0000000..1c775c4 --- /dev/null +++ b/swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/TailscaleKit.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/TailscaleKitTestHost.xcscheme b/swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/TailscaleKitTestHost.xcscheme new file mode 100644 index 0000000..350934f --- /dev/null +++ b/swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/TailscaleKitTestHost.xcscheme @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/TailscaleKitXCTests.xcscheme b/swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/TailscaleKitXCTests.xcscheme new file mode 100644 index 0000000..d85e8c1 --- /dev/null +++ b/swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/TailscaleKitXCTests.xcscheme @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/libtstestcontrol.xcscheme b/swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/libtstestcontrol.xcscheme new file mode 100644 index 0000000..02a4f8f --- /dev/null +++ b/swift/TailscaleKit.xcodeproj/xcshareddata/xcschemes/libtstestcontrol.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swift/TailscaleKit/IncomingConnection.swift b/swift/TailscaleKit/IncomingConnection.swift new file mode 100644 index 0000000..9a13ce6 --- /dev/null +++ b/swift/TailscaleKit/IncomingConnection.swift @@ -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.. 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 + } +} + diff --git a/swift/TailscaleKit/Listener.swift b/swift/TailscaleKit/Listener.swift new file mode 100644 index 0000000..c1d8d2f --- /dev/null +++ b/swift/TailscaleKit/Listener.swift @@ -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) + } +} diff --git a/swift/TailscaleKit/LogSink.swift b/swift/TailscaleKit/LogSink.swift new file mode 100644 index 0000000..30931b4 --- /dev/null +++ b/swift/TailscaleKit/LogSink.swift @@ -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! + } +} diff --git a/swift/TailscaleKit/OutgoingConnection.swift b/swift/TailscaleKit/OutgoingConnection.swift new file mode 100644 index 0000000..cca0f61 --- /dev/null +++ b/swift/TailscaleKit/OutgoingConnection.swift @@ -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 + } + } +} diff --git a/swift/TailscaleKit/TailscaleError.swift b/swift/TailscaleKit/TailscaleError.swift new file mode 100644 index 0000000..9cfe7eb --- /dev/null +++ b/swift/TailscaleKit/TailscaleError.swift @@ -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.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) + } +} diff --git a/swift/TailscaleKit/TailscaleKit.docc/Tailscale.md b/swift/TailscaleKit/TailscaleKit.docc/Tailscale.md new file mode 100644 index 0000000..236b59e --- /dev/null +++ b/swift/TailscaleKit/TailscaleKit.docc/Tailscale.md @@ -0,0 +1,3 @@ +# ``TailscaleKit`` + +Swift 6 wrapper for libtailscale diff --git a/swift/TailscaleKit/TailscaleKit.h b/swift/TailscaleKit/TailscaleKit.h new file mode 100644 index 0000000..c157d66 --- /dev/null +++ b/swift/TailscaleKit/TailscaleKit.h @@ -0,0 +1,201 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +#import + +//! 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 + +#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 , 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 diff --git a/swift/TailscaleKit/TailscaleNode.swift b/swift/TailscaleKit/TailscaleNode.swift new file mode 100644 index 0000000..7523ba5 --- /dev/null +++ b/swift/TailscaleKit/TailscaleNode.swift @@ -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.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.allocate(capacity: 64) + let proxyCredBuf = UnsafeMutablePointer.allocate(capacity: 33) + let apiCredBuf = UnsafeMutablePointer.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 + } +} + + diff --git a/swift/TailscaleKit/URLSession+Tailscale.swift b/swift/TailscaleKit/URLSession+Tailscale.swift new file mode 100644 index 0000000..825b61f --- /dev/null +++ b/swift/TailscaleKit/URLSession+Tailscale.swift @@ -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 : + 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 + } + +} diff --git a/swift/TailscaleKitTestHost/ContentView.swift b/swift/TailscaleKitTestHost/ContentView.swift new file mode 100644 index 0000000..6788c27 --- /dev/null +++ b/swift/TailscaleKitTestHost/ContentView.swift @@ -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() + } +} + diff --git a/swift/TailscaleKitTestHost/Info.plist b/swift/TailscaleKitTestHost/Info.plist new file mode 100644 index 0000000..6a6654d --- /dev/null +++ b/swift/TailscaleKitTestHost/Info.plist @@ -0,0 +1,11 @@ + + + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + diff --git a/swift/TailscaleKitTestHost/TailscaleKitTestHost.entitlements b/swift/TailscaleKitTestHost/TailscaleKitTestHost.entitlements new file mode 100644 index 0000000..40b639e --- /dev/null +++ b/swift/TailscaleKitTestHost/TailscaleKitTestHost.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/swift/TailscaleKitTestHost/TailscaleKitTestHostApp.swift b/swift/TailscaleKitTestHost/TailscaleKitTestHostApp.swift new file mode 100644 index 0000000..93f581b --- /dev/null +++ b/swift/TailscaleKitTestHost/TailscaleKitTestHostApp.swift @@ -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() + } + } +} diff --git a/swift/TailscaleKitXCTests/TailscaleKitTests.swift b/swift/TailscaleKitXCTests/TailscaleKitTests.swift new file mode 100644 index 0000000..bb3b7e1 --- /dev/null +++ b/swift/TailscaleKitXCTests/TailscaleKitTests.swift @@ -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 +} diff --git a/swift/TailscaleKitXCTests/Test-Bridging-Header.h b/swift/TailscaleKitXCTests/Test-Bridging-Header.h new file mode 100644 index 0000000..4fe4d9e --- /dev/null +++ b/swift/TailscaleKitXCTests/Test-Bridging-Header.h @@ -0,0 +1,15 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +#include + +// 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(); diff --git a/tailscale.c b/tailscale.c index 7a39f50..0502fe7 100644 --- a/tailscale.c +++ b/tailscale.c @@ -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); } diff --git a/tailscale.go b/tailscale.go index 7087ed0..68dca70 100644 --- a/tailscale.go +++ b/tailscale.go @@ -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 } diff --git a/tailscale.h b/tailscale.h index 8265698..df08d7e 100644 --- a/tailscale.h +++ b/tailscale.h @@ -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 , 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). diff --git a/tailscale_test.go b/tailscale_test.go index efa35d7..e79fed9 100644 --- a/tailscale_test.go +++ b/tailscale_test.go @@ -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) + } +} diff --git a/tstestcontrol/Makefile b/tstestcontrol/Makefile new file mode 100644 index 0000000..d0acb98 --- /dev/null +++ b/tstestcontrol/Makefile @@ -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 + diff --git a/tstestcontrol/tstestcontrol.c b/tstestcontrol/tstestcontrol.c new file mode 100644 index 0000000..8d4451e --- /dev/null +++ b/tstestcontrol/tstestcontrol.c @@ -0,0 +1,23 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +#include "tstestcontrol.h" +#include + +// 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(); +} \ No newline at end of file diff --git a/tstestcontrol/tstestcontrol.go b/tstestcontrol/tstestcontrol.go new file mode 100644 index 0000000..4da2d9a --- /dev/null +++ b/tstestcontrol/tstestcontrol.go @@ -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 + } + } +} diff --git a/tstestcontrol/tstestcontrol.h b/tstestcontrol/tstestcontrol.h new file mode 100644 index 0000000..3be87a0 --- /dev/null +++ b/tstestcontrol/tstestcontrol.h @@ -0,0 +1,26 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +#include + +#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 \ No newline at end of file