├── version.txt ├── .dockerignore ├── mise.toml ├── .gitignore ├── renovate.json ├── Dockerfile ├── make_release ├── cmd └── ts-proxyd │ └── main.go ├── tcp.go ├── http.go ├── go.mod ├── README.md ├── .github └── workflows │ ├── scorecard.yml │ └── autorelease.yml ├── proxy.go └── go.sum /version.txt: -------------------------------------------------------------------------------- 1 | 0.10.11 -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | *.nix 3 | -------------------------------------------------------------------------------- /mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | go = "1.25.5" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # binary created with go build 2 | main 3 | 4 | # nix result link 5 | result* 6 | ts-proxyd 7 | build 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>lewtec/renovate-config:base", 5 | "config:recommended" 6 | ], 7 | "lockFileMaintenance": { 8 | "enabled": true, 9 | "recreateWhen": "always", 10 | "automergeType": "pr", 11 | "automerge": true 12 | }, 13 | "packageRules": [ 14 | { 15 | "matchPackageNames": "tailscale.com", 16 | "automerge": true 17 | }, 18 | { 19 | "matchUpdateTypes": [ 20 | "patch" 21 | ], 22 | "automerge": true 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.25-alpine@sha256:72567335df90b4ed71c01bf91fb5f8cc09fc4d5f6f21e183a085bafc7ae1bec8 AS build-env 2 | 3 | WORKDIR /go/src/ts-proxy 4 | 5 | COPY go.mod go.sum ./ 6 | 7 | RUN go mod download 8 | 9 | COPY . ./ 10 | 11 | ARG VERSION_LONG 12 | ENV VERSION_LONG=$VERSION_LONG 13 | 14 | ARG VERSION_GIT 15 | ENV VERSION_GIT=$VERSION_GIT 16 | 17 | RUN go build -v -o ts-proxyd ./cmd/ts-proxyd 18 | 19 | FROM alpine:3.23@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62 20 | 21 | RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables 22 | 23 | COPY --from=build-env /go/src/ts-proxy/ts-proxyd /usr/local/bin 24 | 25 | ENTRYPOINT [ "/usr/local/bin/ts-proxyd" ] 26 | 27 | -------------------------------------------------------------------------------- /make_release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# == 1 ]; then 4 | VERSION="$1"; shift 5 | else 6 | echo ./make_release versao_nova >&2 7 | exit 1 8 | fi 9 | 10 | CURRENT_VERSION=($(cat version.txt | sed 's;\.; ;g')) 11 | 12 | case "$VERSION" in 13 | patch) 14 | VERSION="${CURRENT_VERSION[0]}.${CURRENT_VERSION[1]}.$((${CURRENT_VERSION[2]}+1))" 15 | ;; 16 | minor) 17 | VERSION="${CURRENT_VERSION[0]}.$((${CURRENT_VERSION[1]}+1)).0" 18 | ;; 19 | major) 20 | VERSION="$((${CURRENT_VERSION[0]}+1)).0.0" 21 | ;; 22 | esac 23 | 24 | # echo new version: $VERSION 25 | # exit 0 26 | printf "%s" "$VERSION" > version.txt 27 | 28 | git add -A 29 | git commit -sm "bump to $VERSION" 30 | if [[ ! -v NO_TAG ]]; then 31 | git tag "$VERSION" 32 | git push --tag 33 | fi 34 | git push 35 | -------------------------------------------------------------------------------- /cmd/ts-proxyd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | 7 | "github.com/davecgh/go-spew/spew" 8 | tsproxy "github.com/lucasew/ts-proxy" 9 | ) 10 | 11 | var options tsproxy.TailscaleProxyServerOptions 12 | 13 | func init() { 14 | var err error 15 | flag.StringVar(&options.Network, "net", "tcp", "Network, for net.Dial") 16 | flag.StringVar(&options.Address, "address", "", "Where to forward the connection") 17 | flag.StringVar(&options.Hostname, "n", "", "Hostname in tailscale devices list") 18 | flag.BoolVar(&options.EnableFunnel, "f", false, "Enable tailscale funnel") 19 | flag.BoolVar(&options.EnableTLS, "t", false, "Enable HTTPS/TLS") 20 | flag.StringVar(&options.StateDir, "s", "", "State directory") 21 | flag.StringVar(&options.Listen, "listen", "", "Port to listen") 22 | flag.BoolVar(&options.EnableHTTP, "raw", false, "Disable HTTP handling") 23 | flag.Parse() 24 | options.EnableHTTP = !options.EnableHTTP 25 | if options.Listen == "" && options.EnableHTTP { 26 | if options.EnableFunnel || options.EnableTLS { 27 | options.Listen = ":443" 28 | } else { 29 | options.Listen = ":80" 30 | } 31 | } 32 | spew.Dump(options) 33 | if options.Listen == "" { 34 | panic("-listen not defined") 35 | } 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | } 40 | 41 | func main() { 42 | server, err := tsproxy.NewTailscaleProxyServer(options) 43 | if err != nil { 44 | panic(err) 45 | } 46 | server.Run() 47 | } 48 | -------------------------------------------------------------------------------- /tcp.go: -------------------------------------------------------------------------------- 1 | package tsproxy 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "sync" 8 | ) 9 | 10 | type TailscaleTCPProxyServer struct { 11 | server *TailscaleProxyServer 12 | } 13 | 14 | func NewTailscaleTCPProxyServer(server *TailscaleProxyServer) Server { 15 | return &TailscaleTCPProxyServer{ 16 | server: server, 17 | } 18 | } 19 | 20 | func (tps *TailscaleTCPProxyServer) Serve(ln net.Listener) error { 21 | for { 22 | select { 23 | case <-tps.server.ctx.Done(): 24 | break 25 | default: 26 | conn, err := ln.Accept() 27 | if err != nil { 28 | log.Printf("error/accept: %s", err.Error()) 29 | continue 30 | } 31 | log.Printf("got tcp conn") 32 | go handleTCPConn(tps.server, conn, nil) 33 | } 34 | } 35 | } 36 | 37 | var bufferPool = sync.Pool{ 38 | New: func() interface{} { 39 | // TODO maybe different buffer size? 40 | // benchmark pls 41 | return make([]byte, 1<<15) 42 | }, 43 | } 44 | 45 | func handleTCPConn(server *TailscaleProxyServer, c1 net.Conn, c2 net.Conn) { 46 | var err error 47 | if c2 == nil { 48 | c2, err = server.Dial("whatever", "whatever") 49 | if err != nil { 50 | log.Print(err) 51 | c1.Close() 52 | log.Printf("disconnected %v", c1.RemoteAddr()) 53 | return 54 | } 55 | 56 | } 57 | first := make(chan<- struct{}, 1) 58 | cp := func(dst net.Conn, src net.Conn) { 59 | buf := bufferPool.Get().([]byte) 60 | defer bufferPool.Put(buf) 61 | // TODO use splice on linux 62 | // TODO needs some timeout to prevent torshammer ddos 63 | _, err := io.CopyBuffer(dst, src, buf) 64 | select { 65 | case first <- struct{}{}: 66 | if err != nil { 67 | log.Print(err) 68 | } 69 | dst.Close() 70 | src.Close() 71 | log.Printf("disconnected %v", c1.RemoteAddr()) 72 | default: 73 | } 74 | } 75 | go cp(c1, c2) 76 | cp(c2, c1) 77 | } 78 | -------------------------------------------------------------------------------- /http.go: -------------------------------------------------------------------------------- 1 | package tsproxy 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "net/http" 7 | "net/http/httputil" 8 | "net/url" 9 | 10 | "github.com/davecgh/go-spew/spew" 11 | ) 12 | 13 | func init() { 14 | _ = spew.Dump 15 | } 16 | 17 | type TailscaleHTTPProxyServer struct { 18 | server *TailscaleProxyServer 19 | proxy *httputil.ReverseProxy 20 | } 21 | 22 | func NewTailscaleHTTPProxyServer(server *TailscaleProxyServer) (Server, error) { 23 | u := &url.URL{ 24 | Scheme: "http", 25 | Host: server.Hostname(), 26 | } 27 | proxy := httputil.NewSingleHostReverseProxy(u) 28 | proxy.Transport = &http.Transport{ 29 | Dial: server.Dial, 30 | } 31 | return &TailscaleHTTPProxyServer{ 32 | server: server, 33 | proxy: proxy, 34 | }, nil 35 | } 36 | 37 | func (tps *TailscaleHTTPProxyServer) Serve(l net.Listener) error { 38 | server := http.Server{Handler: tps} 39 | return server.Serve(l) 40 | } 41 | 42 | func (tps *TailscaleHTTPProxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 43 | userInfo, err := tps.server.WhoIs(r.Context(), r.RemoteAddr) 44 | if err != nil { 45 | log.Printf("error/http/ts-auth: %s", err.Error()) 46 | w.WriteHeader(500) 47 | return 48 | } 49 | if r.URL.Hostname() != "" && r.URL.Hostname() != tps.server.Hostname() { 50 | destinationURL := new(url.URL) 51 | *destinationURL = *r.URL 52 | destinationURL.Host = tps.server.Hostname() + tps.server.options.Listen 53 | if tps.server.options.EnableTLS { 54 | destinationURL.Scheme = "https" 55 | } else { 56 | destinationURL.Scheme = "http" 57 | } 58 | log.Printf("redirect: '%s' -> '%s'", r.URL.String(), destinationURL.String()) 59 | http.Redirect(w, r, destinationURL.String(), http.StatusMovedPermanently) 60 | return 61 | } 62 | if tps.server.options.EnableTLS { 63 | r.Header.Set("X-Forwarded-Proto", "https") 64 | } else { 65 | r.Header.Set("X-Forwarded-Proto", "http") 66 | } 67 | log.Printf("%s %s %s %s", r.Method, userInfo.UserProfile.LoginName, r.Host, r.URL.String()) 68 | r.Header.Set("Tailscale-User-Login", userInfo.UserProfile.LoginName) 69 | r.Header.Set("Tailscale-User-Name", userInfo.UserProfile.DisplayName) 70 | r.Header.Set("Tailscale-User-Profile-Pic", userInfo.UserProfile.ProfilePicURL) 71 | r.Header.Set("Tailscale-Headers-Info", "https://tailscale.com/s/serve-headers") 72 | tps.proxy.ServeHTTP(w, r) 73 | } 74 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lucasew/ts-proxy 2 | 3 | go 1.25.5 4 | 5 | require tailscale.com v1.92.4 6 | 7 | require ( 8 | github.com/akutz/memconn v0.1.0 // indirect 9 | github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect 10 | github.com/coder/websocket v1.8.12 // indirect 11 | github.com/creachadair/msync v0.7.1 // indirect 12 | github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect 13 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 14 | github.com/gaissmai/bart v0.18.0 // indirect 15 | github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect 16 | github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect 17 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 18 | github.com/google/go-cmp v0.7.0 // indirect 19 | github.com/google/uuid v1.6.0 // indirect 20 | github.com/hdevalence/ed25519consensus v0.2.0 // indirect 21 | github.com/jsimonetti/rtnetlink v1.4.0 // indirect 22 | github.com/klauspost/compress v1.18.0 // indirect 23 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect 24 | github.com/mitchellh/go-ps v1.0.0 // indirect 25 | github.com/pires/go-proxyproto v0.8.1 // indirect 26 | github.com/prometheus-community/pro-bing v0.4.0 // indirect 27 | github.com/safchain/ethtool v0.3.0 // indirect 28 | github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect 29 | github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect 30 | github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect 31 | github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect 32 | github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect 33 | github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect 34 | go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect 35 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect 36 | golang.org/x/crypto v0.45.0 // indirect 37 | golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect 38 | golang.org/x/net v0.47.0 // indirect 39 | golang.org/x/oauth2 v0.30.0 // indirect 40 | golang.org/x/sync v0.18.0 // indirect 41 | golang.org/x/sys v0.38.0 // indirect 42 | golang.org/x/term v0.37.0 // indirect 43 | golang.org/x/time v0.11.0 // indirect 44 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect 45 | golang.zx2c4.com/wireguard/windows v0.5.3 // indirect 46 | gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 // indirect 47 | ) 48 | 49 | require ( 50 | filippo.io/edwards25519 v1.1.0 // indirect 51 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc 52 | github.com/google/btree v1.1.2 // indirect 53 | github.com/mdlayher/socket v0.5.0 // indirect 54 | github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect 55 | github.com/x448/float16 v0.8.4 // indirect 56 | golang.org/x/text v0.31.0 // indirect 57 | ) 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ts-proxy 2 | 3 | Simple proxy program to allow exposing individual services to a Tailnet, and 4 | even to the Internet using Tailscale Funnel. 5 | 6 | Unfortunately, the Tailscale daemon only allows exposing services using the 7 | current node domain, and you can't spawn (so far) nodes for services. With this 8 | you can! 9 | 10 | On first run for one service, you will have to authenticate the service using 11 | your Tailscale account. The authentication can be either done passing an 12 | authentication token through the `TS_AUTHKEY` environment or by reading the 13 | startup logs until you find the authentication link. After authentication, 14 | Tailscale will store the certificates and credentials to the location specified 15 | by the `-s` flag so subsequent runs will not require reauthentication and up-to-date authorization tokens. 16 | 17 | As the [main build of Tailscale](https://tailscale.com/s/serve-headers), you 18 | can get information about the user accessing the service using the following 19 | headers that get forwarded to the upstream service: 20 | - `Tailscale-User-Login` 21 | - `Tailscale-User-Name` 22 | - `Tailscale-User-Profile-Pic` 23 | 24 | And yeah, you can use Tailscale as a single sign on and have a public facing 25 | version! It's as safe and stable as 26 | [tclip](https://github.com/tailscale-dev/tclip) is because this proxy uses the 27 | exact same primitives. 28 | 29 | > [!WARNING] 30 | > You can count on the headers sent by ts-proxy as long as you follow the following conditions: 31 | > - Anything that changes the headers name representation such as Apache with PHP could be cheated 32 | > by passing the header TAILSCALE_USER_LOGIN, for example. 33 | > 34 | > - If some users can access your actual service directly without passing the traffic through ts-proxy 35 | they can change all the headers they want, including authentication ones. 36 | > 37 | > - If you don't use the header authentication for anything in a given service these issues will not be a problem for that service. 38 | 39 | 40 | ## Usage 41 | 42 | ``` 43 | Usage of ./ts-proxyd: 44 | -addr string 45 | Port to listen (default ":443") 46 | -f Enable tailscale funnel 47 | -h string 48 | Where to forward the connection 49 | -n string 50 | Hostname in tailscale devices list 51 | -s string 52 | State directory 53 | ``` 54 | 55 | ## Release schedule 56 | Version structure example: 0.7.10 57 | - 0: major 58 | - 7: minor 59 | - 10: patch 60 | 61 | Each week an automatic PR is sent to update the package dependencies. For each update there will be a patch release. 62 | 63 | Bug fixes would be shipped in patch releases. 64 | 65 | Anything that has breaking changes by changing something in this repository will be released as a minor release. 66 | 67 | No plans for bumping the major versions yet. 68 | 69 | ## Next steps 70 | - [ ] A way to expose a folder, maybe using single page application patterns, instead of only ports. 71 | - [ ] Experiments around exposing many nodes using only one process and a TOML, or YAML, config file. 72 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '25 11 * * 1' 14 | push: 15 | branches: [ "main" ] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. 25 | if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' 26 | permissions: 27 | # Needed to upload the results to code-scanning dashboard. 28 | security-events: write 29 | # Needed to publish results and get a badge (see publish_results below). 30 | id-token: write 31 | # Uncomment the permissions below if installing in a private repository. 32 | # contents: read 33 | # actions: read 34 | 35 | steps: 36 | - name: "Checkout code" 37 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 38 | with: 39 | persist-credentials: false 40 | 41 | - name: "Run analysis" 42 | uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 43 | with: 44 | results_file: results.sarif 45 | results_format: sarif 46 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 47 | # - you want to enable the Branch-Protection check on a *public* repository, or 48 | # - you are installing Scorecard on a *private* repository 49 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. 50 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 51 | 52 | # Public repositories: 53 | # - Publish results to OpenSSF REST API for easy access by consumers 54 | # - Allows the repository to include the Scorecard badge. 55 | # - See https://github.com/ossf/scorecard-action#publishing-results. 56 | # For private repositories: 57 | # - `publish_results` will always be set to `false`, regardless 58 | # of the value entered here. 59 | publish_results: true 60 | 61 | # (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore 62 | # file_mode: git 63 | 64 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 65 | # format to the repository Actions tab. 66 | - name: "Upload artifact" 67 | uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 68 | with: 69 | name: SARIF file 70 | path: results.sarif 71 | retention-days: 5 72 | 73 | # Upload the results to GitHub's code scanning dashboard (optional). 74 | # Commenting out will disable upload of results to your repo's Code Scanning dashboard 75 | - name: "Upload to code-scanning" 76 | uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4 77 | with: 78 | sarif_file: results.sarif 79 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package tsproxy 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | "net" 8 | 9 | "os" 10 | 11 | "tailscale.com/client/tailscale/apitype" 12 | "tailscale.com/tsnet" 13 | ) 14 | 15 | var ( 16 | ErrInvalidUpstream = errors.New("invalid upstream") 17 | ) 18 | 19 | type Server interface { 20 | Serve(ln net.Listener) error 21 | } 22 | 23 | type ListenerFunction func(network string, addr string) (net.Listener, error) 24 | 25 | type TailscaleProxyServer struct { 26 | ctx context.Context 27 | cancel func() 28 | options TailscaleProxyServerOptions 29 | server *tsnet.Server 30 | } 31 | 32 | type TailscaleProxyServerOptions struct { 33 | // context 34 | Context context.Context 35 | // node name in tailscale panel 36 | Hostname string 37 | // wether to enable Tailscale Funnel, will crash if no permissions 38 | EnableFunnel bool 39 | // wether to enable provisioning of TLS Certificates for HTTPS 40 | EnableTLS bool 41 | // wether to enable HTTP proxy logic 42 | EnableHTTP bool 43 | // where to store tailscale data 44 | StateDir string 45 | // protocol to listen, passed to net.Dial 46 | Network string 47 | // where to forward requests 48 | Address string 49 | // address to bind the server, passed to net.Dial 50 | Listen string 51 | } 52 | 53 | func NewTailscaleProxyServer(options TailscaleProxyServerOptions) (*TailscaleProxyServer, error) { 54 | if options.Context == nil { 55 | options.Context = context.Background() 56 | } 57 | ctx, cancel := context.WithCancel(options.Context) 58 | s := new(tsnet.Server) 59 | if options.Hostname == "" { 60 | options.Hostname = "tsproxy" 61 | } 62 | s.Hostname = options.Hostname 63 | if options.Address == "" { 64 | return nil, ErrInvalidUpstream 65 | } 66 | if options.StateDir != "" { 67 | err := os.MkdirAll(options.StateDir, 0700) 68 | if err != nil { 69 | return nil, err 70 | } 71 | s.Dir = options.StateDir 72 | } 73 | return &TailscaleProxyServer{ 74 | ctx: ctx, 75 | cancel: cancel, 76 | options: options, 77 | server: s, 78 | }, nil 79 | } 80 | 81 | func (tps *TailscaleProxyServer) listenFunnel(network string, addr string) (net.Listener, error) { 82 | return tps.server.ListenFunnel(network, addr) 83 | } 84 | 85 | func (tps *TailscaleProxyServer) Hostname() string { 86 | for _, domain := range tps.server.CertDomains() { 87 | return domain 88 | } 89 | return tps.options.Hostname 90 | } 91 | 92 | func (tps *TailscaleProxyServer) GetListenerFunction() ListenerFunction { 93 | if tps.options.EnableFunnel { 94 | return tps.listenFunnel 95 | } 96 | if tps.options.EnableTLS { 97 | return tps.server.ListenTLS 98 | } 99 | return tps.server.Listen 100 | } 101 | 102 | func (tps *TailscaleProxyServer) GetListener() (net.Listener, error) { 103 | return tps.GetListenerFunction()("tcp", tps.options.Listen) 104 | } 105 | 106 | func (tps *TailscaleProxyServer) Dial(network string, addr string) (net.Conn, error) { 107 | dialNetwork := tps.options.Network 108 | dialHost := tps.options.Address 109 | return net.Dial(dialNetwork, dialHost) 110 | } 111 | 112 | func (tps *TailscaleProxyServer) WhoIs(ctx context.Context, remoteAddr string) (*apitype.WhoIsResponse, error) { 113 | lc, err := tps.server.LocalClient() 114 | if err != nil { 115 | return nil, err 116 | } 117 | return lc.WhoIs(ctx, remoteAddr) 118 | } 119 | 120 | func (tps *TailscaleProxyServer) handleError(err error) bool { 121 | if err != nil { 122 | log.Printf("FATAL ERROR: %s\n", err.Error()) 123 | tps.cancel() 124 | } 125 | return err != nil 126 | } 127 | 128 | func (tps *TailscaleProxyServer) Run() { 129 | ln, err := tps.GetListener() 130 | if tps.handleError(err) { 131 | return 132 | } 133 | defer ln.Close() 134 | server := NewTailscaleTCPProxyServer(tps) 135 | if tps.options.EnableHTTP { 136 | server, err = NewTailscaleHTTPProxyServer(tps) 137 | if tps.handleError(err) { 138 | return 139 | } 140 | } 141 | server.Serve(ln) 142 | } 143 | -------------------------------------------------------------------------------- /.github/workflows/autorelease.yml: -------------------------------------------------------------------------------- 1 | name: Autorelease 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '*' 9 | pull_request: 10 | branches: 11 | - main 12 | workflow_dispatch: 13 | inputs: 14 | new_version: 15 | description: 'New tag version' 16 | type: choice 17 | default: 'patch' 18 | options: 19 | - patch 20 | - minor 21 | - major 22 | schedule: 23 | - cron: '0 2 * * 6' # saturday 2am 24 | jobs: 25 | autorelease: 26 | env: 27 | REGISTRY: ghcr.io 28 | IMAGE_NAME: ${{ github.repository }} 29 | USERNAME: ${{ github.actor }} 30 | runs-on: ubuntu-latest 31 | permissions: 32 | contents: write 33 | packages: write 34 | attestations: write 35 | id-token: write 36 | pull-requests: write 37 | steps: 38 | 39 | - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 40 | - name: Setup git config 41 | run: | 42 | git config user.name actions-bot 43 | git config user.email actions-bot@users.noreply.github.com 44 | 45 | - uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3 46 | 47 | - name: Go get 48 | run: go get ./... 49 | 50 | - name: Go fmt 51 | run: go fmt ./... 52 | 53 | - name: Create Pull Request if there is new stuff from updaters 54 | if: github.event_name != 'pull_request' 55 | uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8 56 | id: pr_create 57 | with: 58 | commit-message: Updater script changes 59 | branch: updater-bot 60 | delete-branch: true 61 | title: "Updater: stuff changed" 62 | body: | 63 | Changes caused from update scripts 64 | 65 | - name: Stop if a pull request was created 66 | if: github.event_name != 'pull_request' 67 | env: 68 | PR_NUMBER: ${{ steps.pr_create.outputs.pull-request-number }} 69 | run: | 70 | if [[ ! -z "$PR_NUMBER" ]]; then 71 | echo "The update scripts changed something and a PR was created. Giving up deploy." >> $GITHUB_STEP_SUMMARY 72 | exit 1 73 | fi 74 | 75 | - name: Build binaries 76 | env: 77 | TAG: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 78 | run: | 79 | echo "::group::Build container" 80 | docker build -t "$TAG:latest" . 81 | echo "::endgroup::" 82 | 83 | mkdir build -p && ls && pwd 84 | 85 | echo "# Built targets" >> $GITHUB_STEP_SUMMARY 86 | export CGO_ENABLED=0 87 | go tool dist list | grep -vE 'wasm|aix|plan9|android|ios|illumos|solaris|dragonfly' | while IFS=/ read -r GOOS GOARCH; do 88 | echo "::group::Build $GOOS/$GOARCH" 89 | GOOS=$GOOS GOARCH=$GOARCH go build -v -o build/ts-proxyd-$GOOS-$GOARCH ./cmd/ts-proxyd && (echo "- $GOOS/$GOARCH" >> $GITHUB_STEP_SUMMARY) || true 90 | echo "::endgroup::" 91 | done 92 | 93 | - name: Make release if everything looks right 94 | if: github.event_name != 'pull_request' 95 | env: 96 | NEW_VERSION: ${{ github.event.inputs.new_version }} 97 | run: | 98 | if [[ ! -z "$NEW_VERSION" ]]; then 99 | NO_TAG=1 ./make_release "$NEW_VERSION" 100 | echo "New version: $(cat version.txt)" >> $GITHUB_STEP_SUMMARY 101 | echo "RELEASE_VERSION=$(cat version.txt)" >> $GITHUB_ENV 102 | fi 103 | 104 | - name: Create relase 105 | if: env.RELEASE_VERSION != '' && github.event_name != 'pull_request' 106 | env: 107 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 108 | TAG: ${{ env.RELEASE_VERSION }} 109 | TITLE: Release ${{ env.RELEASE_VERSION }} 110 | run: | 111 | gh release create "$TAG" \ 112 | --title "$TITLE" \ 113 | --generate-notes \ 114 | --notes-start-tag $(gh release list --limit 1 --json tagName -q .[].tagName) 115 | 116 | - name: Upload release artifacts 117 | if: env.RELEASE_VERSION != '' && github.event_name != 'pull_request' 118 | env: 119 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 120 | TAG: ${{ env.RELEASE_VERSION }} 121 | run: | 122 | gh release upload "$TAG" build/* --clobber 123 | 124 | - name: Login to registry 125 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 126 | if: env.RELEASE_VERSION != '' && github.event_name != 'pull_request' 127 | with: 128 | registry: ${{ env.REGISTRY }} 129 | username: ${{ env.USERNAME }} 130 | password: ${{ github.token }} 131 | 132 | - name: "Build and publish container" 133 | env: 134 | TAG: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 135 | if: env.RELEASE_VERSION != '' && github.event_name != 'pull_request' 136 | run: | 137 | VERSION="$(cat version.txt)" 138 | docker tag "$TAG:latest" "$TAG:$VERSION" 139 | docker push "$TAG:$VERSION" 140 | docker push "$TAG:latest" 141 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | 9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f h1:1C7nZuxUMNz7eiQALRfiqNOm04+m3edWlRff/BYHf0Q= 2 | 9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM= 3 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 4 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 5 | filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc= 6 | filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA= 7 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= 8 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 9 | github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= 10 | github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= 11 | github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= 12 | github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= 13 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= 14 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 15 | github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk= 16 | github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= 17 | github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k= 18 | github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg= 19 | github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y= 20 | github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A= 21 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8= 22 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY= 23 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ= 24 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI= 25 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o= 26 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k= 27 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= 28 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= 29 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= 30 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= 31 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM= 32 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk= 33 | github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE= 34 | github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM= 35 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok= 36 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs= 37 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU= 38 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U= 39 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc= 40 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= 41 | github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= 42 | github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= 43 | github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= 44 | github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= 45 | github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= 46 | github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= 47 | github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= 48 | github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= 49 | github.com/creachadair/msync v0.7.1 h1:SeZmuEBXQPe5GqV/C94ER7QIZPwtvFbeQiykzt/7uho= 50 | github.com/creachadair/msync v0.7.1/go.mod h1:8CcFlLsSujfHE5wWm19uUBLHIPDAUr6LXDwneVMO008= 51 | github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc= 52 | github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g= 53 | github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= 54 | github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= 55 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 56 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 57 | github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk= 58 | github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ= 59 | github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= 60 | github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= 61 | github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= 62 | github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= 63 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 64 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 65 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 66 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 67 | github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= 68 | github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= 69 | github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= 70 | github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= 71 | github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I= 72 | github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= 73 | github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= 74 | github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 75 | github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo= 76 | github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g= 77 | github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= 78 | github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= 79 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 80 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 81 | github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 82 | github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 83 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 84 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 85 | github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I= 86 | github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= 87 | github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= 88 | github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= 89 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 90 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 91 | github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= 92 | github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= 93 | github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk= 94 | github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U= 95 | github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= 96 | github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= 97 | github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g= 98 | github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= 99 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 100 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 101 | github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= 102 | github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= 103 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 104 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 105 | github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= 106 | github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= 107 | github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= 108 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 109 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 110 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 111 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 112 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 113 | github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= 114 | github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= 115 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= 116 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= 117 | github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= 118 | github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= 119 | github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= 120 | github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= 121 | github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= 122 | github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= 123 | github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= 124 | github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= 125 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 126 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 127 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 128 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 129 | github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= 130 | github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 131 | github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= 132 | github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= 133 | github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= 134 | github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= 135 | github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4= 136 | github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4= 137 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 138 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 139 | github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= 140 | github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= 141 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 142 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 143 | github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= 144 | github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= 145 | github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= 146 | github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4= 147 | github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= 148 | github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= 149 | github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 h1:SRL6irQkKGQKKLzvQP/ke/2ZuB7Py5+XuqtOgSj+iMM= 150 | github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= 151 | github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= 152 | github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= 153 | github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= 154 | github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= 155 | github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= 156 | github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= 157 | github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= 158 | github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= 159 | github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= 160 | github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= 161 | github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M= 162 | github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y= 163 | github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw= 164 | github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= 165 | github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= 166 | github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= 167 | github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= 168 | github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= 169 | github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg= 170 | github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE= 171 | github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= 172 | github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= 173 | github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= 174 | github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 175 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 176 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 177 | go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= 178 | go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= 179 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= 180 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= 181 | golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= 182 | golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= 183 | golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= 184 | golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= 185 | golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= 186 | golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 187 | golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= 188 | golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= 189 | golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= 190 | golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= 191 | golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= 192 | golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= 193 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= 194 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 195 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 196 | golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= 197 | golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 198 | golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 199 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 200 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 201 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 202 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 203 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 204 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= 205 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 206 | golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 207 | golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 208 | golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= 209 | golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= 210 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= 211 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= 212 | golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= 213 | golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= 214 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 215 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 216 | gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= 217 | gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= 218 | honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 h1:5SXjd4ET5dYijLaf0O3aOenC0Z4ZafIWSpjUzsQaNho= 219 | honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ= 220 | howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= 221 | howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= 222 | software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= 223 | software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= 224 | tailscale.com v1.92.1 h1:nlC+4o3DuBhhgIDDcU5ht/12FH8R9ZtER6KKCksCWCA= 225 | tailscale.com v1.92.1/go.mod h1:jzTfKDd6XNNSNoX+Q9INIMpMU5rfZ9g8ojcAoMKi5w0= 226 | tailscale.com v1.92.2 h1:lnCcqX+T2MMszLG0zijT6VfuZv9ilk78mo3aTd69u04= 227 | tailscale.com v1.92.2/go.mod h1:jzTfKDd6XNNSNoX+Q9INIMpMU5rfZ9g8ojcAoMKi5w0= 228 | tailscale.com v1.92.3 h1:xMEaw3TunKvI7GyC1pXyPZqDAnkpYdOKK3yYO0npb8k= 229 | tailscale.com v1.92.3/go.mod h1:jzTfKDd6XNNSNoX+Q9INIMpMU5rfZ9g8ojcAoMKi5w0= 230 | tailscale.com v1.92.4 h1:agmk0My9BJZRVplQj6Sf+3k/BpsoXuf1IA0qeWdBKE0= 231 | tailscale.com v1.92.4/go.mod h1:jzTfKDd6XNNSNoX+Q9INIMpMU5rfZ9g8ojcAoMKi5w0= 232 | --------------------------------------------------------------------------------