Compare commits
1 Commits
main
...
marwan/pol
Author | SHA1 | Date |
---|---|---|
![]() |
aa528bb7bf |
|
@ -1,15 +0,0 @@
|
||||||
name: "Dockerfile build"
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- "*"
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: "Build Docker image"
|
|
||||||
run: docker build .
|
|
|
@ -50,7 +50,7 @@ jobs:
|
||||||
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
|
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Send pull request
|
- name: Send pull request
|
||||||
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 #v5.0.1
|
uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 #v5.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
author: License Updater <noreply+license-updater@tailscale.com>
|
author: License Updater <noreply+license-updater@tailscale.com>
|
|
@ -32,7 +32,7 @@ jobs:
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
# Note: this is the 'v3' tag as of 2023-04-17
|
# Note: this is the 'v3' tag as of 2023-04-17
|
||||||
uses: golangci/golangci-lint-action@639cd343e1d3b897ff35927a75193d57cfcba299
|
uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5
|
||||||
with:
|
with:
|
||||||
version: v1.52.2
|
version: v1.52.2
|
||||||
|
|
|
@ -90,11 +90,11 @@ jobs:
|
||||||
- name: build test wrapper
|
- name: build test wrapper
|
||||||
run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper
|
run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper
|
||||||
- name: test all
|
- name: test all
|
||||||
run: PATH=$PWD/tool:$PATH /tmp/testwrapper ./... ${{matrix.buildflags}}
|
run: ./tool/go test ${{matrix.buildflags}} -exec=/tmp/testwrapper
|
||||||
env:
|
env:
|
||||||
GOARCH: ${{ matrix.goarch }}
|
GOARCH: ${{ matrix.goarch }}
|
||||||
- name: bench all
|
- name: bench all
|
||||||
run: PATH=$PWD/tool:$PATH /tmp/testwrapper ./... ${{matrix.buildflags}} -bench=. -benchtime=1x -run=^$
|
run: ./tool/go test ${{matrix.buildflags}} -exec=/tmp/testwrapper -test.bench=. -test.benchtime=1x -test.run=^$
|
||||||
env:
|
env:
|
||||||
GOARCH: ${{ matrix.goarch }}
|
GOARCH: ${{ matrix.goarch }}
|
||||||
- name: check that no tracked files changed
|
- name: check that no tracked files changed
|
|
@ -35,7 +35,7 @@ jobs:
|
||||||
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
|
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Send pull request
|
- name: Send pull request
|
||||||
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 #v5.0.1
|
uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 #v5.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
author: Flakes Updater <noreply+flakes-updater@tailscale.com>
|
author: Flakes Updater <noreply+flakes-updater@tailscale.com>
|
|
@ -47,7 +47,8 @@ RUN go install \
|
||||||
golang.org/x/crypto/ssh \
|
golang.org/x/crypto/ssh \
|
||||||
golang.org/x/crypto/acme \
|
golang.org/x/crypto/acme \
|
||||||
nhooyr.io/websocket \
|
nhooyr.io/websocket \
|
||||||
github.com/mdlayher/netlink
|
github.com/mdlayher/netlink \
|
||||||
|
golang.zx2c4.com/wireguard/device
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
@ -72,4 +73,4 @@ RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables
|
||||||
COPY --from=build-env /go/bin/* /usr/local/bin/
|
COPY --from=build-env /go/bin/* /usr/local/bin/
|
||||||
# For compat with the previous run.sh, although ideally you should be
|
# For compat with the previous run.sh, although ideally you should be
|
||||||
# using build_docker.sh which sets an entrypoint for the image.
|
# using build_docker.sh which sets an entrypoint for the image.
|
||||||
RUN mkdir /tailscale && ln -s /usr/local/bin/containerboot /tailscale/run.sh
|
RUN ln -s /usr/local/bin/containerboot /tailscale/run.sh
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
# SPDX-License-Identifier: BSD-3-Clause
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
FROM alpine:3.16
|
FROM alpine:3.16
|
||||||
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables iputils
|
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables
|
||||||
|
|
5
Makefile
5
Makefile
|
@ -48,10 +48,11 @@ staticcheck: ## Run staticcheck.io checks
|
||||||
./tool/go run honnef.co/go/tools/cmd/staticcheck -- $$(./tool/go list ./... | grep -v tempfork)
|
./tool/go run honnef.co/go/tools/cmd/staticcheck -- $$(./tool/go list ./... | grep -v tempfork)
|
||||||
|
|
||||||
spk: ## Build synology package for ${SYNO_ARCH} architecture and ${SYNO_DSM} DSM version
|
spk: ## Build synology package for ${SYNO_ARCH} architecture and ${SYNO_DSM} DSM version
|
||||||
./tool/go run ./cmd/dist build synology/dsm${SYNO_DSM}/${SYNO_ARCH}
|
PATH="${PWD}/tool:${PATH}" ./tool/go run github.com/tailscale/tailscale-synology@main -o tailscale.spk --source=. --goarch=${SYNO_ARCH} --dsm-version=${SYNO_DSM}
|
||||||
|
|
||||||
spkall: ## Build synology packages for all architectures and DSM versions
|
spkall: ## Build synology packages for all architectures and DSM versions
|
||||||
./tool/go run ./cmd/dist build synology
|
mkdir -p spks
|
||||||
|
PATH="${PWD}/tool:${PATH}" ./tool/go run github.com/tailscale/tailscale-synology@main -o spks --source=. --goarch=all --dsm-version=all
|
||||||
|
|
||||||
pushspk: spk ## Push and install synology package on ${SYNO_HOST} host
|
pushspk: spk ## Push and install synology package on ${SYNO_HOST} host
|
||||||
echo "Pushing SPK to root@${SYNO_HOST} (env var SYNO_HOST) ..."
|
echo "Pushing SPK to root@${SYNO_HOST} (env var SYNO_HOST) ..."
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
1.45.0
|
1.43.0
|
||||||
|
|
21
api.md
21
api.md
|
@ -101,8 +101,8 @@ You can also [list all devices in the tailnet](#list-tailnet-devices) to get the
|
||||||
``` jsonc
|
``` jsonc
|
||||||
{
|
{
|
||||||
// addresses (array of strings) is a list of Tailscale IP
|
// addresses (array of strings) is a list of Tailscale IP
|
||||||
// addresses for the device, including both IPv4 (formatted as 100.x.y.z)
|
// addresses for the device, including both ipv4 (formatted as 100.x.y.z)
|
||||||
// and IPv6 (formatted as fd7a:115c:a1e0:a:b:c:d:e) addresses.
|
// and ipv6 (formatted as fd7a:115c:a1e0:a:b:c:d:e) addresses.
|
||||||
"addresses": [
|
"addresses": [
|
||||||
"100.87.74.78",
|
"100.87.74.78",
|
||||||
"fd7a:115c:a1e0:ac82:4843:ca90:697d:c36e"
|
"fd7a:115c:a1e0:ac82:4843:ca90:697d:c36e"
|
||||||
|
@ -1222,11 +1222,6 @@ The remaining three methods operate on auth keys and API access tokens.
|
||||||
|
|
||||||
// expirySeconds (int) is the duration in seconds a new key is valid.
|
// expirySeconds (int) is the duration in seconds a new key is valid.
|
||||||
"expirySeconds": 86400
|
"expirySeconds": 86400
|
||||||
|
|
||||||
// description (string) is an optional short phrase that describes what
|
|
||||||
// this key is used for. It can be a maximum of 50 alphanumeric characters.
|
|
||||||
// Hyphens and underscores are also allowed.
|
|
||||||
"description": "short description of key purpose"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1313,9 +1308,6 @@ Note the following about required vs. optional values:
|
||||||
Specifies the duration in seconds until the key should expire.
|
Specifies the duration in seconds until the key should expire.
|
||||||
Defaults to 90 days if not supplied.
|
Defaults to 90 days if not supplied.
|
||||||
|
|
||||||
- **`description`:** Optional in `POST` body.
|
|
||||||
A short string specifying the purpose of the key. Can be a maximum of 50 alphanumeric characters. Hyphens and spaces are also allowed.
|
|
||||||
|
|
||||||
### Request example
|
### Request example
|
||||||
|
|
||||||
``` jsonc
|
``` jsonc
|
||||||
|
@ -1333,8 +1325,7 @@ curl "https://api.tailscale.com/api/v2/tailnet/example.com/keys" \
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"expirySeconds": 86400,
|
"expirySeconds": 86400
|
||||||
"description": "dev access"
|
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1360,8 +1351,7 @@ It holds the capabilities specified in the request and can no longer be retrieve
|
||||||
"tags": [ "tag:example" ]
|
"tags": [ "tag:example" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"description": "dev access"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1413,8 +1403,7 @@ The response is a JSON object with information about the key supplied.
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"description": "dev access"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -49,4 +49,4 @@ while [ "$#" -gt 1 ]; do
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
exec $go build ${tags:+-tags=$tags} -ldflags "$ldflags" "$@"
|
exec ./tool/go build ${tags:+-tags=$tags} -ldflags "$ldflags" "$@"
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"tailscale.com/types/opt"
|
"tailscale.com/types/opt"
|
||||||
)
|
)
|
||||||
|
@ -212,20 +213,8 @@ func (c *Client) DeleteDevice(ctx context.Context, deviceID string) (err error)
|
||||||
|
|
||||||
// AuthorizeDevice marks a device as authorized.
|
// AuthorizeDevice marks a device as authorized.
|
||||||
func (c *Client) AuthorizeDevice(ctx context.Context, deviceID string) error {
|
func (c *Client) AuthorizeDevice(ctx context.Context, deviceID string) error {
|
||||||
return c.SetAuthorized(ctx, deviceID, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAuthorized marks a device as authorized or not.
|
|
||||||
func (c *Client) SetAuthorized(ctx context.Context, deviceID string, authorized bool) error {
|
|
||||||
params := &struct {
|
|
||||||
Authorized bool `json:"authorized"`
|
|
||||||
}{Authorized: authorized}
|
|
||||||
data, err := json.Marshal(params)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
path := fmt.Sprintf("%s/api/v2/device/%s/authorized", c.baseURL(), url.PathEscape(deviceID))
|
path := fmt.Sprintf("%s/api/v2/device/%s/authorized", c.baseURL(), url.PathEscape(deviceID))
|
||||||
req, err := http.NewRequestWithContext(ctx, "POST", path, bytes.NewBuffer(data))
|
req, err := http.NewRequestWithContext(ctx, "POST", path, strings.NewReader(`{"authorized":true}`))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -946,21 +946,6 @@ func (lc *LocalClient) NetworkLockForceLocalDisable(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkLockVerifySigningDeeplink verifies the network lock deeplink contained
|
|
||||||
// in url and returns information extracted from it.
|
|
||||||
func (lc *LocalClient) NetworkLockVerifySigningDeeplink(ctx context.Context, url string) (*tka.DeeplinkValidationResult, error) {
|
|
||||||
vr := struct {
|
|
||||||
URL string
|
|
||||||
}{url}
|
|
||||||
|
|
||||||
body, err := lc.send(ctx, "POST", "/localapi/v0/tka/verify-deeplink", 200, jsonBody(vr))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("sending verify-deeplink: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return decodeJSON[*tka.DeeplinkValidationResult](body)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetServeConfig sets or replaces the serving settings.
|
// SetServeConfig sets or replaces the serving settings.
|
||||||
// If config is nil, settings are cleared and serving is disabled.
|
// If config is nil, settings are cleared and serving is disabled.
|
||||||
func (lc *LocalClient) SetServeConfig(ctx context.Context, config *ipn.ServeConfig) error {
|
func (lc *LocalClient) SetServeConfig(ctx context.Context, config *ipn.ServeConfig) error {
|
||||||
|
|
|
@ -72,7 +72,7 @@ func NewManualCertManager(certdir, hostname string) (certProvider, error) {
|
||||||
return nil, fmt.Errorf("can not load cert: %w", err)
|
return nil, fmt.Errorf("can not load cert: %w", err)
|
||||||
}
|
}
|
||||||
if err := x509Cert.VerifyHostname(hostname); err != nil {
|
if err := x509Cert.VerifyHostname(hostname); err != nil {
|
||||||
// return nil, fmt.Errorf("cert invalid for hostname %q: %w", hostname, err)
|
return nil, fmt.Errorf("cert invalid for hostname %q: %w", hostname, err)
|
||||||
}
|
}
|
||||||
return &manualCertManager{cert: &cert, hostname: hostname}, nil
|
return &manualCertManager{cert: &cert, hostname: hostname}, nil
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ func (m *manualCertManager) TLSConfig() *tls.Config {
|
||||||
|
|
||||||
func (m *manualCertManager) getCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
func (m *manualCertManager) getCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
if hi.ServerName != m.hostname {
|
if hi.ServerName != m.hostname {
|
||||||
//return nil, fmt.Errorf("cert mismatch with hostname: %q", hi.ServerName)
|
return nil, fmt.Errorf("cert mismatch with hostname: %q", hi.ServerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a shallow copy of the cert so the caller can append to its
|
// Return a shallow copy of the cert so the caller can append to its
|
||||||
|
|
|
@ -12,16 +12,9 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||||
github.com/beorn7/perks/quantile from github.com/prometheus/client_golang/prometheus
|
github.com/beorn7/perks/quantile from github.com/prometheus/client_golang/prometheus
|
||||||
💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus
|
💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus
|
||||||
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
|
||||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||||
github.com/golang/protobuf/proto from github.com/matttproud/golang_protobuf_extensions/pbutil+
|
github.com/golang/protobuf/proto from github.com/matttproud/golang_protobuf_extensions/pbutil+
|
||||||
L github.com/google/nftables from tailscale.com/util/linuxfw
|
|
||||||
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
|
||||||
L 💣 github.com/google/nftables/binaryutil from github.com/google/nftables+
|
|
||||||
L github.com/google/nftables/expr from github.com/google/nftables+
|
|
||||||
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
|
|
||||||
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
|
||||||
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
||||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
||||||
|
@ -30,7 +23,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||||
github.com/matttproud/golang_protobuf_extensions/pbutil from github.com/prometheus/common/expfmt
|
github.com/matttproud/golang_protobuf_extensions/pbutil from github.com/prometheus/common/expfmt
|
||||||
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
|
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
|
||||||
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
|
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
|
||||||
L github.com/mdlayher/netlink/nltest from github.com/google/nftables
|
|
||||||
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
||||||
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
|
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
|
||||||
💣 github.com/prometheus/client_golang/prometheus from tailscale.com/tsweb/promvarz
|
💣 github.com/prometheus/client_golang/prometheus from tailscale.com/tsweb/promvarz
|
||||||
|
@ -42,9 +34,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||||
LD github.com/prometheus/procfs from github.com/prometheus/client_golang/prometheus
|
LD github.com/prometheus/procfs from github.com/prometheus/client_golang/prometheus
|
||||||
LD github.com/prometheus/procfs/internal/fs from github.com/prometheus/procfs
|
LD github.com/prometheus/procfs/internal/fs from github.com/prometheus/procfs
|
||||||
LD github.com/prometheus/procfs/internal/util from github.com/prometheus/procfs
|
LD github.com/prometheus/procfs/internal/util from github.com/prometheus/procfs
|
||||||
L 💣 github.com/tailscale/netlink from tailscale.com/util/linuxfw
|
|
||||||
L 💣 github.com/vishvananda/netlink/nl from github.com/tailscale/netlink
|
|
||||||
L github.com/vishvananda/netns from github.com/tailscale/netlink+
|
|
||||||
github.com/x448/float16 from github.com/fxamacker/cbor/v2
|
github.com/x448/float16 from github.com/fxamacker/cbor/v2
|
||||||
💣 go4.org/mem from tailscale.com/client/tailscale+
|
💣 go4.org/mem from tailscale.com/client/tailscale+
|
||||||
go4.org/netipx from tailscale.com/wgengine/filter
|
go4.org/netipx from tailscale.com/wgengine/filter
|
||||||
|
@ -77,20 +66,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||||
google.golang.org/protobuf/runtime/protoimpl from github.com/golang/protobuf/proto+
|
google.golang.org/protobuf/runtime/protoimpl from github.com/golang/protobuf/proto+
|
||||||
google.golang.org/protobuf/types/descriptorpb from google.golang.org/protobuf/reflect/protodesc
|
google.golang.org/protobuf/types/descriptorpb from google.golang.org/protobuf/reflect/protodesc
|
||||||
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+
|
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+
|
||||||
L gvisor.dev/gvisor/pkg/abi from gvisor.dev/gvisor/pkg/abi/linux
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/abi/linux from tailscale.com/util/linuxfw
|
|
||||||
L gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/abi/linux
|
|
||||||
L gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/abi/linux
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/abi/linux+
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/hostarch from gvisor.dev/gvisor/pkg/abi/linux+
|
|
||||||
L gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
|
|
||||||
L gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context
|
|
||||||
L gvisor.dev/gvisor/pkg/marshal from gvisor.dev/gvisor/pkg/abi/linux+
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/marshal/primitive from gvisor.dev/gvisor/pkg/abi/linux
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/abi/linux+
|
|
||||||
L gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/linewriter+
|
|
||||||
L gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context
|
|
||||||
nhooyr.io/websocket from tailscale.com/cmd/derper+
|
nhooyr.io/websocket from tailscale.com/cmd/derper+
|
||||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
||||||
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
|
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
|
||||||
|
@ -118,7 +93,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||||
tailscale.com/net/packet from tailscale.com/wgengine/filter
|
tailscale.com/net/packet from tailscale.com/wgengine/filter
|
||||||
tailscale.com/net/sockstats from tailscale.com/derp/derphttp
|
tailscale.com/net/sockstats from tailscale.com/derp/derphttp
|
||||||
tailscale.com/net/stun from tailscale.com/cmd/derper
|
tailscale.com/net/stun from tailscale.com/cmd/derper
|
||||||
L tailscale.com/net/tcpinfo from tailscale.com/derp
|
|
||||||
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp
|
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp
|
||||||
tailscale.com/net/tsaddr from tailscale.com/ipn+
|
tailscale.com/net/tsaddr from tailscale.com/ipn+
|
||||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
|
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
|
||||||
|
@ -151,14 +125,12 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||||
W tailscale.com/util/clientmetric from tailscale.com/net/tshttpproxy
|
W tailscale.com/util/clientmetric from tailscale.com/net/tshttpproxy
|
||||||
tailscale.com/util/cloudenv from tailscale.com/hostinfo+
|
tailscale.com/util/cloudenv from tailscale.com/hostinfo+
|
||||||
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy
|
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy
|
||||||
tailscale.com/util/cmpx from tailscale.com/cmd/derper+
|
|
||||||
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
|
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
|
||||||
tailscale.com/util/dnsname from tailscale.com/hostinfo+
|
tailscale.com/util/dnsname from tailscale.com/hostinfo+
|
||||||
tailscale.com/util/httpm from tailscale.com/client/tailscale
|
tailscale.com/util/httpm from tailscale.com/client/tailscale
|
||||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
||||||
L 💣 tailscale.com/util/linuxfw from tailscale.com/net/netns
|
|
||||||
tailscale.com/util/mak from tailscale.com/syncs+
|
tailscale.com/util/mak from tailscale.com/syncs+
|
||||||
tailscale.com/util/multierr from tailscale.com/health+
|
tailscale.com/util/multierr from tailscale.com/health
|
||||||
tailscale.com/util/set from tailscale.com/health+
|
tailscale.com/util/set from tailscale.com/health+
|
||||||
tailscale.com/util/singleflight from tailscale.com/net/dnscache
|
tailscale.com/util/singleflight from tailscale.com/net/dnscache
|
||||||
tailscale.com/util/slicesx from tailscale.com/cmd/derper+
|
tailscale.com/util/slicesx from tailscale.com/cmd/derper+
|
||||||
|
@ -182,7 +154,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||||
golang.org/x/exp/constraints from golang.org/x/exp/slices
|
golang.org/x/exp/constraints from golang.org/x/exp/slices
|
||||||
golang.org/x/exp/maps from tailscale.com/types/views
|
|
||||||
golang.org/x/exp/slices from tailscale.com/net/tsaddr+
|
golang.org/x/exp/slices from tailscale.com/net/tsaddr+
|
||||||
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
||||||
golang.org/x/net/dns/dnsmessage from net+
|
golang.org/x/net/dns/dnsmessage from net+
|
||||||
|
@ -208,7 +179,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||||
bytes from bufio+
|
bytes from bufio+
|
||||||
compress/flate from compress/gzip+
|
compress/flate from compress/gzip+
|
||||||
compress/gzip from internal/profile+
|
compress/gzip from internal/profile+
|
||||||
L compress/zlib from debug/elf
|
|
||||||
container/list from crypto/tls+
|
container/list from crypto/tls+
|
||||||
context from crypto/tls+
|
context from crypto/tls+
|
||||||
crypto from crypto/ecdsa+
|
crypto from crypto/ecdsa+
|
||||||
|
@ -232,8 +202,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||||
crypto/tls from golang.org/x/crypto/acme+
|
crypto/tls from golang.org/x/crypto/acme+
|
||||||
crypto/x509 from crypto/tls+
|
crypto/x509 from crypto/tls+
|
||||||
crypto/x509/pkix from crypto/x509+
|
crypto/x509/pkix from crypto/x509+
|
||||||
L debug/dwarf from debug/elf
|
|
||||||
L debug/elf from golang.org/x/sys/unix
|
|
||||||
embed from crypto/internal/nistec+
|
embed from crypto/internal/nistec+
|
||||||
encoding from encoding/json+
|
encoding from encoding/json+
|
||||||
encoding/asn1 from crypto/x509+
|
encoding/asn1 from crypto/x509+
|
||||||
|
@ -249,7 +217,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||||
fmt from compress/flate+
|
fmt from compress/flate+
|
||||||
go/token from google.golang.org/protobuf/internal/strs
|
go/token from google.golang.org/protobuf/internal/strs
|
||||||
hash from crypto+
|
hash from crypto+
|
||||||
L hash/adler32 from compress/zlib
|
|
||||||
hash/crc32 from compress/gzip+
|
hash/crc32 from compress/gzip+
|
||||||
hash/fnv from google.golang.org/protobuf/internal/detrand
|
hash/fnv from google.golang.org/protobuf/internal/detrand
|
||||||
hash/maphash from go4.org/mem
|
hash/maphash from go4.org/mem
|
||||||
|
@ -258,7 +225,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||||
io/fs from crypto/x509+
|
io/fs from crypto/x509+
|
||||||
io/ioutil from github.com/mitchellh/go-ps+
|
io/ioutil from github.com/mitchellh/go-ps+
|
||||||
log from expvar+
|
log from expvar+
|
||||||
log/internal from log
|
|
||||||
math from compress/flate+
|
math from compress/flate+
|
||||||
math/big from crypto/dsa+
|
math/big from crypto/dsa+
|
||||||
math/bits from compress/flate+
|
math/bits from compress/flate+
|
||||||
|
|
|
@ -33,7 +33,6 @@ import (
|
||||||
"tailscale.com/net/stun"
|
"tailscale.com/net/stun"
|
||||||
"tailscale.com/tsweb"
|
"tailscale.com/tsweb"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/util/cmpx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -437,7 +436,11 @@ func defaultMeshPSKFile() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func rateLimitedListenAndServeTLS(srv *http.Server) error {
|
func rateLimitedListenAndServeTLS(srv *http.Server) error {
|
||||||
ln, err := net.Listen("tcp", cmpx.Or(srv.Addr, ":https"))
|
addr := srv.Addr
|
||||||
|
if addr == "" {
|
||||||
|
addr = ":https"
|
||||||
|
}
|
||||||
|
ln, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,38 +13,15 @@ import (
|
||||||
|
|
||||||
"tailscale.com/release/dist"
|
"tailscale.com/release/dist"
|
||||||
"tailscale.com/release/dist/cli"
|
"tailscale.com/release/dist/cli"
|
||||||
"tailscale.com/release/dist/synology"
|
|
||||||
"tailscale.com/release/dist/unixpkgs"
|
"tailscale.com/release/dist/unixpkgs"
|
||||||
)
|
)
|
||||||
|
|
||||||
var synologyPackageCenter bool
|
|
||||||
|
|
||||||
func getTargets() ([]dist.Target, error) {
|
func getTargets() ([]dist.Target, error) {
|
||||||
var ret []dist.Target
|
return unixpkgs.Targets(), nil
|
||||||
|
|
||||||
ret = append(ret, unixpkgs.Targets()...)
|
|
||||||
// Synology packages can be built either for sideloading, or for
|
|
||||||
// distribution by Synology in their package center. When
|
|
||||||
// distributed through the package center, apps can request
|
|
||||||
// additional permissions to use a tuntap interface and control
|
|
||||||
// the NAS's network stack, rather than be forced to run in
|
|
||||||
// userspace mode.
|
|
||||||
//
|
|
||||||
// Since only we can provide packages to Synology for
|
|
||||||
// distribution, we default to building the "sideload" variant of
|
|
||||||
// packages that we distribute on pkgs.tailscale.com.
|
|
||||||
ret = append(ret, synology.Targets(synologyPackageCenter)...)
|
|
||||||
return ret, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd := cli.CLI(getTargets)
|
cmd := cli.CLI(getTargets)
|
||||||
for _, subcmd := range cmd.Subcommands {
|
|
||||||
if subcmd.Name == "build" {
|
|
||||||
subcmd.FlagSet.BoolVar(&synologyPackageCenter, "synology-package-center", false, "build synology packages with extra metadata for the official package center")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil && !errors.Is(err, flag.ErrHelp) {
|
if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil && !errors.Is(err, flag.ErrHelp) {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import (
|
||||||
|
|
||||||
"golang.org/x/oauth2/clientcredentials"
|
"golang.org/x/oauth2/clientcredentials"
|
||||||
"tailscale.com/client/tailscale"
|
"tailscale.com/client/tailscale"
|
||||||
"tailscale.com/util/cmpx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -40,7 +39,10 @@ func main() {
|
||||||
log.Fatal("at least one tag must be specified")
|
log.Fatal("at least one tag must be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseURL := cmpx.Or(os.Getenv("TS_BASE_URL"), "https://api.tailscale.com")
|
baseURL := os.Getenv("TS_BASE_URL")
|
||||||
|
if baseURL == "" {
|
||||||
|
baseURL = "https://api.tailscale.com"
|
||||||
|
}
|
||||||
|
|
||||||
credentials := clientcredentials.Config{
|
credentials := clientcredentials.Config{
|
||||||
ClientID: clientID,
|
ClientID: clientID,
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/client-go/transport"
|
"k8s.io/client-go/transport"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||||
|
@ -37,6 +38,7 @@ import (
|
||||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
|
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
"tailscale.com/client/tailscale"
|
"tailscale.com/client/tailscale"
|
||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
|
@ -183,17 +185,17 @@ waitOnline:
|
||||||
// the cache that sits a few layers below the builder stuff, which will
|
// the cache that sits a few layers below the builder stuff, which will
|
||||||
// implicitly filter what parts of the world the builder code gets to see at
|
// implicitly filter what parts of the world the builder code gets to see at
|
||||||
// all.
|
// all.
|
||||||
nsFilter := cache.ByObject{
|
nsFilter := cache.ObjectSelector{
|
||||||
Field: client.InNamespace(tsNamespace).AsSelector(),
|
Field: fields.SelectorFromSet(fields.Set{"metadata.namespace": tsNamespace}),
|
||||||
}
|
}
|
||||||
restConfig := config.GetConfigOrDie()
|
restConfig := config.GetConfigOrDie()
|
||||||
mgr, err := manager.New(restConfig, manager.Options{
|
mgr, err := manager.New(restConfig, manager.Options{
|
||||||
Cache: cache.Options{
|
NewCache: cache.BuilderWithOptions(cache.Options{
|
||||||
ByObject: map[client.Object]cache.ByObject{
|
SelectorsByObject: map[client.Object]cache.ObjectSelector{
|
||||||
&corev1.Secret{}: nsFilter,
|
&corev1.Secret{}: nsFilter,
|
||||||
&appsv1.StatefulSet{}: nsFilter,
|
&appsv1.StatefulSet{}: nsFilter,
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
startlog.Fatalf("could not create manager: %v", err)
|
startlog.Fatalf("could not create manager: %v", err)
|
||||||
|
@ -209,7 +211,7 @@ waitOnline:
|
||||||
logger: zlog.Named("service-reconciler"),
|
logger: zlog.Named("service-reconciler"),
|
||||||
}
|
}
|
||||||
|
|
||||||
reconcileFilter := handler.EnqueueRequestsFromMapFunc(func(_ context.Context, o client.Object) []reconcile.Request {
|
reconcileFilter := handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request {
|
||||||
ls := o.GetLabels()
|
ls := o.GetLabels()
|
||||||
if ls[LabelManaged] != "true" {
|
if ls[LabelManaged] != "true" {
|
||||||
return nil
|
return nil
|
||||||
|
@ -229,8 +231,8 @@ waitOnline:
|
||||||
err = builder.
|
err = builder.
|
||||||
ControllerManagedBy(mgr).
|
ControllerManagedBy(mgr).
|
||||||
For(&corev1.Service{}).
|
For(&corev1.Service{}).
|
||||||
Watches(&appsv1.StatefulSet{}, reconcileFilter).
|
Watches(&source.Kind{Type: &appsv1.StatefulSet{}}, reconcileFilter).
|
||||||
Watches(&corev1.Secret{}, reconcileFilter).
|
Watches(&source.Kind{Type: &corev1.Secret{}}, reconcileFilter).
|
||||||
Complete(sr)
|
Complete(sr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
startlog.Fatalf("could not create controller: %v", err)
|
startlog.Fatalf("could not create controller: %v", err)
|
||||||
|
|
|
@ -110,8 +110,6 @@ func TestLoadBalancerClass(t *testing.T) {
|
||||||
mustUpdate(t, fc, "default", "test", func(s *corev1.Service) {
|
mustUpdate(t, fc, "default", "test", func(s *corev1.Service) {
|
||||||
s.Spec.Type = corev1.ServiceTypeClusterIP
|
s.Spec.Type = corev1.ServiceTypeClusterIP
|
||||||
s.Spec.LoadBalancerClass = nil
|
s.Spec.LoadBalancerClass = nil
|
||||||
})
|
|
||||||
mustUpdateStatus(t, fc, "default", "test", func(s *corev1.Service) {
|
|
||||||
// Fake client doesn't automatically delete the LoadBalancer status when
|
// Fake client doesn't automatically delete the LoadBalancer status when
|
||||||
// changing away from the LoadBalancer type, we have to do
|
// changing away from the LoadBalancer type, we have to do
|
||||||
// controller-manager's work by hand.
|
// controller-manager's work by hand.
|
||||||
|
@ -449,8 +447,6 @@ func TestLBIntoAnnotation(t *testing.T) {
|
||||||
}
|
}
|
||||||
s.Spec.Type = corev1.ServiceTypeClusterIP
|
s.Spec.Type = corev1.ServiceTypeClusterIP
|
||||||
s.Spec.LoadBalancerClass = nil
|
s.Spec.LoadBalancerClass = nil
|
||||||
})
|
|
||||||
mustUpdateStatus(t, fc, "default", "test", func(s *corev1.Service) {
|
|
||||||
// Fake client doesn't automatically delete the LoadBalancer status when
|
// Fake client doesn't automatically delete the LoadBalancer status when
|
||||||
// changing away from the LoadBalancer type, we have to do
|
// changing away from the LoadBalancer type, we have to do
|
||||||
// controller-manager's work by hand.
|
// controller-manager's work by hand.
|
||||||
|
@ -781,21 +777,6 @@ func mustUpdate[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustUpdateStatus[T any, O ptrObject[T]](t *testing.T, client client.Client, ns, name string, update func(O)) {
|
|
||||||
t.Helper()
|
|
||||||
obj := O(new(T))
|
|
||||||
if err := client.Get(context.Background(), types.NamespacedName{
|
|
||||||
Name: name,
|
|
||||||
Namespace: ns,
|
|
||||||
}, obj); err != nil {
|
|
||||||
t.Fatalf("getting %q: %v", name, err)
|
|
||||||
}
|
|
||||||
update(obj)
|
|
||||||
if err := client.Status().Update(context.Background(), obj); err != nil {
|
|
||||||
t.Fatalf("updating %q: %v", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectEqual[T any, O ptrObject[T]](t *testing.T, client client.Client, want O) {
|
func expectEqual[T any, O ptrObject[T]](t *testing.T, client client.Client, want O) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
got := O(new(T))
|
got := O(new(T))
|
||||||
|
|
|
@ -26,7 +26,6 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ports = flag.String("ports", "443", "comma-separated list of ports to proxy")
|
ports = flag.String("ports", "443", "comma-separated list of ports to proxy")
|
||||||
wgPort = flag.Int("wg-listen-port", 0, "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select")
|
|
||||||
promoteHTTPS = flag.Bool("promote-https", true, "promote HTTP to HTTPS")
|
promoteHTTPS = flag.Bool("promote-https", true, "promote HTTP to HTTPS")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,7 +40,6 @@ func main() {
|
||||||
hostinfo.SetApp("sniproxy")
|
hostinfo.SetApp("sniproxy")
|
||||||
|
|
||||||
var s server
|
var s server
|
||||||
s.ts.Port = uint16(*wgPort)
|
|
||||||
defer s.ts.Close()
|
defer s.ts.Close()
|
||||||
|
|
||||||
lc, err := s.ts.LocalClient()
|
lc, err := s.ts.LocalClient()
|
||||||
|
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
"tailscale.com/types/persist"
|
"tailscale.com/types/persist"
|
||||||
"tailscale.com/types/preftype"
|
"tailscale.com/types/preftype"
|
||||||
"tailscale.com/util/cmpx"
|
|
||||||
"tailscale.com/version/distro"
|
"tailscale.com/version/distro"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -720,7 +719,10 @@ func TestPrefsFromUpArgs(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var warnBuf tstest.MemLogger
|
var warnBuf tstest.MemLogger
|
||||||
goos := cmpx.Or(tt.goos, "linux")
|
goos := tt.goos
|
||||||
|
if goos == "" {
|
||||||
|
goos = "linux"
|
||||||
|
}
|
||||||
st := tt.st
|
st := tt.st
|
||||||
if st == nil {
|
if st == nil {
|
||||||
st = new(ipnstate.Status)
|
st = new(ipnstate.Status)
|
||||||
|
|
|
@ -30,10 +30,10 @@ func newFunnelCommand(e *serveEnv) *ffcli.Command {
|
||||||
return &ffcli.Command{
|
return &ffcli.Command{
|
||||||
Name: "funnel",
|
Name: "funnel",
|
||||||
ShortHelp: "Turn on/off Funnel service",
|
ShortHelp: "Turn on/off Funnel service",
|
||||||
ShortUsage: strings.Join([]string{
|
ShortUsage: strings.TrimSpace(`
|
||||||
"funnel <serve-port> {on|off}",
|
funnel <serve-port> {on|off}
|
||||||
"funnel status [--json]",
|
funnel status [--json]
|
||||||
}, "\n "),
|
`),
|
||||||
LongHelp: strings.Join([]string{
|
LongHelp: strings.Join([]string{
|
||||||
"Funnel allows you to publish a 'tailscale serve'",
|
"Funnel allows you to publish a 'tailscale serve'",
|
||||||
"server publicly, open to the entire internet.",
|
"server publicly, open to the entire internet.",
|
||||||
|
|
|
@ -465,16 +465,7 @@ func runNetworkLockSign(ctx context.Context, args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := localClient.NetworkLockSign(ctx, nodeKey, []byte(rotationKey.Verifier()))
|
return localClient.NetworkLockSign(ctx, nodeKey, []byte(rotationKey.Verifier()))
|
||||||
// Provide a better help message for when someone clicks through the signing flow
|
|
||||||
// on the wrong device.
|
|
||||||
if err != nil && strings.Contains(err.Error(), "this node is not trusted by network lock") {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error: Signing is not available on this device because it does not have a trusted tailnet lock key.")
|
|
||||||
fmt.Fprintln(os.Stderr)
|
|
||||||
fmt.Fprintln(os.Stderr, "Try again on a signing device instead. Tailnet admins can see signing devices on the admin panel.")
|
|
||||||
fmt.Fprintln(os.Stderr)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var nlDisableCmd = &ffcli.Command{
|
var nlDisableCmd = &ffcli.Command{
|
||||||
|
|
|
@ -51,7 +51,7 @@ relay node.
|
||||||
fs.BoolVar(&pingArgs.tsmp, "tsmp", false, "do a TSMP-level ping (through WireGuard, but not either host OS stack)")
|
fs.BoolVar(&pingArgs.tsmp, "tsmp", false, "do a TSMP-level ping (through WireGuard, but not either host OS stack)")
|
||||||
fs.BoolVar(&pingArgs.icmp, "icmp", false, "do a ICMP-level ping (through WireGuard, but not the local host OS stack)")
|
fs.BoolVar(&pingArgs.icmp, "icmp", false, "do a ICMP-level ping (through WireGuard, but not the local host OS stack)")
|
||||||
fs.BoolVar(&pingArgs.peerAPI, "peerapi", false, "try hitting the peer's peerapi HTTP server")
|
fs.BoolVar(&pingArgs.peerAPI, "peerapi", false, "try hitting the peer's peerapi HTTP server")
|
||||||
fs.IntVar(&pingArgs.num, "c", 10, "max number of pings to send. 0 for infinity.")
|
fs.IntVar(&pingArgs.num, "c", 10, "max number of pings to send")
|
||||||
fs.DurationVar(&pingArgs.timeout, "timeout", 5*time.Second, "timeout before giving up on a ping")
|
fs.DurationVar(&pingArgs.timeout, "timeout", 5*time.Second, "timeout before giving up on a ping")
|
||||||
return fs
|
return fs
|
||||||
})(),
|
})(),
|
||||||
|
|
|
@ -35,14 +35,13 @@ func newServeCommand(e *serveEnv) *ffcli.Command {
|
||||||
return &ffcli.Command{
|
return &ffcli.Command{
|
||||||
Name: "serve",
|
Name: "serve",
|
||||||
ShortHelp: "Serve content and local servers",
|
ShortHelp: "Serve content and local servers",
|
||||||
ShortUsage: strings.Join([]string{
|
ShortUsage: strings.TrimSpace(`
|
||||||
"serve http:<port> <mount-point> <source> [off]",
|
serve https:<port> <mount-point> <source> [off]
|
||||||
"serve https:<port> <mount-point> <source> [off]",
|
serve tcp:<port> tcp://localhost:<local-port> [off]
|
||||||
"serve tcp:<port> tcp://localhost:<local-port> [off]",
|
serve tls-terminated-tcp:<port> tcp://localhost:<local-port> [off]
|
||||||
"serve tls-terminated-tcp:<port> tcp://localhost:<local-port> [off]",
|
serve status [--json]
|
||||||
"serve status [--json]",
|
serve reset
|
||||||
"serve reset",
|
`),
|
||||||
}, "\n "),
|
|
||||||
LongHelp: strings.TrimSpace(`
|
LongHelp: strings.TrimSpace(`
|
||||||
*** BETA; all of this is subject to change ***
|
*** BETA; all of this is subject to change ***
|
||||||
|
|
||||||
|
@ -59,7 +58,7 @@ EXAMPLES
|
||||||
- To proxy requests to a web server at 127.0.0.1:3000:
|
- To proxy requests to a web server at 127.0.0.1:3000:
|
||||||
$ tailscale serve https:443 / http://127.0.0.1:3000
|
$ tailscale serve https:443 / http://127.0.0.1:3000
|
||||||
|
|
||||||
Or, using the default port (443):
|
Or, using the default port:
|
||||||
$ tailscale serve https / http://127.0.0.1:3000
|
$ tailscale serve https / http://127.0.0.1:3000
|
||||||
|
|
||||||
- To serve a single file or a directory of files:
|
- To serve a single file or a directory of files:
|
||||||
|
@ -69,12 +68,6 @@ EXAMPLES
|
||||||
- To serve simple static text:
|
- To serve simple static text:
|
||||||
$ tailscale serve https:8080 / text:"Hello, world!"
|
$ tailscale serve https:8080 / text:"Hello, world!"
|
||||||
|
|
||||||
- To serve over HTTP (tailnet only):
|
|
||||||
$ tailscale serve http:80 / http://127.0.0.1:3000
|
|
||||||
|
|
||||||
Or, using the default port (80):
|
|
||||||
$ tailscale serve http / http://127.0.0.1:3000
|
|
||||||
|
|
||||||
- To forward incoming TCP connections on port 2222 to a local TCP server on
|
- To forward incoming TCP connections on port 2222 to a local TCP server on
|
||||||
port 22 (e.g. to run OpenSSH in parallel with Tailscale SSH):
|
port 22 (e.g. to run OpenSSH in parallel with Tailscale SSH):
|
||||||
$ tailscale serve tcp:2222 tcp://localhost:22
|
$ tailscale serve tcp:2222 tcp://localhost:22
|
||||||
|
@ -182,7 +175,6 @@ func (e *serveEnv) getLocalClientStatus(ctx context.Context) (*ipnstate.Status,
|
||||||
// serve config types like proxy, path, and text.
|
// serve config types like proxy, path, and text.
|
||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
// - tailscale serve http / http://localhost:3000
|
|
||||||
// - tailscale serve https / http://localhost:3000
|
// - tailscale serve https / http://localhost:3000
|
||||||
// - tailscale serve https /images/ /var/www/images/
|
// - tailscale serve https /images/ /var/www/images/
|
||||||
// - tailscale serve https:10000 /motd.txt text:"Hello, world!"
|
// - tailscale serve https:10000 /motd.txt text:"Hello, world!"
|
||||||
|
@ -207,14 +199,19 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
|
||||||
return e.lc.SetServeConfig(ctx, sc)
|
return e.lc.SetServeConfig(ctx, sc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parsePort := func(portStr string) (uint16, error) {
|
||||||
|
port64, err := strconv.ParseUint(portStr, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint16(port64), nil
|
||||||
|
}
|
||||||
|
|
||||||
srcType, srcPortStr, found := strings.Cut(args[0], ":")
|
srcType, srcPortStr, found := strings.Cut(args[0], ":")
|
||||||
if !found {
|
if !found {
|
||||||
if srcType == "https" && srcPortStr == "" {
|
if srcType == "https" && srcPortStr == "" {
|
||||||
// Default https port to 443.
|
// Default https port to 443.
|
||||||
srcPortStr = "443"
|
srcPortStr = "443"
|
||||||
} else if srcType == "http" && srcPortStr == "" {
|
|
||||||
// Default http port to 80.
|
|
||||||
srcPortStr = "80"
|
|
||||||
} else {
|
} else {
|
||||||
return flag.ErrHelp
|
return flag.ErrHelp
|
||||||
}
|
}
|
||||||
|
@ -222,18 +219,18 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
|
||||||
|
|
||||||
turnOff := "off" == args[len(args)-1]
|
turnOff := "off" == args[len(args)-1]
|
||||||
|
|
||||||
if len(args) < 2 || ((srcType == "https" || srcType == "http") && !turnOff && len(args) < 3) {
|
if len(args) < 2 || (srcType == "https" && !turnOff && len(args) < 3) {
|
||||||
fmt.Fprintf(os.Stderr, "error: invalid number of arguments\n\n")
|
fmt.Fprintf(os.Stderr, "error: invalid number of arguments\n\n")
|
||||||
return flag.ErrHelp
|
return flag.ErrHelp
|
||||||
}
|
}
|
||||||
|
|
||||||
srcPort, err := parseServePort(srcPortStr)
|
srcPort, err := parsePort(srcPortStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid port %q: %w", srcPortStr, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch srcType {
|
switch srcType {
|
||||||
case "https", "http":
|
case "https":
|
||||||
mount, err := cleanMountPoint(args[1])
|
mount, err := cleanMountPoint(args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -241,8 +238,7 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
|
||||||
if turnOff {
|
if turnOff {
|
||||||
return e.handleWebServeRemove(ctx, srcPort, mount)
|
return e.handleWebServeRemove(ctx, srcPort, mount)
|
||||||
}
|
}
|
||||||
useTLS := srcType == "https"
|
return e.handleWebServe(ctx, srcPort, mount, args[2])
|
||||||
return e.handleWebServe(ctx, srcPort, useTLS, mount, args[2])
|
|
||||||
case "tcp", "tls-terminated-tcp":
|
case "tcp", "tls-terminated-tcp":
|
||||||
if turnOff {
|
if turnOff {
|
||||||
return e.handleTCPServeRemove(ctx, srcPort)
|
return e.handleTCPServeRemove(ctx, srcPort)
|
||||||
|
@ -250,20 +246,20 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
|
||||||
return e.handleTCPServe(ctx, srcType, srcPort, args[1])
|
return e.handleTCPServe(ctx, srcType, srcPort, args[1])
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "error: invalid serve type %q\n", srcType)
|
fmt.Fprintf(os.Stderr, "error: invalid serve type %q\n", srcType)
|
||||||
fmt.Fprint(os.Stderr, "must be one of: http:<port>, https:<port>, tcp:<port> or tls-terminated-tcp:<port>\n\n", srcType)
|
fmt.Fprint(os.Stderr, "must be one of: https:<port>, tcp:<port> or tls-terminated-tcp:<port>\n\n", srcType)
|
||||||
return flag.ErrHelp
|
return flag.ErrHelp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleWebServe handles the "tailscale serve (http/https):..." subcommand. It
|
// handleWebServe handles the "tailscale serve https:..." subcommand.
|
||||||
// configures the serve config to forward HTTPS connections to the given source.
|
// It configures the serve config to forward HTTPS connections to the
|
||||||
|
// given source.
|
||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
// - tailscale serve http / http://localhost:3000
|
|
||||||
// - tailscale serve https / http://localhost:3000
|
// - tailscale serve https / http://localhost:3000
|
||||||
// - tailscale serve https:8443 /files/ /home/alice/shared-files/
|
// - tailscale serve https:8443 /files/ /home/alice/shared-files/
|
||||||
// - tailscale serve https:10000 /motd.txt text:"Hello, world!"
|
// - tailscale serve https:10000 /motd.txt text:"Hello, world!"
|
||||||
func (e *serveEnv) handleWebServe(ctx context.Context, srvPort uint16, useTLS bool, mount, source string) error {
|
func (e *serveEnv) handleWebServe(ctx context.Context, srvPort uint16, mount, source string) error {
|
||||||
h := new(ipn.HTTPHandler)
|
h := new(ipn.HTTPHandler)
|
||||||
|
|
||||||
ts, _, _ := strings.Cut(source, ":")
|
ts, _, _ := strings.Cut(source, ":")
|
||||||
|
@ -322,7 +318,7 @@ func (e *serveEnv) handleWebServe(ctx context.Context, srvPort uint16, useTLS bo
|
||||||
return flag.ErrHelp
|
return flag.ErrHelp
|
||||||
}
|
}
|
||||||
|
|
||||||
mak.Set(&sc.TCP, srvPort, &ipn.TCPPortHandler{HTTPS: useTLS, HTTP: !useTLS})
|
mak.Set(&sc.TCP, srvPort, &ipn.TCPPortHandler{HTTPS: true})
|
||||||
|
|
||||||
if _, ok := sc.Web[hp]; !ok {
|
if _, ok := sc.Web[hp]; !ok {
|
||||||
mak.Set(&sc.Web, hp, new(ipn.WebServerConfig))
|
mak.Set(&sc.Web, hp, new(ipn.WebServerConfig))
|
||||||
|
@ -630,10 +626,7 @@ func (e *serveEnv) runServeStatus(ctx context.Context, args []string) error {
|
||||||
printf("\n")
|
printf("\n")
|
||||||
}
|
}
|
||||||
for hp := range sc.Web {
|
for hp := range sc.Web {
|
||||||
err := e.printWebStatusTree(sc, hp)
|
printWebStatusTree(sc, hp)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
printf("\n")
|
printf("\n")
|
||||||
}
|
}
|
||||||
printFunnelWarning(sc)
|
printFunnelWarning(sc)
|
||||||
|
@ -672,37 +665,20 @@ func printTCPStatusTree(ctx context.Context, sc *ipn.ServeConfig, st *ipnstate.S
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *serveEnv) printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) error {
|
func printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) {
|
||||||
// No-op if no serve config
|
|
||||||
if sc == nil {
|
if sc == nil {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
fStatus := "tailnet only"
|
fStatus := "tailnet only"
|
||||||
if sc.AllowFunnel[hp] {
|
if sc.AllowFunnel[hp] {
|
||||||
fStatus = "Funnel on"
|
fStatus = "Funnel on"
|
||||||
}
|
}
|
||||||
host, portStr, _ := net.SplitHostPort(string(hp))
|
host, portStr, _ := net.SplitHostPort(string(hp))
|
||||||
|
if portStr == "443" {
|
||||||
port, err := parseServePort(portStr)
|
printf("https://%s (%s)\n", host, fStatus)
|
||||||
if err != nil {
|
} else {
|
||||||
return fmt.Errorf("invalid port %q: %w", portStr, err)
|
printf("https://%s:%s (%s)\n", host, portStr, fStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := "https"
|
|
||||||
if sc.IsServingHTTP(port) {
|
|
||||||
scheme = "http"
|
|
||||||
}
|
|
||||||
|
|
||||||
portPart := ":" + portStr
|
|
||||||
if scheme == "http" && portStr == "80" ||
|
|
||||||
scheme == "https" && portStr == "443" {
|
|
||||||
portPart = ""
|
|
||||||
}
|
|
||||||
if scheme == "http" {
|
|
||||||
hostname, _, _ := strings.Cut("host", ".")
|
|
||||||
printf("%s://%s%s (%s)\n", scheme, hostname, portPart, fStatus)
|
|
||||||
}
|
|
||||||
printf("%s://%s%s (%s)\n", scheme, host, portPart, fStatus)
|
|
||||||
srvTypeAndDesc := func(h *ipn.HTTPHandler) (string, string) {
|
srvTypeAndDesc := func(h *ipn.HTTPHandler) (string, string) {
|
||||||
switch {
|
switch {
|
||||||
case h.Path != "":
|
case h.Path != "":
|
||||||
|
@ -729,8 +705,6 @@ func (e *serveEnv) printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) erro
|
||||||
t, d := srvTypeAndDesc(h)
|
t, d := srvTypeAndDesc(h)
|
||||||
printf("%s %s%s %-5s %s\n", "|--", m, strings.Repeat(" ", maxLen-len(m)), t, d)
|
printf("%s %s%s %-5s %s\n", "|--", m, strings.Repeat(" ", maxLen-len(m)), t, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func elipticallyTruncate(s string, max int) string {
|
func elipticallyTruncate(s string, max int) string {
|
||||||
|
@ -751,16 +725,3 @@ func (e *serveEnv) runServeReset(ctx context.Context, args []string) error {
|
||||||
sc := new(ipn.ServeConfig)
|
sc := new(ipn.ServeConfig)
|
||||||
return e.lc.SetServeConfig(ctx, sc)
|
return e.lc.SetServeConfig(ctx, sc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseServePort parses a port number from a string and returns it as a
|
|
||||||
// uint16. It returns an error if the port number is invalid or zero.
|
|
||||||
func parseServePort(s string) (uint16, error) {
|
|
||||||
p, err := strconv.ParseUint(s, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if p == 0 {
|
|
||||||
return 0, errors.New("port number must be non-zero")
|
|
||||||
}
|
|
||||||
return uint16(p), nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -89,59 +89,6 @@ func TestServeConfigMutations(t *testing.T) {
|
||||||
wantErr: exactErr(flag.ErrHelp, "flag.ErrHelp"),
|
wantErr: exactErr(flag.ErrHelp, "flag.ErrHelp"),
|
||||||
})
|
})
|
||||||
|
|
||||||
// https
|
|
||||||
add(step{reset: true})
|
|
||||||
add(step{ // allow omitting port (default to 80)
|
|
||||||
command: cmd("http / http://localhost:3000"),
|
|
||||||
want: &ipn.ServeConfig{
|
|
||||||
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
|
|
||||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
|
||||||
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
|
|
||||||
"/": {Proxy: "http://127.0.0.1:3000"},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
add(step{ // support non Funnel port
|
|
||||||
command: cmd("http:9999 /abc http://localhost:3001"),
|
|
||||||
want: &ipn.ServeConfig{
|
|
||||||
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 9999: {HTTP: true}},
|
|
||||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
|
||||||
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
|
|
||||||
"/": {Proxy: "http://127.0.0.1:3000"},
|
|
||||||
}},
|
|
||||||
"foo.test.ts.net:9999": {Handlers: map[string]*ipn.HTTPHandler{
|
|
||||||
"/abc": {Proxy: "http://127.0.0.1:3001"},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
add(step{
|
|
||||||
command: cmd("http:9999 /abc off"),
|
|
||||||
want: &ipn.ServeConfig{
|
|
||||||
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
|
|
||||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
|
||||||
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
|
|
||||||
"/": {Proxy: "http://127.0.0.1:3000"},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
add(step{
|
|
||||||
command: cmd("http:8080 /abc http://127.0.0.1:3001"),
|
|
||||||
want: &ipn.ServeConfig{
|
|
||||||
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 8080: {HTTP: true}},
|
|
||||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
|
||||||
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
|
|
||||||
"/": {Proxy: "http://127.0.0.1:3000"},
|
|
||||||
}},
|
|
||||||
"foo.test.ts.net:8080": {Handlers: map[string]*ipn.HTTPHandler{
|
|
||||||
"/abc": {Proxy: "http://127.0.0.1:3001"},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// https
|
// https
|
||||||
add(step{reset: true})
|
add(step{reset: true})
|
||||||
add(step{
|
add(step{
|
||||||
|
|
|
@ -29,7 +29,6 @@ import (
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/util/cmpx"
|
|
||||||
"tailscale.com/util/groupmember"
|
"tailscale.com/util/groupmember"
|
||||||
"tailscale.com/version/distro"
|
"tailscale.com/version/distro"
|
||||||
)
|
)
|
||||||
|
@ -156,7 +155,10 @@ func runWeb(ctx context.Context, args []string) error {
|
||||||
// urlOfListenAddr parses a given listen address into a formatted URL
|
// urlOfListenAddr parses a given listen address into a formatted URL
|
||||||
func urlOfListenAddr(addr string) string {
|
func urlOfListenAddr(addr string) string {
|
||||||
host, port, _ := net.SplitHostPort(addr)
|
host, port, _ := net.SplitHostPort(addr)
|
||||||
return fmt.Sprintf("http://%s", net.JoinHostPort(cmpx.Or(host, "127.0.0.1"), port))
|
if host == "" {
|
||||||
|
host = "127.0.0.1"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("http://%s", net.JoinHostPort(host, port))
|
||||||
}
|
}
|
||||||
|
|
||||||
// authorize returns the name of the user accessing the web UI after verifying
|
// authorize returns the name of the user accessing the web UI after verifying
|
||||||
|
|
|
@ -10,15 +10,8 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+
|
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+
|
||||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||||
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
|
||||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||||
L github.com/google/nftables from tailscale.com/util/linuxfw
|
|
||||||
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
|
||||||
L 💣 github.com/google/nftables/binaryutil from github.com/google/nftables+
|
|
||||||
L github.com/google/nftables/expr from github.com/google/nftables+
|
|
||||||
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
|
|
||||||
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
|
||||||
github.com/google/uuid from tailscale.com/util/quarantine+
|
github.com/google/uuid from tailscale.com/util/quarantine+
|
||||||
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
||||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||||
|
@ -30,7 +23,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
💣 github.com/mattn/go-isatty from github.com/mattn/go-colorable+
|
💣 github.com/mattn/go-isatty from github.com/mattn/go-colorable+
|
||||||
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
|
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
|
||||||
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
|
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
|
||||||
L github.com/mdlayher/netlink/nltest from github.com/google/nftables
|
|
||||||
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
||||||
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli+
|
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli+
|
||||||
github.com/peterbourgon/ff/v3 from github.com/peterbourgon/ff/v3/ffcli
|
github.com/peterbourgon/ff/v3 from github.com/peterbourgon/ff/v3/ffcli
|
||||||
|
@ -44,30 +36,13 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
github.com/tailscale/goupnp/scpd from github.com/tailscale/goupnp
|
github.com/tailscale/goupnp/scpd from github.com/tailscale/goupnp
|
||||||
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
|
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
|
||||||
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
|
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
|
||||||
L 💣 github.com/tailscale/netlink from tailscale.com/util/linuxfw
|
|
||||||
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
||||||
github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli
|
github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli
|
||||||
L 💣 github.com/vishvananda/netlink/nl from github.com/tailscale/netlink
|
|
||||||
L github.com/vishvananda/netns from github.com/tailscale/netlink+
|
|
||||||
github.com/x448/float16 from github.com/fxamacker/cbor/v2
|
github.com/x448/float16 from github.com/fxamacker/cbor/v2
|
||||||
💣 go4.org/mem from tailscale.com/derp+
|
💣 go4.org/mem from tailscale.com/derp+
|
||||||
go4.org/netipx from tailscale.com/wgengine/filter
|
go4.org/netipx from tailscale.com/wgengine/filter
|
||||||
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
|
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
|
||||||
gopkg.in/yaml.v2 from sigs.k8s.io/yaml
|
gopkg.in/yaml.v2 from sigs.k8s.io/yaml
|
||||||
L gvisor.dev/gvisor/pkg/abi from gvisor.dev/gvisor/pkg/abi/linux
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/abi/linux from tailscale.com/util/linuxfw
|
|
||||||
L gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/abi/linux
|
|
||||||
L gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/abi/linux
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/abi/linux+
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/hostarch from gvisor.dev/gvisor/pkg/abi/linux+
|
|
||||||
L gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
|
|
||||||
L gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context
|
|
||||||
L gvisor.dev/gvisor/pkg/marshal from gvisor.dev/gvisor/pkg/abi/linux+
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/marshal/primitive from gvisor.dev/gvisor/pkg/abi/linux
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/abi/linux+
|
|
||||||
L gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/linewriter+
|
|
||||||
L gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context
|
|
||||||
k8s.io/client-go/util/homedir from tailscale.com/cmd/tailscale/cli
|
k8s.io/client-go/util/homedir from tailscale.com/cmd/tailscale/cli
|
||||||
nhooyr.io/websocket from tailscale.com/derp/derphttp+
|
nhooyr.io/websocket from tailscale.com/derp/derphttp+
|
||||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
||||||
|
@ -109,7 +84,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
tailscale.com/net/portmapper from tailscale.com/net/netcheck+
|
tailscale.com/net/portmapper from tailscale.com/net/netcheck+
|
||||||
tailscale.com/net/sockstats from tailscale.com/control/controlhttp+
|
tailscale.com/net/sockstats from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/net/stun from tailscale.com/net/netcheck
|
tailscale.com/net/stun from tailscale.com/net/netcheck
|
||||||
L tailscale.com/net/tcpinfo from tailscale.com/derp
|
|
||||||
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp+
|
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp+
|
||||||
tailscale.com/net/tsaddr from tailscale.com/net/interfaces+
|
tailscale.com/net/tsaddr from tailscale.com/net/interfaces+
|
||||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
|
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
|
||||||
|
@ -140,13 +114,11 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
tailscale.com/util/clientmetric from tailscale.com/net/netcheck+
|
tailscale.com/util/clientmetric from tailscale.com/net/netcheck+
|
||||||
tailscale.com/util/cloudenv from tailscale.com/net/dnscache+
|
tailscale.com/util/cloudenv from tailscale.com/net/dnscache+
|
||||||
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy
|
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy
|
||||||
tailscale.com/util/cmpx from tailscale.com/cmd/tailscale/cli+
|
|
||||||
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
|
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
|
||||||
tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/util/groupmember from tailscale.com/cmd/tailscale/cli
|
tailscale.com/util/groupmember from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/util/httpm from tailscale.com/client/tailscale
|
tailscale.com/util/httpm from tailscale.com/client/tailscale
|
||||||
tailscale.com/util/lineread from tailscale.com/net/interfaces+
|
tailscale.com/util/lineread from tailscale.com/net/interfaces+
|
||||||
L 💣 tailscale.com/util/linuxfw from tailscale.com/net/netns
|
|
||||||
tailscale.com/util/mak from tailscale.com/net/netcheck+
|
tailscale.com/util/mak from tailscale.com/net/netcheck+
|
||||||
tailscale.com/util/multierr from tailscale.com/control/controlhttp+
|
tailscale.com/util/multierr from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/util/must from tailscale.com/cmd/tailscale/cli
|
tailscale.com/util/must from tailscale.com/cmd/tailscale/cli
|
||||||
|
@ -173,7 +145,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
golang.org/x/crypto/pbkdf2 from software.sslmate.com/src/go-pkcs12
|
golang.org/x/crypto/pbkdf2 from software.sslmate.com/src/go-pkcs12
|
||||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||||
golang.org/x/exp/constraints from golang.org/x/exp/slices
|
golang.org/x/exp/constraints from golang.org/x/exp/slices
|
||||||
golang.org/x/exp/maps from tailscale.com/types/views
|
|
||||||
golang.org/x/exp/slices from tailscale.com/net/tsaddr+
|
golang.org/x/exp/slices from tailscale.com/net/tsaddr+
|
||||||
golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
||||||
golang.org/x/net/dns/dnsmessage from net+
|
golang.org/x/net/dns/dnsmessage from net+
|
||||||
|
@ -205,7 +176,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
bytes from bufio+
|
bytes from bufio+
|
||||||
compress/flate from compress/gzip+
|
compress/flate from compress/gzip+
|
||||||
compress/gzip from net/http
|
compress/gzip from net/http
|
||||||
compress/zlib from image/png+
|
compress/zlib from image/png
|
||||||
container/list from crypto/tls+
|
container/list from crypto/tls+
|
||||||
context from crypto/tls+
|
context from crypto/tls+
|
||||||
crypto from crypto/ecdsa+
|
crypto from crypto/ecdsa+
|
||||||
|
@ -230,8 +201,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
crypto/x509 from crypto/tls+
|
crypto/x509 from crypto/tls+
|
||||||
crypto/x509/pkix from crypto/x509+
|
crypto/x509/pkix from crypto/x509+
|
||||||
database/sql/driver from github.com/google/uuid
|
database/sql/driver from github.com/google/uuid
|
||||||
L debug/dwarf from debug/elf
|
|
||||||
L debug/elf from golang.org/x/sys/unix
|
|
||||||
embed from tailscale.com/cmd/tailscale/cli+
|
embed from tailscale.com/cmd/tailscale/cli+
|
||||||
encoding from encoding/json+
|
encoding from encoding/json+
|
||||||
encoding/asn1 from crypto/x509+
|
encoding/asn1 from crypto/x509+
|
||||||
|
@ -259,7 +228,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
io/fs from crypto/x509+
|
io/fs from crypto/x509+
|
||||||
io/ioutil from golang.org/x/sys/cpu+
|
io/ioutil from golang.org/x/sys/cpu+
|
||||||
log from expvar+
|
log from expvar+
|
||||||
log/internal from log
|
|
||||||
math from compress/flate+
|
math from compress/flate+
|
||||||
math/big from crypto/dsa+
|
math/big from crypto/dsa+
|
||||||
math/bits from compress/flate+
|
math/bits from compress/flate+
|
||||||
|
|
|
@ -75,7 +75,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
L github.com/aws/smithy-go/transport/http from github.com/aws/aws-sdk-go-v2/aws/middleware+
|
L github.com/aws/smithy-go/transport/http from github.com/aws/aws-sdk-go-v2/aws/middleware+
|
||||||
L github.com/aws/smithy-go/transport/http/internal/io from github.com/aws/smithy-go/transport/http
|
L github.com/aws/smithy-go/transport/http/internal/io from github.com/aws/smithy-go/transport/http
|
||||||
L github.com/aws/smithy-go/waiter from github.com/aws/aws-sdk-go-v2/service/ssm
|
L github.com/aws/smithy-go/waiter from github.com/aws/aws-sdk-go-v2/service/ssm
|
||||||
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router
|
||||||
LD 💣 github.com/creack/pty from tailscale.com/ssh/tailssh
|
LD 💣 github.com/creack/pty from tailscale.com/ssh/tailssh
|
||||||
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com
|
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com
|
||||||
W 💣 github.com/dblohm7/wingoes/com from tailscale.com/cmd/tailscaled
|
W 💣 github.com/dblohm7/wingoes/com from tailscale.com/cmd/tailscaled
|
||||||
|
@ -86,12 +86,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns+
|
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns+
|
||||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||||
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+
|
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+
|
||||||
L github.com/google/nftables from tailscale.com/util/linuxfw
|
|
||||||
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
|
||||||
L 💣 github.com/google/nftables/binaryutil from github.com/google/nftables+
|
|
||||||
L github.com/google/nftables/expr from github.com/google/nftables+
|
|
||||||
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
|
|
||||||
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
|
||||||
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
||||||
L 💣 github.com/illarion/gonotify from tailscale.com/net/dns
|
L 💣 github.com/illarion/gonotify from tailscale.com/net/dns
|
||||||
L github.com/insomniacslk/dhcp/dhcpv4 from tailscale.com/net/tstun
|
L github.com/insomniacslk/dhcp/dhcpv4 from tailscale.com/net/tstun
|
||||||
|
@ -115,7 +109,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
L github.com/mdlayher/genetlink from tailscale.com/net/tstun
|
L github.com/mdlayher/genetlink from tailscale.com/net/tstun
|
||||||
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
|
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
|
||||||
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
|
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
|
||||||
L github.com/mdlayher/netlink/nltest from github.com/google/nftables
|
|
||||||
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
|
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
|
||||||
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
||||||
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
|
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
|
||||||
|
@ -160,18 +153,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
go4.org/netipx from tailscale.com/ipn/ipnlocal+
|
go4.org/netipx from tailscale.com/ipn/ipnlocal+
|
||||||
W 💣 golang.zx2c4.com/wintun from github.com/tailscale/wireguard-go/tun+
|
W 💣 golang.zx2c4.com/wintun from github.com/tailscale/wireguard-go/tun+
|
||||||
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/dns+
|
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/dns+
|
||||||
L gvisor.dev/gvisor/pkg/abi from gvisor.dev/gvisor/pkg/abi/linux
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/abi/linux from tailscale.com/util/linuxfw
|
|
||||||
gvisor.dev/gvisor/pkg/atomicbitops from gvisor.dev/gvisor/pkg/tcpip+
|
gvisor.dev/gvisor/pkg/atomicbitops from gvisor.dev/gvisor/pkg/tcpip+
|
||||||
gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/bufferv2+
|
gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/bufferv2
|
||||||
💣 gvisor.dev/gvisor/pkg/bufferv2 from gvisor.dev/gvisor/pkg/tcpip+
|
💣 gvisor.dev/gvisor/pkg/bufferv2 from gvisor.dev/gvisor/pkg/tcpip+
|
||||||
gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/refs+
|
gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/refs
|
||||||
💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/state/wire+
|
💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/state/wire+
|
||||||
L 💣 gvisor.dev/gvisor/pkg/hostarch from gvisor.dev/gvisor/pkg/abi/linux+
|
|
||||||
gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
|
gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
|
||||||
gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context+
|
gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context+
|
||||||
L gvisor.dev/gvisor/pkg/marshal from gvisor.dev/gvisor/pkg/abi/linux+
|
|
||||||
L 💣 gvisor.dev/gvisor/pkg/marshal/primitive from gvisor.dev/gvisor/pkg/abi/linux
|
|
||||||
gvisor.dev/gvisor/pkg/rand from gvisor.dev/gvisor/pkg/tcpip/network/hash+
|
gvisor.dev/gvisor/pkg/rand from gvisor.dev/gvisor/pkg/tcpip/network/hash+
|
||||||
gvisor.dev/gvisor/pkg/refs from gvisor.dev/gvisor/pkg/bufferv2+
|
gvisor.dev/gvisor/pkg/refs from gvisor.dev/gvisor/pkg/bufferv2+
|
||||||
💣 gvisor.dev/gvisor/pkg/sleep from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
💣 gvisor.dev/gvisor/pkg/sleep from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
||||||
|
@ -276,7 +264,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled
|
tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled
|
||||||
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
|
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
|
||||||
tailscale.com/net/stun from tailscale.com/net/netcheck+
|
tailscale.com/net/stun from tailscale.com/net/netcheck+
|
||||||
L tailscale.com/net/tcpinfo from tailscale.com/derp
|
|
||||||
tailscale.com/net/tlsdial from tailscale.com/control/controlclient+
|
tailscale.com/net/tlsdial from tailscale.com/control/controlclient+
|
||||||
tailscale.com/net/tsaddr from tailscale.com/ipn+
|
tailscale.com/net/tsaddr from tailscale.com/ipn+
|
||||||
tailscale.com/net/tsdial from tailscale.com/control/controlclient+
|
tailscale.com/net/tsdial from tailscale.com/control/controlclient+
|
||||||
|
@ -321,7 +308,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
tailscale.com/util/clientmetric from tailscale.com/control/controlclient+
|
tailscale.com/util/clientmetric from tailscale.com/control/controlclient+
|
||||||
tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+
|
tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+
|
||||||
LW tailscale.com/util/cmpver from tailscale.com/net/dns+
|
LW tailscale.com/util/cmpver from tailscale.com/net/dns+
|
||||||
tailscale.com/util/cmpx from tailscale.com/derp/derphttp+
|
|
||||||
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
|
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
|
||||||
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics+
|
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics+
|
||||||
tailscale.com/util/dnsname from tailscale.com/hostinfo+
|
tailscale.com/util/dnsname from tailscale.com/hostinfo+
|
||||||
|
@ -330,7 +316,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
||||||
tailscale.com/util/httpm from tailscale.com/client/tailscale+
|
tailscale.com/util/httpm from tailscale.com/client/tailscale+
|
||||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
||||||
L 💣 tailscale.com/util/linuxfw from tailscale.com/net/netns+
|
|
||||||
tailscale.com/util/mak from tailscale.com/control/controlclient+
|
tailscale.com/util/mak from tailscale.com/control/controlclient+
|
||||||
tailscale.com/util/multierr from tailscale.com/control/controlclient+
|
tailscale.com/util/multierr from tailscale.com/control/controlclient+
|
||||||
tailscale.com/util/must from tailscale.com/logpolicy
|
tailscale.com/util/must from tailscale.com/logpolicy
|
||||||
|
@ -379,7 +364,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||||
LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh+
|
LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh+
|
||||||
golang.org/x/exp/constraints from golang.org/x/exp/slices+
|
golang.org/x/exp/constraints from golang.org/x/exp/slices+
|
||||||
golang.org/x/exp/maps from tailscale.com/wgengine+
|
golang.org/x/exp/maps from tailscale.com/wgengine
|
||||||
golang.org/x/exp/slices from tailscale.com/ipn/ipnlocal+
|
golang.org/x/exp/slices from tailscale.com/ipn/ipnlocal+
|
||||||
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
||||||
golang.org/x/net/dns/dnsmessage from net+
|
golang.org/x/net/dns/dnsmessage from net+
|
||||||
|
@ -412,7 +397,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
bytes from bufio+
|
bytes from bufio+
|
||||||
compress/flate from compress/gzip+
|
compress/flate from compress/gzip+
|
||||||
compress/gzip from golang.org/x/net/http2+
|
compress/gzip from golang.org/x/net/http2+
|
||||||
L compress/zlib from debug/elf
|
|
||||||
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
||||||
container/list from crypto/tls+
|
container/list from crypto/tls+
|
||||||
context from crypto/tls+
|
context from crypto/tls+
|
||||||
|
@ -437,8 +421,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
crypto/tls from github.com/tcnksm/go-httpstat+
|
crypto/tls from github.com/tcnksm/go-httpstat+
|
||||||
crypto/x509 from crypto/tls+
|
crypto/x509 from crypto/tls+
|
||||||
crypto/x509/pkix from crypto/x509+
|
crypto/x509/pkix from crypto/x509+
|
||||||
L debug/dwarf from debug/elf
|
|
||||||
L debug/elf from golang.org/x/sys/unix
|
|
||||||
embed from tailscale.com+
|
embed from tailscale.com+
|
||||||
encoding from encoding/json+
|
encoding from encoding/json+
|
||||||
encoding/asn1 from crypto/x509+
|
encoding/asn1 from crypto/x509+
|
||||||
|
@ -454,7 +436,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
flag from net/http/httptest+
|
flag from net/http/httptest+
|
||||||
fmt from compress/flate+
|
fmt from compress/flate+
|
||||||
hash from crypto+
|
hash from crypto+
|
||||||
hash/adler32 from tailscale.com/ipn/ipnlocal+
|
hash/adler32 from tailscale.com/ipn/ipnlocal
|
||||||
hash/crc32 from compress/gzip+
|
hash/crc32 from compress/gzip+
|
||||||
hash/fnv from tailscale.com/wgengine/magicsock+
|
hash/fnv from tailscale.com/wgengine/magicsock+
|
||||||
hash/maphash from go4.org/mem
|
hash/maphash from go4.org/mem
|
||||||
|
@ -463,7 +445,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
io/fs from crypto/x509+
|
io/fs from crypto/x509+
|
||||||
io/ioutil from github.com/godbus/dbus/v5+
|
io/ioutil from github.com/godbus/dbus/v5+
|
||||||
log from expvar+
|
log from expvar+
|
||||||
log/internal from log
|
|
||||||
LD log/syslog from tailscale.com/ssh/tailssh
|
LD log/syslog from tailscale.com/ssh/tailssh
|
||||||
math from compress/flate+
|
math from compress/flate+
|
||||||
math/big from crypto/dsa+
|
math/big from crypto/dsa+
|
||||||
|
|
|
@ -126,10 +126,6 @@ var syslogf logger.Logf = logger.Discard
|
||||||
// At this point we're still the parent process that
|
// At this point we're still the parent process that
|
||||||
// Windows started.
|
// Windows started.
|
||||||
func runWindowsService(pol *logpolicy.Policy) error {
|
func runWindowsService(pol *logpolicy.Policy) error {
|
||||||
go func() {
|
|
||||||
winutil.LogSupportInfo(log.Printf)
|
|
||||||
}()
|
|
||||||
|
|
||||||
if winutil.GetPolicyInteger("LogSCMInteractions", 0) != 0 {
|
if winutil.GetPolicyInteger("LogSCMInteractions", 0) != 0 {
|
||||||
syslog, err := eventlog.Open(serviceName)
|
syslog, err := eventlog.Open(serviceName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -7,20 +7,16 @@
|
||||||
package flakytest
|
package flakytest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FlakyTestLogMessage is a sentinel value that is printed to stderr when a
|
// InTestWrapper returns whether or not this binary is running under our test
|
||||||
// flaky test is marked. This is used by cmd/testwrapper to detect flaky tests
|
// wrapper.
|
||||||
// and retry them.
|
func InTestWrapper() bool {
|
||||||
const FlakyTestLogMessage = "flakytest: this is a known flaky test"
|
return os.Getenv("TS_IN_TESTWRAPPER") != ""
|
||||||
|
}
|
||||||
// FlakeAttemptEnv is an environment variable that is set by cmd/testwrapper
|
|
||||||
// when a flaky test is retried. It contains the attempt number, starting at 1.
|
|
||||||
const FlakeAttemptEnv = "TS_TESTWRAPPER_ATTEMPT"
|
|
||||||
|
|
||||||
var issueRegexp = regexp.MustCompile(`\Ahttps://github\.com/tailscale/[a-zA-Z0-9_.-]+/issues/\d+\z`)
|
var issueRegexp = regexp.MustCompile(`\Ahttps://github\.com/tailscale/[a-zA-Z0-9_.-]+/issues/\d+\z`)
|
||||||
|
|
||||||
|
@ -34,6 +30,16 @@ func Mark(t testing.TB, issue string) {
|
||||||
t.Fatalf("bad issue format: %q", issue)
|
t.Fatalf("bad issue format: %q", issue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, FlakyTestLogMessage) // sentinel value for testwrapper
|
if !InTestWrapper() {
|
||||||
t.Logf("flakytest: issue tracking this flaky test: %s", issue)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if t.Failed() {
|
||||||
|
t.Logf("flakytest: signaling test wrapper to retry test")
|
||||||
|
|
||||||
|
// Signal to test wrapper that we should restart.
|
||||||
|
os.Exit(123)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,7 @@
|
||||||
|
|
||||||
package flakytest
|
package flakytest
|
||||||
|
|
||||||
import (
|
import "testing"
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIssueFormat(t *testing.T) {
|
func TestIssueFormat(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
@ -27,17 +24,3 @@ func TestIssueFormat(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFlakeRun is a test that fails when run in the testwrapper
|
|
||||||
// for the first time, but succeeds on the second run.
|
|
||||||
// It's used to test whether the testwrapper retries flaky tests.
|
|
||||||
func TestFlakeRun(t *testing.T) {
|
|
||||||
Mark(t, "https://github.com/tailscale/tailscale/issues/0") // random issue
|
|
||||||
e := os.Getenv(FlakeAttemptEnv)
|
|
||||||
if e == "" {
|
|
||||||
t.Skip("not running in testwrapper")
|
|
||||||
}
|
|
||||||
if e == "1" {
|
|
||||||
t.Fatal("First run in testwrapper, failing so that test is retried. This is expected.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,278 +1,62 @@
|
||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
// testwrapper is a wrapper for retrying flaky tests. It is an alternative to
|
// testwrapper is a wrapper for retrying flaky tests, using the -exec flag of
|
||||||
// `go test` and re-runs failed marked flaky tests (using the flakytest pkg). It
|
// 'go test'. Tests that are flaky can use the 'flakytest' subpackage to mark
|
||||||
// takes different arguments than go test and requires the first positional
|
// themselves as flaky and be retried on failure.
|
||||||
// argument to be the pattern to test.
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
"tailscale.com/cmd/testwrapper/flakytest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxAttempts = 3
|
const (
|
||||||
|
retryStatus = 123
|
||||||
type testAttempt struct {
|
maxIterations = 3
|
||||||
name testName
|
)
|
||||||
outcome string // "pass", "fail", "skip"
|
|
||||||
logs bytes.Buffer
|
|
||||||
isMarkedFlaky bool // set if the test is marked as flaky
|
|
||||||
|
|
||||||
pkgFinished bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type testName struct {
|
|
||||||
pkg string // "tailscale.com/types/key"
|
|
||||||
name string // "TestFoo"
|
|
||||||
}
|
|
||||||
|
|
||||||
type packageTests struct {
|
|
||||||
// pattern is the package pattern to run.
|
|
||||||
// Must be a single pattern, not a list of patterns.
|
|
||||||
pattern string // "./...", "./types/key"
|
|
||||||
// tests is a list of tests to run. If empty, all tests in the package are
|
|
||||||
// run.
|
|
||||||
tests []string // ["TestFoo", "TestBar"]
|
|
||||||
}
|
|
||||||
|
|
||||||
type goTestOutput struct {
|
|
||||||
Time time.Time
|
|
||||||
Action string
|
|
||||||
Package string
|
|
||||||
Test string
|
|
||||||
Output string
|
|
||||||
}
|
|
||||||
|
|
||||||
var debug = os.Getenv("TS_TESTWRAPPER_DEBUG") != ""
|
|
||||||
|
|
||||||
// runTests runs the tests in pt and sends the results on ch. It sends a
|
|
||||||
// testAttempt for each test and a final testAttempt per pkg with pkgFinished
|
|
||||||
// set to true.
|
|
||||||
// It calls close(ch) when it's done.
|
|
||||||
func runTests(ctx context.Context, attempt int, pt *packageTests, otherArgs []string, ch chan<- *testAttempt) {
|
|
||||||
defer close(ch)
|
|
||||||
args := []string{"test", "-json", pt.pattern}
|
|
||||||
args = append(args, otherArgs...)
|
|
||||||
if len(pt.tests) > 0 {
|
|
||||||
runArg := strings.Join(pt.tests, "|")
|
|
||||||
args = append(args, "-run", runArg)
|
|
||||||
}
|
|
||||||
if debug {
|
|
||||||
fmt.Println("running", strings.Join(args, " "))
|
|
||||||
}
|
|
||||||
cmd := exec.CommandContext(ctx, "go", args...)
|
|
||||||
r, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error creating stdout pipe: %v", err)
|
|
||||||
}
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
cmd.Env = os.Environ()
|
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", flakytest.FlakeAttemptEnv, attempt))
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
log.Printf("error starting test: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
defer close(done)
|
|
||||||
cmd.Wait()
|
|
||||||
}()
|
|
||||||
|
|
||||||
jd := json.NewDecoder(r)
|
|
||||||
resultMap := make(map[testName]*testAttempt)
|
|
||||||
for {
|
|
||||||
var goOutput goTestOutput
|
|
||||||
if err := jd.Decode(&goOutput); err != nil {
|
|
||||||
if errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if goOutput.Test == "" {
|
|
||||||
switch goOutput.Action {
|
|
||||||
case "fail", "pass", "skip":
|
|
||||||
ch <- &testAttempt{
|
|
||||||
name: testName{
|
|
||||||
pkg: goOutput.Package,
|
|
||||||
},
|
|
||||||
outcome: goOutput.Action,
|
|
||||||
pkgFinished: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := testName{
|
|
||||||
pkg: goOutput.Package,
|
|
||||||
name: goOutput.Test,
|
|
||||||
}
|
|
||||||
if test, _, isSubtest := strings.Cut(goOutput.Test, "/"); isSubtest {
|
|
||||||
name.name = test
|
|
||||||
if goOutput.Action == "output" {
|
|
||||||
resultMap[name].logs.WriteString(goOutput.Output)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch goOutput.Action {
|
|
||||||
case "start":
|
|
||||||
// ignore
|
|
||||||
case "run":
|
|
||||||
resultMap[name] = &testAttempt{
|
|
||||||
name: name,
|
|
||||||
}
|
|
||||||
case "skip", "pass", "fail":
|
|
||||||
resultMap[name].outcome = goOutput.Action
|
|
||||||
ch <- resultMap[name]
|
|
||||||
case "output":
|
|
||||||
if strings.TrimSpace(goOutput.Output) == flakytest.FlakyTestLogMessage {
|
|
||||||
resultMap[name].isMarkedFlaky = true
|
|
||||||
} else {
|
|
||||||
resultMap[name].logs.WriteString(goOutput.Output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<-done
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
debug := os.Getenv("TS_TESTWRAPPER_DEBUG") != ""
|
||||||
|
|
||||||
// We only need to parse the -v flag to figure out whether to print the logs
|
log.SetPrefix("testwrapper: ")
|
||||||
// for a test. We don't need to parse any other flags, so we just use the
|
if !debug {
|
||||||
// flag package to parse the -v flag and then pass the rest of the args
|
log.SetFlags(0)
|
||||||
// through to 'go test'.
|
|
||||||
// We run `go test -json` which returns the same information as `go test -v`,
|
|
||||||
// but in a machine-readable format. So this flag is only for testwrapper's
|
|
||||||
// output.
|
|
||||||
v := flag.Bool("v", false, "verbose")
|
|
||||||
|
|
||||||
flag.Usage = func() {
|
|
||||||
fmt.Println("usage: testwrapper [testwrapper-flags] [pattern] [build/test flags & test binary flags]")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("testwrapper-flags:")
|
|
||||||
flag.CommandLine.PrintDefaults()
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("examples:")
|
|
||||||
fmt.Println("\ttestwrapper -v ./... -count=1")
|
|
||||||
fmt.Println("\ttestwrapper ./pkg/foo -run TestBar -count=1")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Unlike 'go test', testwrapper requires a package pattern as the first positional argument and only supports a single pattern.")
|
|
||||||
}
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
args := flag.Args()
|
|
||||||
if len(args) < 1 || strings.HasPrefix(args[0], "-") {
|
|
||||||
fmt.Println("no pattern specified")
|
|
||||||
flag.Usage()
|
|
||||||
os.Exit(1)
|
|
||||||
} else if len(args) > 1 && !strings.HasPrefix(args[1], "-") {
|
|
||||||
fmt.Println("expected single pattern")
|
|
||||||
flag.Usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
pattern, otherArgs := args[0], args[1:]
|
|
||||||
|
|
||||||
type nextRun struct {
|
|
||||||
tests []*packageTests
|
|
||||||
attempt int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toRun := []*nextRun{
|
for i := 1; i <= maxIterations; i++ {
|
||||||
{
|
if i > 1 {
|
||||||
tests: []*packageTests{{pattern: pattern}},
|
log.Printf("retrying flaky tests (%d of %d)", i, maxIterations)
|
||||||
attempt: 1,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
printPkgOutcome := func(pkg, outcome string, attempt int) {
|
cmd := exec.CommandContext(ctx, os.Args[1], os.Args[2:]...)
|
||||||
if outcome == "skip" {
|
cmd.Stdout = os.Stdout
|
||||||
fmt.Printf("?\t%s [skipped/no tests] \n", pkg)
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = append(os.Environ(), "TS_IN_TESTWRAPPER=1")
|
||||||
|
err := cmd.Run()
|
||||||
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if outcome == "pass" {
|
|
||||||
outcome = "ok"
|
|
||||||
}
|
|
||||||
if outcome == "fail" {
|
|
||||||
outcome = "FAIL"
|
|
||||||
}
|
|
||||||
if attempt > 1 {
|
|
||||||
fmt.Printf("%s\t%s [attempt=%d]\n", outcome, pkg, attempt)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("%s\t%s\n", outcome, pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(toRun) > 0 {
|
var exitErr *exec.ExitError
|
||||||
var thisRun *nextRun
|
if !errors.As(err, &exitErr) {
|
||||||
thisRun, toRun = toRun[0], toRun[1:]
|
if debug {
|
||||||
|
log.Printf("error isn't an ExitError")
|
||||||
if thisRun.attempt >= maxAttempts {
|
}
|
||||||
fmt.Println("max attempts reached")
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if thisRun.attempt > 1 {
|
|
||||||
fmt.Printf("\n\nAttempt #%d: Retrying flaky tests:\n\n", thisRun.attempt)
|
if code := exitErr.ExitCode(); code != retryStatus {
|
||||||
|
if debug {
|
||||||
|
log.Printf("code (%d) != retryStatus (%d)", code, retryStatus)
|
||||||
|
}
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
failed := false
|
log.Printf("test did not pass in %d iterations", maxIterations)
|
||||||
toRetry := make(map[string][]string) // pkg -> tests to retry
|
|
||||||
for _, pt := range thisRun.tests {
|
|
||||||
ch := make(chan *testAttempt)
|
|
||||||
go runTests(ctx, thisRun.attempt, pt, otherArgs, ch)
|
|
||||||
for tr := range ch {
|
|
||||||
if tr.pkgFinished {
|
|
||||||
printPkgOutcome(tr.name.pkg, tr.outcome, thisRun.attempt)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if *v || tr.outcome == "fail" {
|
|
||||||
io.Copy(os.Stdout, &tr.logs)
|
|
||||||
}
|
|
||||||
if tr.outcome != "fail" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if tr.isMarkedFlaky {
|
|
||||||
toRetry[tr.name.pkg] = append(toRetry[tr.name.pkg], tr.name.name)
|
|
||||||
} else {
|
|
||||||
failed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if failed {
|
|
||||||
fmt.Println("\n\nNot retrying flaky tests because non-flaky tests failed.")
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if len(toRetry) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pkgs := maps.Keys(toRetry)
|
|
||||||
sort.Strings(pkgs)
|
|
||||||
nextRun := &nextRun{
|
|
||||||
attempt: thisRun.attempt + 1,
|
|
||||||
}
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
tests := toRetry[pkg]
|
|
||||||
sort.Strings(tests)
|
|
||||||
nextRun.tests = append(nextRun.tests, &packageTests{
|
|
||||||
pattern: pkg,
|
|
||||||
tests: tests,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
toRun = append(toRun, nextRun)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ func dumpGoroutinesToURL(c *http.Client, targetURL string) {
|
||||||
|
|
||||||
zbuf := new(bytes.Buffer)
|
zbuf := new(bytes.Buffer)
|
||||||
zw := gzip.NewWriter(zbuf)
|
zw := gzip.NewWriter(zbuf)
|
||||||
zw.Write(goroutines.ScrubbedGoroutineDump(true))
|
zw.Write(goroutines.ScrubbedGoroutineDump())
|
||||||
zw.Close()
|
zw.Close()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "PUT", targetURL, zbuf)
|
req, err := http.NewRequestWithContext(ctx, "PUT", targetURL, zbuf)
|
||||||
|
|
|
@ -287,25 +287,6 @@ func (nc *NoiseClient) GetSingleUseRoundTripper(ctx context.Context) (http.Round
|
||||||
return nil, nil, errors.New("[unexpected] failed to reserve a request on a connection")
|
return nil, nil, errors.New("[unexpected] failed to reserve a request on a connection")
|
||||||
}
|
}
|
||||||
|
|
||||||
// contextErr is an error that wraps another error and is used to indicate that
|
|
||||||
// the error was because a context expired.
|
|
||||||
type contextErr struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e contextErr) Error() string {
|
|
||||||
return e.err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e contextErr) Unwrap() error {
|
|
||||||
return e.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// getConn returns a noiseConn that can be used to make requests to the
|
|
||||||
// coordination server. It may return a cached connection or create a new one.
|
|
||||||
// Dials are singleflighted, so concurrent calls to getConn may only dial once.
|
|
||||||
// As such, context values may not be respected as there are no guarantees that
|
|
||||||
// the context passed to getConn is the same as the context passed to dial.
|
|
||||||
func (nc *NoiseClient) getConn(ctx context.Context) (*noiseConn, error) {
|
func (nc *NoiseClient) getConn(ctx context.Context) (*noiseConn, error) {
|
||||||
nc.mu.Lock()
|
nc.mu.Lock()
|
||||||
if last := nc.last; last != nil && last.canTakeNewRequest() {
|
if last := nc.last; last != nil && last.canTakeNewRequest() {
|
||||||
|
@ -314,35 +295,11 @@ func (nc *NoiseClient) getConn(ctx context.Context) (*noiseConn, error) {
|
||||||
}
|
}
|
||||||
nc.mu.Unlock()
|
nc.mu.Unlock()
|
||||||
|
|
||||||
for {
|
conn, err, _ := nc.sfDial.Do(struct{}{}, nc.dial)
|
||||||
// We singeflight the dial to avoid making multiple connections, however
|
|
||||||
// that means that we can't simply cancel the dial if the context is
|
|
||||||
// canceled. Instead, we have to additionally check that the context
|
|
||||||
// which was canceled is our context and retry if our context is still
|
|
||||||
// valid.
|
|
||||||
conn, err, _ := nc.sfDial.Do(struct{}{}, func() (*noiseConn, error) {
|
|
||||||
c, err := nc.dial(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ctx.Err() != nil {
|
|
||||||
return nil, contextErr{ctx.Err()}
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return c, nil
|
return conn, nil
|
||||||
})
|
|
||||||
var ce contextErr
|
|
||||||
if err == nil || !errors.As(err, &ce) {
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
if ctx.Err() == nil {
|
|
||||||
// The dial failed because of a context error, but our context
|
|
||||||
// is still valid. Retry.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// The dial failed because our context was canceled. Return the
|
|
||||||
// underlying error.
|
|
||||||
return nil, ce.Unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *NoiseClient) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (nc *NoiseClient) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
@ -387,7 +344,7 @@ func (nc *NoiseClient) Close() error {
|
||||||
|
|
||||||
// dial opens a new connection to tailcontrol, fetching the server noise key
|
// dial opens a new connection to tailcontrol, fetching the server noise key
|
||||||
// if not cached.
|
// if not cached.
|
||||||
func (nc *NoiseClient) dial(ctx context.Context) (*noiseConn, error) {
|
func (nc *NoiseClient) dial() (*noiseConn, error) {
|
||||||
nc.mu.Lock()
|
nc.mu.Lock()
|
||||||
connID := nc.nextID
|
connID := nc.nextID
|
||||||
nc.nextID++
|
nc.nextID++
|
||||||
|
@ -435,7 +392,7 @@ func (nc *NoiseClient) dial(ctx context.Context) (*noiseConn, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout := time.Duration(timeoutSec * float64(time.Second))
|
timeout := time.Duration(timeoutSec * float64(time.Second))
|
||||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
clientConn, err := (&controlhttp.Dialer{
|
clientConn, err := (&controlhttp.Dialer{
|
||||||
|
|
|
@ -583,20 +583,19 @@ func TestDialPlan(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
want: goodAddr,
|
want: goodAddr,
|
||||||
},
|
},
|
||||||
// TODO(#8442): fix this test
|
{
|
||||||
// {
|
name: "multiple-priority-fast-path",
|
||||||
// name: "multiple-priority-fast-path",
|
plan: &tailcfg.ControlDialPlan{Candidates: []tailcfg.ControlIPCandidate{
|
||||||
// plan: &tailcfg.ControlDialPlan{Candidates: []tailcfg.ControlIPCandidate{
|
// Dials some good IPs and our bad one (which
|
||||||
// // Dials some good IPs and our bad one (which
|
// hangs forever), which then hits the fast
|
||||||
// // hangs forever), which then hits the fast
|
// path where we bail without waiting.
|
||||||
// // path where we bail without waiting.
|
{IP: brokenAddr, Priority: 1, DialTimeoutSec: 10},
|
||||||
// {IP: brokenAddr, Priority: 1, DialTimeoutSec: 10},
|
{IP: goodAddr, Priority: 1, DialTimeoutSec: 10},
|
||||||
// {IP: goodAddr, Priority: 1, DialTimeoutSec: 10},
|
{IP: other2Addr, Priority: 1, DialTimeoutSec: 10},
|
||||||
// {IP: other2Addr, Priority: 1, DialTimeoutSec: 10},
|
{IP: otherAddr, Priority: 2, DialTimeoutSec: 10},
|
||||||
// {IP: otherAddr, Priority: 2, DialTimeoutSec: 10},
|
}},
|
||||||
// }},
|
want: otherAddr,
|
||||||
// want: otherAddr,
|
},
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
name: "multiple-priority-slow-path",
|
name: "multiple-priority-slow-path",
|
||||||
plan: &tailcfg.ControlDialPlan{Candidates: []tailcfg.ControlIPCandidate{
|
plan: &tailcfg.ControlDialPlan{Candidates: []tailcfg.ControlIPCandidate{
|
||||||
|
|
|
@ -9,18 +9,19 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tailscale.com/net/tcpinfo"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *sclient) statsLoop(ctx context.Context) error {
|
func (c *sclient) statsLoop(ctx context.Context) error {
|
||||||
// Get the RTT initially to verify it's supported.
|
// If we can't get a TCP socket, then we can't send stats.
|
||||||
conn := c.tcpConn()
|
tcpConn := c.tcpConn()
|
||||||
if conn == nil {
|
if tcpConn == nil {
|
||||||
c.s.tcpRtt.Add("non-tcp", 1)
|
c.s.tcpRtt.Add("non-tcp", 1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if _, err := tcpinfo.RTT(conn); err != nil {
|
rawConn, err := tcpConn.SyscallConn()
|
||||||
c.logf("error fetching initial RTT: %v", err)
|
if err != nil {
|
||||||
|
c.logf("error getting SyscallConn: %v", err)
|
||||||
c.s.tcpRtt.Add("error", 1)
|
c.s.tcpRtt.Add("error", 1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -30,16 +31,23 @@ func (c *sclient) statsLoop(ctx context.Context) error {
|
||||||
ticker := time.NewTicker(statsInterval)
|
ticker := time.NewTicker(statsInterval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
var (
|
||||||
|
tcpInfo *unix.TCPInfo
|
||||||
|
sysErr error
|
||||||
|
)
|
||||||
statsLoop:
|
statsLoop:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
rtt, err := tcpinfo.RTT(conn)
|
err = rawConn.Control(func(fd uintptr) {
|
||||||
if err != nil {
|
tcpInfo, sysErr = unix.GetsockoptTCPInfo(int(fd), unix.IPPROTO_TCP, unix.TCP_INFO)
|
||||||
|
})
|
||||||
|
if err != nil || sysErr != nil {
|
||||||
continue statsLoop
|
continue statsLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(andrew): more metrics?
|
// TODO(andrew): more metrics?
|
||||||
|
rtt := time.Duration(tcpInfo.Rtt) * time.Microsecond
|
||||||
c.s.tcpRtt.Add(durationToLabel(rtt), 1)
|
c.s.tcpRtt.Add(durationToLabel(rtt), 1)
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|
|
@ -40,7 +40,6 @@ import (
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/cmpx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is a DERP-over-HTTP client.
|
// Client is a DERP-over-HTTP client.
|
||||||
|
@ -655,7 +654,10 @@ func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, e
|
||||||
// Start v4 dial
|
// Start v4 dial
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dst := cmpx.Or(dstPrimary, n.HostName)
|
dst := dstPrimary
|
||||||
|
if dst == "" {
|
||||||
|
dst = n.HostName
|
||||||
|
}
|
||||||
port := "443"
|
port := "443"
|
||||||
if n.DERPPort != 0 {
|
if n.DERPPort != 0 {
|
||||||
port = fmt.Sprint(n.DERPPort)
|
port = fmt.Sprint(n.DERPPort)
|
||||||
|
|
|
@ -6,20 +6,22 @@ SA_NAME ?= tailscale
|
||||||
TS_KUBE_SECRET ?= tailscale
|
TS_KUBE_SECRET ?= tailscale
|
||||||
|
|
||||||
rbac:
|
rbac:
|
||||||
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" role.yaml
|
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" role.yaml | kubectl apply -f -
|
||||||
@echo "---"
|
@sed -e "s;{{SA_NAME}};$(SA_NAME);g" rolebinding.yaml | kubectl apply -f -
|
||||||
@sed -e "s;{{SA_NAME}};$(SA_NAME);g" rolebinding.yaml
|
@sed -e "s;{{SA_NAME}};$(SA_NAME);g" sa.yaml | kubectl apply -f -
|
||||||
@echo "---"
|
|
||||||
@sed -e "s;{{SA_NAME}};$(SA_NAME);g" sa.yaml
|
|
||||||
|
|
||||||
sidecar:
|
sidecar:
|
||||||
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" sidecar.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g"
|
@kubectl delete -f sidecar.yaml --ignore-not-found --grace-period=0
|
||||||
|
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" sidecar.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | kubectl create -f-
|
||||||
|
|
||||||
userspace-sidecar:
|
userspace-sidecar:
|
||||||
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" userspace-sidecar.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g"
|
@kubectl delete -f userspace-sidecar.yaml --ignore-not-found --grace-period=0
|
||||||
|
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" userspace-sidecar.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | kubectl create -f-
|
||||||
|
|
||||||
proxy:
|
proxy:
|
||||||
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" proxy.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | sed -e "s;{{TS_DEST_IP}};$(TS_DEST_IP);g"
|
kubectl delete -f proxy.yaml --ignore-not-found --grace-period=0
|
||||||
|
sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" proxy.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | sed -e "s;{{TS_DEST_IP}};$(TS_DEST_IP);g" | kubectl create -f-
|
||||||
|
|
||||||
subnet-router:
|
subnet-router:
|
||||||
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" subnet.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | sed -e "s;{{TS_ROUTES}};$(TS_ROUTES);g"
|
@kubectl delete -f subnet.yaml --ignore-not-found --grace-period=0
|
||||||
|
@sed -e "s;{{TS_KUBE_SECRET}};$(TS_KUBE_SECRET);g" subnet.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | sed -e "s;{{TS_ROUTES}};$(TS_ROUTES);g" | kubectl create -f-
|
||||||
|
|
|
@ -26,7 +26,7 @@ There are quite a few ways of running Tailscale inside a Kubernetes Cluster, som
|
||||||
```bash
|
```bash
|
||||||
export SA_NAME=tailscale
|
export SA_NAME=tailscale
|
||||||
export TS_KUBE_SECRET=tailscale-auth
|
export TS_KUBE_SECRET=tailscale-auth
|
||||||
make rbac | kubectl apply -f-
|
make rbac
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sample Sidecar
|
### Sample Sidecar
|
||||||
|
@ -36,7 +36,7 @@ Running as a sidecar allows you to directly expose a Kubernetes pod over Tailsca
|
||||||
1. Create and login to the sample nginx pod with a Tailscale sidecar
|
1. Create and login to the sample nginx pod with a Tailscale sidecar
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make sidecar | kubectl apply -f-
|
make sidecar
|
||||||
# If not using an auth key, authenticate by grabbing the Login URL here:
|
# If not using an auth key, authenticate by grabbing the Login URL here:
|
||||||
kubectl logs nginx ts-sidecar
|
kubectl logs nginx ts-sidecar
|
||||||
```
|
```
|
||||||
|
@ -60,7 +60,7 @@ You can also run the sidecar in userspace mode. The obvious benefit is reducing
|
||||||
1. Create and login to the sample nginx pod with a Tailscale sidecar
|
1. Create and login to the sample nginx pod with a Tailscale sidecar
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make userspace-sidecar | kubectl apply -f-
|
make userspace-sidecar
|
||||||
# If not using an auth key, authenticate by grabbing the Login URL here:
|
# If not using an auth key, authenticate by grabbing the Login URL here:
|
||||||
kubectl logs nginx ts-sidecar
|
kubectl logs nginx ts-sidecar
|
||||||
```
|
```
|
||||||
|
@ -100,7 +100,7 @@ Running a Tailscale proxy allows you to provide inbound connectivity to a Kubern
|
||||||
1. Deploy the proxy pod
|
1. Deploy the proxy pod
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make proxy | kubectl apply -f-
|
make proxy
|
||||||
# If not using an auth key, authenticate by grabbing the Login URL here:
|
# If not using an auth key, authenticate by grabbing the Login URL here:
|
||||||
kubectl logs proxy
|
kubectl logs proxy
|
||||||
```
|
```
|
||||||
|
@ -133,7 +133,7 @@ the entire Kubernetes cluster network (assuming NetworkPolicies allow) over Tail
|
||||||
1. Deploy the subnet-router pod.
|
1. Deploy the subnet-router pod.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make subnet-router | kubectl apply -f-
|
make subnet-router
|
||||||
# If not using an auth key, authenticate by grabbing the Login URL here:
|
# If not using an auth key, authenticate by grabbing the Login URL here:
|
||||||
kubectl logs subnet-router
|
kubectl logs subnet-router
|
||||||
```
|
```
|
||||||
|
|
|
@ -115,4 +115,4 @@
|
||||||
in
|
in
|
||||||
flake-utils.lib.eachDefaultSystem (system: flakeForSystem nixpkgs system);
|
flake-utils.lib.eachDefaultSystem (system: flakeForSystem nixpkgs system);
|
||||||
}
|
}
|
||||||
# nix-direnv cache busting line: sha256-fgCrmtJs1svFz0Xn7iwLNrbBNlcO6V0yqGPMY0+V1VQ=
|
# nix-direnv cache busting line: sha256-7L+dvS++UNfMVcPUCbK/xuBPwtrzW4RpZTtcl7VCwQs=
|
||||||
|
|
26
go.mod
26
go.mod
|
@ -24,7 +24,7 @@ require (
|
||||||
github.com/frankban/quicktest v1.14.5
|
github.com/frankban/quicktest v1.14.5
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0
|
github.com/fxamacker/cbor/v2 v2.4.0
|
||||||
github.com/go-json-experiment/json v0.0.0-20230321051131-ccbac49a6929
|
github.com/go-json-experiment/json v0.0.0-20230321051131-ccbac49a6929
|
||||||
github.com/go-logr/zapr v1.2.4
|
github.com/go-logr/zapr v1.2.3
|
||||||
github.com/go-ole/go-ole v1.2.6
|
github.com/go-ole/go-ole v1.2.6
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
github.com/godbus/dbus/v5 v5.1.0
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
|
||||||
|
@ -48,7 +48,7 @@ require (
|
||||||
github.com/mdlayher/genetlink v1.3.2
|
github.com/mdlayher/genetlink v1.3.2
|
||||||
github.com/mdlayher/netlink v1.7.2
|
github.com/mdlayher/netlink v1.7.2
|
||||||
github.com/mdlayher/sdnotify v1.0.0
|
github.com/mdlayher/sdnotify v1.0.0
|
||||||
github.com/miekg/dns v1.1.55
|
github.com/miekg/dns v1.1.54
|
||||||
github.com/mitchellh/go-ps v1.0.0
|
github.com/mitchellh/go-ps v1.0.0
|
||||||
github.com/peterbourgon/ff/v3 v3.3.0
|
github.com/peterbourgon/ff/v3 v3.3.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
@ -76,13 +76,13 @@ require (
|
||||||
golang.org/x/crypto v0.8.0
|
golang.org/x/crypto v0.8.0
|
||||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
|
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
|
||||||
golang.org/x/mod v0.10.0
|
golang.org/x/mod v0.10.0
|
||||||
golang.org/x/net v0.10.0
|
golang.org/x/net v0.9.0
|
||||||
golang.org/x/oauth2 v0.7.0
|
golang.org/x/oauth2 v0.7.0
|
||||||
golang.org/x/sync v0.2.0
|
golang.org/x/sync v0.2.0
|
||||||
golang.org/x/sys v0.8.1-0.20230609144347-5059a07aa46a
|
golang.org/x/sys v0.8.0
|
||||||
golang.org/x/term v0.8.0
|
golang.org/x/term v0.7.0
|
||||||
golang.org/x/time v0.3.0
|
golang.org/x/time v0.3.0
|
||||||
golang.org/x/tools v0.9.1
|
golang.org/x/tools v0.8.0
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||||
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f
|
gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f
|
||||||
|
@ -90,11 +90,11 @@ require (
|
||||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
|
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
|
||||||
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626
|
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626
|
||||||
inet.af/wf v0.0.0-20221017222439-36129f591884
|
inet.af/wf v0.0.0-20221017222439-36129f591884
|
||||||
k8s.io/api v0.27.2
|
k8s.io/api v0.26.1
|
||||||
k8s.io/apimachinery v0.27.2
|
k8s.io/apimachinery v0.26.1
|
||||||
k8s.io/client-go v0.27.2
|
k8s.io/client-go v0.26.1
|
||||||
nhooyr.io/websocket v1.8.7
|
nhooyr.io/websocket v1.8.7
|
||||||
sigs.k8s.io/controller-runtime v0.15.0
|
sigs.k8s.io/controller-runtime v0.14.6
|
||||||
sigs.k8s.io/yaml v1.3.0
|
sigs.k8s.io/yaml v1.3.0
|
||||||
software.sslmate.com/src/go-pkcs12 v0.2.0
|
software.sslmate.com/src/go-pkcs12 v0.2.0
|
||||||
)
|
)
|
||||||
|
@ -334,7 +334,7 @@ require (
|
||||||
golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 // indirect
|
golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 // indirect
|
||||||
golang.org/x/image v0.7.0 // indirect
|
golang.org/x/image v0.7.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
|
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
@ -343,8 +343,8 @@ require (
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
howett.net/plist v1.0.0 // indirect
|
howett.net/plist v1.0.0 // indirect
|
||||||
k8s.io/apiextensions-apiserver v0.27.2 // indirect
|
k8s.io/apiextensions-apiserver v0.26.1 // indirect
|
||||||
k8s.io/component-base v0.27.2 // indirect
|
k8s.io/component-base v0.26.1 // indirect
|
||||||
k8s.io/klog/v2 v2.100.1 // indirect
|
k8s.io/klog/v2 v2.100.1 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
|
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
|
||||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
sha256-fgCrmtJs1svFz0Xn7iwLNrbBNlcO6V0yqGPMY0+V1VQ=
|
sha256-7L+dvS++UNfMVcPUCbK/xuBPwtrzW4RpZTtcl7VCwQs=
|
||||||
|
|
65
go.sum
65
go.sum
|
@ -274,6 +274,7 @@ github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStB
|
||||||
github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0=
|
github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0=
|
||||||
github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw=
|
github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw=
|
||||||
github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY=
|
github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY=
|
||||||
|
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
|
||||||
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
|
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
|
||||||
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
|
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
|
||||||
|
@ -338,10 +339,11 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
|
github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
|
||||||
github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
|
github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=
|
||||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
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-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
@ -360,7 +362,6 @@ github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1
|
||||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
|
||||||
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
||||||
github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=
|
github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=
|
||||||
github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=
|
github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=
|
||||||
|
@ -513,7 +514,6 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
|
||||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/rpmpack v0.0.0-20201206194719-59e495f2b7e1/go.mod h1:+y9lKiqDhR4zkLl+V9h4q0rdyrYVsWWm6LLCQP33DIk=
|
github.com/google/rpmpack v0.0.0-20201206194719-59e495f2b7e1/go.mod h1:+y9lKiqDhR4zkLl+V9h4q0rdyrYVsWWm6LLCQP33DIk=
|
||||||
github.com/google/rpmpack v0.0.0-20221120200012-98b63d62fd77 h1:+C0+foB1Bm0WYdbaDIuUGEVG1Eqx9WWcGUoJBSLdZo0=
|
github.com/google/rpmpack v0.0.0-20221120200012-98b63d62fd77 h1:+C0+foB1Bm0WYdbaDIuUGEVG1Eqx9WWcGUoJBSLdZo0=
|
||||||
|
@ -767,8 +767,8 @@ github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8Ku
|
||||||
github.com/mgechev/revive v1.3.1 h1:OlQkcH40IB2cGuprTPcjB0iIUddgVZgGmDX3IAMR8D4=
|
github.com/mgechev/revive v1.3.1 h1:OlQkcH40IB2cGuprTPcjB0iIUddgVZgGmDX3IAMR8D4=
|
||||||
github.com/mgechev/revive v1.3.1/go.mod h1:YlD6TTWl2B8A103R9KWJSPVI9DrEf+oqr15q21Ld+5I=
|
github.com/mgechev/revive v1.3.1/go.mod h1:YlD6TTWl2B8A103R9KWJSPVI9DrEf+oqr15q21Ld+5I=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
|
||||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
|
@ -831,11 +831,11 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4=
|
github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4=
|
||||||
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
|
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
@ -1172,13 +1172,13 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8=
|
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8=
|
||||||
|
@ -1316,8 +1316,8 @@ golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -1432,8 +1432,8 @@ golang.org/x/sys v0.4.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.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.1-0.20230609144347-5059a07aa46a h1:qMsju+PNttu/NMbq8bQ9waDdxgJMu9QNoUDuhnBaYt0=
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
golang.org/x/sys v0.8.1-0.20230609144347-5059a07aa46a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
@ -1444,8 +1444,8 @@ golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -1566,8 +1566,8 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||||
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
||||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -1576,8 +1576,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
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 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||||
gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc=
|
gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY=
|
||||||
gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
@ -1709,6 +1709,7 @@ 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.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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -1733,16 +1734,16 @@ inet.af/tcpproxy v0.0.0-20221017015627-91f861402626 h1:2dMP3Ox/Wh5BiItwOt4jxRsfz
|
||||||
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626/go.mod h1:Tojt5kmHpDIR2jMojxzZK2w2ZR7OILODmUo2gaSwjrk=
|
inet.af/tcpproxy v0.0.0-20221017015627-91f861402626/go.mod h1:Tojt5kmHpDIR2jMojxzZK2w2ZR7OILODmUo2gaSwjrk=
|
||||||
inet.af/wf v0.0.0-20221017222439-36129f591884 h1:zg9snq3Cpy50lWuVqDYM7AIRVTtU50y5WXETMFohW/Q=
|
inet.af/wf v0.0.0-20221017222439-36129f591884 h1:zg9snq3Cpy50lWuVqDYM7AIRVTtU50y5WXETMFohW/Q=
|
||||||
inet.af/wf v0.0.0-20221017222439-36129f591884/go.mod h1:bSAQ38BYbY68uwpasXOTZo22dKGy9SNvI6PZFeKomZE=
|
inet.af/wf v0.0.0-20221017222439-36129f591884/go.mod h1:bSAQ38BYbY68uwpasXOTZo22dKGy9SNvI6PZFeKomZE=
|
||||||
k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo=
|
k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
|
||||||
k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4=
|
k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=
|
||||||
k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo=
|
k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI=
|
||||||
k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ=
|
k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM=
|
||||||
k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg=
|
k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ=
|
||||||
k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
|
k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=
|
||||||
k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE=
|
k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU=
|
||||||
k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ=
|
k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE=
|
||||||
k8s.io/component-base v0.27.2 h1:neju+7s/r5O4x4/txeUONNTS9r1HsPbyoPBAtHsDCpo=
|
k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4=
|
||||||
k8s.io/component-base v0.27.2/go.mod h1:5UPk7EjfgrfgRIuDBFtsEFAe4DAvP3U+M8RTzoSJkpo=
|
k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU=
|
||||||
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
|
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
|
||||||
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||||
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg=
|
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg=
|
||||||
|
@ -1766,8 +1767,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU=
|
sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA=
|
||||||
sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk=
|
sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0=
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
tailscale.go1.21
|
tailscale.go1.20
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
492f6d9d792fa6e4caa388e4d7bab46b48d07ad5
|
ddff070c02790cb571006e820e58cce9627569cf
|
||||||
|
|
|
@ -7,10 +7,8 @@ package hostinfo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -283,7 +281,7 @@ func inContainer() opt.Bool {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
lineread.File("/proc/mounts", func(line []byte) error {
|
lineread.File("/proc/mounts", func(line []byte) error {
|
||||||
if mem.Contains(mem.B(line), mem.S("lxcfs /proc/cpuinfo fuse.lxcfs")) {
|
if mem.Contains(mem.B(line), mem.S("fuse.lxcfs")) {
|
||||||
ret.Set(true)
|
ret.Set(true)
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
|
@ -436,12 +434,3 @@ func etcAptSourceFileIsDisabled(r io.Reader) bool {
|
||||||
}
|
}
|
||||||
return disabled
|
return disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSELinuxEnforcing reports whether SELinux is in "Enforcing" mode.
|
|
||||||
func IsSELinuxEnforcing() bool {
|
|
||||||
if runtime.GOOS != "linux" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
out, _ := exec.Command("getenforce").Output()
|
|
||||||
return string(bytes.TrimSpace(out)) == "Enforcing"
|
|
||||||
}
|
|
||||||
|
|
|
@ -103,7 +103,6 @@ func (src *TCPPortHandler) Clone() *TCPPortHandler {
|
||||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
var _TCPPortHandlerCloneNeedsRegeneration = TCPPortHandler(struct {
|
var _TCPPortHandlerCloneNeedsRegeneration = TCPPortHandler(struct {
|
||||||
HTTPS bool
|
HTTPS bool
|
||||||
HTTP bool
|
|
||||||
TCPForward string
|
TCPForward string
|
||||||
TerminateTLS string
|
TerminateTLS string
|
||||||
}{})
|
}{})
|
||||||
|
|
|
@ -228,14 +228,12 @@ func (v *TCPPortHandlerView) UnmarshalJSON(b []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v TCPPortHandlerView) HTTPS() bool { return v.ж.HTTPS }
|
func (v TCPPortHandlerView) HTTPS() bool { return v.ж.HTTPS }
|
||||||
func (v TCPPortHandlerView) HTTP() bool { return v.ж.HTTP }
|
|
||||||
func (v TCPPortHandlerView) TCPForward() string { return v.ж.TCPForward }
|
func (v TCPPortHandlerView) TCPForward() string { return v.ж.TCPForward }
|
||||||
func (v TCPPortHandlerView) TerminateTLS() string { return v.ж.TerminateTLS }
|
func (v TCPPortHandlerView) TerminateTLS() string { return v.ж.TerminateTLS }
|
||||||
|
|
||||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
var _TCPPortHandlerViewNeedsRegeneration = TCPPortHandler(struct {
|
var _TCPPortHandlerViewNeedsRegeneration = TCPPortHandler(struct {
|
||||||
HTTPS bool
|
HTTPS bool
|
||||||
HTTP bool
|
|
||||||
TCPForward string
|
TCPForward string
|
||||||
TerminateTLS string
|
TerminateTLS string
|
||||||
}{})
|
}{})
|
||||||
|
|
|
@ -49,7 +49,7 @@ func (b *LocalBackend) handleC2N(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
case "/debug/goroutines":
|
case "/debug/goroutines":
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
w.Write(goroutines.ScrubbedGoroutineDump(true))
|
w.Write(goroutines.ScrubbedGoroutineDump())
|
||||||
case "/debug/prefs":
|
case "/debug/prefs":
|
||||||
writeJSON(b.Prefs())
|
writeJSON(b.Prefs())
|
||||||
case "/debug/metrics":
|
case "/debug/metrics":
|
||||||
|
|
|
@ -101,13 +101,11 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK
|
||||||
}
|
}
|
||||||
|
|
||||||
if pair, err := getCertPEMCached(cs, domain, now); err == nil {
|
if pair, err := getCertPEMCached(cs, domain, now); err == nil {
|
||||||
shouldRenew, err := shouldStartDomainRenewal(domain, now, pair)
|
future := now.AddDate(0, 0, 14)
|
||||||
if err != nil {
|
if b.shouldStartDomainRenewal(cs, domain, future) {
|
||||||
logf("error checking for certificate renewal: %v", err)
|
|
||||||
} else if shouldRenew {
|
|
||||||
logf("starting async renewal")
|
logf("starting async renewal")
|
||||||
// Start renewal in the background.
|
// Start renewal in the background.
|
||||||
go b.getCertPEM(context.Background(), cs, logf, traceACME, domain, now)
|
go b.getCertPEM(context.Background(), cs, logf, traceACME, domain, future)
|
||||||
}
|
}
|
||||||
return pair, nil
|
return pair, nil
|
||||||
}
|
}
|
||||||
|
@ -120,41 +118,18 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK
|
||||||
return pair, nil
|
return pair, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldStartDomainRenewal(domain string, now time.Time, pair *TLSCertKeyPair) (bool, error) {
|
func (b *LocalBackend) shouldStartDomainRenewal(cs certStore, domain string, future time.Time) bool {
|
||||||
renewMu.Lock()
|
renewMu.Lock()
|
||||||
defer renewMu.Unlock()
|
defer renewMu.Unlock()
|
||||||
|
now := time.Now()
|
||||||
if last, ok := lastRenewCheck[domain]; ok && now.Sub(last) < time.Minute {
|
if last, ok := lastRenewCheck[domain]; ok && now.Sub(last) < time.Minute {
|
||||||
// We checked very recently. Don't bother reparsing &
|
// We checked very recently. Don't bother reparsing &
|
||||||
// validating the x509 cert.
|
// validating the x509 cert.
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
lastRenewCheck[domain] = now
|
lastRenewCheck[domain] = now
|
||||||
|
_, err := getCertPEMCached(cs, domain, future)
|
||||||
block, _ := pem.Decode(pair.CertPEM)
|
return errors.Is(err, errCertExpired)
|
||||||
if block == nil {
|
|
||||||
return false, fmt.Errorf("parsing certificate PEM")
|
|
||||||
}
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("parsing certificate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certLifetime := cert.NotAfter.Sub(cert.NotBefore)
|
|
||||||
if certLifetime < 0 {
|
|
||||||
return false, fmt.Errorf("negative certificate lifetime %v", certLifetime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Per https://github.com/tailscale/tailscale/issues/8204, check
|
|
||||||
// whether we're more than 2/3 of the way through the certificate's
|
|
||||||
// lifetime, which is the officially-recommended best practice by Let's
|
|
||||||
// Encrypt.
|
|
||||||
renewalDuration := certLifetime * 2 / 3
|
|
||||||
renewAt := cert.NotBefore.Add(renewalDuration)
|
|
||||||
|
|
||||||
if now.After(renewAt) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// certStore provides a way to perist and retrieve TLS certificates.
|
// certStore provides a way to perist and retrieve TLS certificates.
|
||||||
|
|
|
@ -6,19 +6,12 @@
|
||||||
package ipnlocal
|
package ipnlocal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/pem"
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
"tailscale.com/ipn/store/mem"
|
"tailscale.com/ipn/store/mem"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -107,94 +100,3 @@ func TestCertStoreRoundTrip(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldStartDomainRenewal(t *testing.T) {
|
|
||||||
reset := func() {
|
|
||||||
renewMu.Lock()
|
|
||||||
defer renewMu.Unlock()
|
|
||||||
maps.Clear(lastRenewCheck)
|
|
||||||
}
|
|
||||||
|
|
||||||
mustMakePair := func(template *x509.Certificate) *TLSCertKeyPair {
|
|
||||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
certPEM := pem.EncodeToMemory(&pem.Block{
|
|
||||||
Type: "CERTIFICATE",
|
|
||||||
Bytes: b,
|
|
||||||
})
|
|
||||||
|
|
||||||
return &TLSCertKeyPair{
|
|
||||||
Cached: false,
|
|
||||||
CertPEM: certPEM,
|
|
||||||
KeyPEM: []byte("unused"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Unix(1685714838, 0)
|
|
||||||
subject := pkix.Name{
|
|
||||||
Organization: []string{"Tailscale, Inc."},
|
|
||||||
Country: []string{"CA"},
|
|
||||||
Province: []string{"ON"},
|
|
||||||
Locality: []string{"Toronto"},
|
|
||||||
StreetAddress: []string{"290 Bremner Blvd"},
|
|
||||||
PostalCode: []string{"M5V 3L9"},
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
notBefore time.Time
|
|
||||||
lifetime time.Duration
|
|
||||||
want bool
|
|
||||||
wantErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "should renew",
|
|
||||||
notBefore: now.AddDate(0, 0, -89),
|
|
||||||
lifetime: 90 * 24 * time.Hour,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "short-lived renewal",
|
|
||||||
notBefore: now.AddDate(0, 0, -7),
|
|
||||||
lifetime: 10 * 24 * time.Hour,
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no renew",
|
|
||||||
notBefore: now.AddDate(0, 0, -59), // 59 days ago == not 2/3rds of the way through 90 days yet
|
|
||||||
lifetime: 90 * 24 * time.Hour,
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range testCases {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
reset()
|
|
||||||
|
|
||||||
ret, err := shouldStartDomainRenewal("example.com", now, mustMakePair(&x509.Certificate{
|
|
||||||
SerialNumber: big.NewInt(2019),
|
|
||||||
Subject: subject,
|
|
||||||
NotBefore: tt.notBefore,
|
|
||||||
NotAfter: tt.notBefore.Add(tt.lifetime),
|
|
||||||
}))
|
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("wanted error, got nil")
|
|
||||||
} else if err.Error() != tt.wantErr {
|
|
||||||
t.Errorf("got err=%q, want %q", err.Error(), tt.wantErr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ret != tt.want {
|
|
||||||
t.Errorf("got ret=%v, want %v", ret, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"tailscale.com/types/dnstype"
|
"tailscale.com/types/dnstype"
|
||||||
"tailscale.com/types/netmap"
|
"tailscale.com/types/netmap"
|
||||||
"tailscale.com/util/cloudenv"
|
"tailscale.com/util/cloudenv"
|
||||||
"tailscale.com/util/cmpx"
|
|
||||||
"tailscale.com/util/dnsname"
|
"tailscale.com/util/dnsname"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -309,7 +308,10 @@ func TestDNSConfigForNetmap(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
verOS := cmpx.Or(tt.os, "linux")
|
verOS := tt.os
|
||||||
|
if verOS == "" {
|
||||||
|
verOS = "linux"
|
||||||
|
}
|
||||||
var log tstest.MemLogger
|
var log tstest.MemLogger
|
||||||
got := dnsConfigForNetmap(tt.nm, tt.prefs.View(), log.Logf, verOS)
|
got := dnsConfigForNetmap(tt.nm, tt.prefs.View(), log.Logf, verOS)
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package ipnlocal
|
package ipnlocal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -17,6 +18,7 @@ import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -30,7 +32,6 @@ import (
|
||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/control/controlclient"
|
"tailscale.com/control/controlclient"
|
||||||
"tailscale.com/doctor"
|
"tailscale.com/doctor"
|
||||||
|
@ -70,7 +71,6 @@ import (
|
||||||
"tailscale.com/types/preftype"
|
"tailscale.com/types/preftype"
|
||||||
"tailscale.com/types/ptr"
|
"tailscale.com/types/ptr"
|
||||||
"tailscale.com/types/views"
|
"tailscale.com/types/views"
|
||||||
"tailscale.com/util/cmpx"
|
|
||||||
"tailscale.com/util/deephash"
|
"tailscale.com/util/deephash"
|
||||||
"tailscale.com/util/dnsname"
|
"tailscale.com/util/dnsname"
|
||||||
"tailscale.com/util/mak"
|
"tailscale.com/util/mak"
|
||||||
|
@ -292,7 +292,10 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
|
||||||
osshare.SetFileSharingEnabled(false, logf)
|
osshare.SetFileSharingEnabled(false, logf)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
portpoll := new(portlist.Poller)
|
portpoll, err := portlist.NewPoller(portlist.PollerOptions{})
|
||||||
|
if err != nil {
|
||||||
|
logf("skipping portlist: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
b := &LocalBackend{
|
b := &LocalBackend{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
@ -742,6 +745,7 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
|
||||||
HostName: p.Hostinfo.Hostname(),
|
HostName: p.Hostinfo.Hostname(),
|
||||||
DNSName: p.Name,
|
DNSName: p.Name,
|
||||||
OS: p.Hostinfo.OS(),
|
OS: p.Hostinfo.OS(),
|
||||||
|
KeepAlive: p.KeepAlive,
|
||||||
LastSeen: lastSeen,
|
LastSeen: lastSeen,
|
||||||
Online: p.Online != nil && *p.Online,
|
Online: p.Online != nil && *p.Online,
|
||||||
ShareeNode: p.Hostinfo.ShareeNode(),
|
ShareeNode: p.Hostinfo.ShareeNode(),
|
||||||
|
@ -1373,6 +1377,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||||
|
|
||||||
if b.portpoll != nil {
|
if b.portpoll != nil {
|
||||||
b.portpollOnce.Do(func() {
|
b.portpollOnce.Do(func() {
|
||||||
|
go b.portpoll.Run(b.ctx)
|
||||||
go b.readPoller()
|
go b.readPoller()
|
||||||
|
|
||||||
// Give the poller a second to get results to
|
// Give the poller a second to get results to
|
||||||
|
@ -1807,30 +1812,11 @@ func dnsMapsEqual(new, old *netmap.NetworkMap) bool {
|
||||||
// readPoller is a goroutine that receives service lists from
|
// readPoller is a goroutine that receives service lists from
|
||||||
// b.portpoll and propagates them into the controlclient's HostInfo.
|
// b.portpoll and propagates them into the controlclient's HostInfo.
|
||||||
func (b *LocalBackend) readPoller() {
|
func (b *LocalBackend) readPoller() {
|
||||||
isFirst := true
|
n := 0
|
||||||
ticker := time.NewTicker(portlist.PollInterval())
|
|
||||||
defer ticker.Stop()
|
|
||||||
initChan := make(chan struct{})
|
|
||||||
close(initChan)
|
|
||||||
for {
|
for {
|
||||||
select {
|
ports, ok := <-b.portpoll.Updates()
|
||||||
case <-ticker.C:
|
if !ok {
|
||||||
case <-b.ctx.Done():
|
|
||||||
return
|
return
|
||||||
case <-initChan:
|
|
||||||
// Preserving old behavior: readPoller should
|
|
||||||
// immediately poll the first time, then wait
|
|
||||||
// for a tick after.
|
|
||||||
initChan = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ports, changed, err := b.portpoll.Poll()
|
|
||||||
if err != nil {
|
|
||||||
b.logf("error polling for open ports: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !changed {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
sl := []tailcfg.Service{}
|
sl := []tailcfg.Service{}
|
||||||
for _, p := range ports {
|
for _, p := range ports {
|
||||||
|
@ -1854,8 +1840,8 @@ func (b *LocalBackend) readPoller() {
|
||||||
|
|
||||||
b.doSetHostinfoFilterServices(hi)
|
b.doSetHostinfoFilterServices(hi)
|
||||||
|
|
||||||
if isFirst {
|
n++
|
||||||
isFirst = false
|
if n == 1 {
|
||||||
close(b.gotPortPollRes)
|
close(b.gotPortPollRes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2580,7 +2566,7 @@ func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
|
||||||
if distro.Get() == distro.QNAP && !envknob.UseWIPCode() {
|
if distro.Get() == distro.QNAP && !envknob.UseWIPCode() {
|
||||||
return errors.New("The Tailscale SSH server does not run on QNAP.")
|
return errors.New("The Tailscale SSH server does not run on QNAP.")
|
||||||
}
|
}
|
||||||
b.updateSELinuxHealthWarning()
|
checkSELinux()
|
||||||
// otherwise okay
|
// otherwise okay
|
||||||
case "darwin":
|
case "darwin":
|
||||||
// okay only in tailscaled mode for now.
|
// okay only in tailscaled mode for now.
|
||||||
|
@ -2826,14 +2812,14 @@ func (b *LocalBackend) GetPeerAPIPort(ip netip.Addr) (port uint16, ok bool) {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePeerAPIConn serves an already-accepted connection c.
|
// ServePeerAPIConnection serves an already-accepted connection c.
|
||||||
//
|
//
|
||||||
// The remote parameter is the remote address.
|
// The remote parameter is the remote address.
|
||||||
// The local parameter is the local address (either a Tailscale IPv4
|
// The local parameter is the local address (either a Tailscale IPv4
|
||||||
// or IPv6 IP and the peerapi port for that address).
|
// or IPv6 IP and the peerapi port for that address).
|
||||||
//
|
//
|
||||||
// The connection will be closed by handlePeerAPIConn.
|
// The connection will be closed by ServePeerAPIConnection.
|
||||||
func (b *LocalBackend) handlePeerAPIConn(remote, local netip.AddrPort, c net.Conn) {
|
func (b *LocalBackend) ServePeerAPIConnection(remote, local netip.AddrPort, c net.Conn) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
for _, pln := range b.peerAPIListeners {
|
for _, pln := range b.peerAPIListeners {
|
||||||
|
@ -2847,48 +2833,6 @@ func (b *LocalBackend) handlePeerAPIConn(remote, local netip.AddrPort, c net.Con
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) isLocalIP(ip netip.Addr) bool {
|
|
||||||
nm := b.NetMap()
|
|
||||||
return nm != nil && slices.Contains(nm.Addresses, netip.PrefixFrom(ip, ip.BitLen()))
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
magicDNSIP = tsaddr.TailscaleServiceIP()
|
|
||||||
magicDNSIPv6 = tsaddr.TailscaleServiceIPv6()
|
|
||||||
)
|
|
||||||
|
|
||||||
// TCPHandlerForDst returns a TCP handler for connections to dst, or nil if
|
|
||||||
// no handler is needed. It also returns a list of TCP socket options to
|
|
||||||
// apply to the socket before calling the handler.
|
|
||||||
func (b *LocalBackend) TCPHandlerForDst(src, dst netip.AddrPort) (handler func(c net.Conn) error, opts []tcpip.SettableSocketOption) {
|
|
||||||
if dst.Port() == 80 && (dst.Addr() == magicDNSIP || dst.Addr() == magicDNSIPv6) {
|
|
||||||
return b.HandleQuad100Port80Conn, opts
|
|
||||||
}
|
|
||||||
if !b.isLocalIP(dst.Addr()) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if dst.Port() == 22 && b.ShouldRunSSH() {
|
|
||||||
// Use a higher keepalive idle time for SSH connections, as they are
|
|
||||||
// typically long lived and idle connections are more likely to be
|
|
||||||
// intentional. Ideally we would turn this off entirely, but we can't
|
|
||||||
// tell the difference between a long lived connection that is idle
|
|
||||||
// vs a connection that is dead because the peer has gone away.
|
|
||||||
// We pick 72h as that is typically sufficient for a long weekend.
|
|
||||||
opts = append(opts, ptr.To(tcpip.KeepaliveIdleOption(72*time.Hour)))
|
|
||||||
return b.handleSSHConn, opts
|
|
||||||
}
|
|
||||||
if port, ok := b.GetPeerAPIPort(dst.Addr()); ok && dst.Port() == port {
|
|
||||||
return func(c net.Conn) error {
|
|
||||||
b.handlePeerAPIConn(src, dst, c)
|
|
||||||
return nil
|
|
||||||
}, opts
|
|
||||||
}
|
|
||||||
if handler := b.tcpHandlerForServe(dst.Port(), src); handler != nil {
|
|
||||||
return handler, opts
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *LocalBackend) peerAPIServicesLocked() (ret []tailcfg.Service) {
|
func (b *LocalBackend) peerAPIServicesLocked() (ret []tailcfg.Service) {
|
||||||
for _, pln := range b.peerAPIListeners {
|
for _, pln := range b.peerAPIListeners {
|
||||||
proto := tailcfg.PeerAPI4
|
proto := tailcfg.PeerAPI4
|
||||||
|
@ -3973,7 +3917,10 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
|
||||||
b.dialer.SetNetMap(nm)
|
b.dialer.SetNetMap(nm)
|
||||||
var login string
|
var login string
|
||||||
if nm != nil {
|
if nm != nil {
|
||||||
login = cmpx.Or(nm.UserProfiles[nm.User].LoginName, "<missing-profile>")
|
login = nm.UserProfiles[nm.User].LoginName
|
||||||
|
if login == "" {
|
||||||
|
login = "<missing-profile>"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
b.netMap = nm
|
b.netMap = nm
|
||||||
if login != b.activeLogin {
|
if login != b.activeLogin {
|
||||||
|
@ -4128,10 +4075,6 @@ func (b *LocalBackend) setServeProxyHandlersLocked() {
|
||||||
b.serveConfig.Web().Range(func(_ ipn.HostPort, conf ipn.WebServerConfigView) (cont bool) {
|
b.serveConfig.Web().Range(func(_ ipn.HostPort, conf ipn.WebServerConfigView) (cont bool) {
|
||||||
conf.Handlers().Range(func(_ string, h ipn.HTTPHandlerView) (cont bool) {
|
conf.Handlers().Range(func(_ string, h ipn.HTTPHandlerView) (cont bool) {
|
||||||
backend := h.Proxy()
|
backend := h.Proxy()
|
||||||
if backend == "" {
|
|
||||||
// Only create proxy handlers for servers with a proxy backend.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
mak.Set(&backends, backend, true)
|
mak.Set(&backends, backend, true)
|
||||||
if _, ok := b.serveProxyHandlers.Load(backend); ok {
|
if _, ok := b.serveProxyHandlers.Load(backend); ok {
|
||||||
return true
|
return true
|
||||||
|
@ -4706,29 +4649,33 @@ func (b *LocalBackend) sshServerOrInit() (_ SSHServer, err error) {
|
||||||
|
|
||||||
var warnSSHSELinux = health.NewWarnable()
|
var warnSSHSELinux = health.NewWarnable()
|
||||||
|
|
||||||
func (b *LocalBackend) updateSELinuxHealthWarning() {
|
func checkSELinux() {
|
||||||
if hostinfo.IsSELinuxEnforcing() {
|
if runtime.GOOS != "linux" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out, _ := exec.Command("getenforce").Output()
|
||||||
|
if string(bytes.TrimSpace(out)) == "Enforcing" {
|
||||||
warnSSHSELinux.Set(errors.New("SELinux is enabled; Tailscale SSH may not work. See https://tailscale.com/s/ssh-selinux"))
|
warnSSHSELinux.Set(errors.New("SELinux is enabled; Tailscale SSH may not work. See https://tailscale.com/s/ssh-selinux"))
|
||||||
} else {
|
} else {
|
||||||
warnSSHSELinux.Set(nil)
|
warnSSHSELinux.Set(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) handleSSHConn(c net.Conn) (err error) {
|
func (b *LocalBackend) HandleSSHConn(c net.Conn) (err error) {
|
||||||
s, err := b.sshServerOrInit()
|
s, err := b.sshServerOrInit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.updateSELinuxHealthWarning()
|
checkSELinux()
|
||||||
return s.HandleSSHConn(c)
|
return s.HandleSSHConn(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleQuad100Port80Conn serves http://100.100.100.100/ on port 80 (and
|
// HandleQuad100Port80Conn serves http://100.100.100.100/ on port 80 (and
|
||||||
// the equivalent tsaddr.TailscaleServiceIPv6 address).
|
// the equivalent tsaddr.TailscaleServiceIPv6 address).
|
||||||
func (b *LocalBackend) HandleQuad100Port80Conn(c net.Conn) error {
|
func (b *LocalBackend) HandleQuad100Port80Conn(c net.Conn) {
|
||||||
var s http.Server
|
var s http.Server
|
||||||
s.Handler = http.HandlerFunc(b.handleQuad100Port80Conn)
|
s.Handler = http.HandlerFunc(b.handleQuad100Port80Conn)
|
||||||
return s.Serve(netutil.NewOneConnListener(c, nil))
|
s.Serve(netutil.NewOneConnListener(c, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func validQuad100Host(h string) bool {
|
func validQuad100Host(h string) bool {
|
||||||
|
|
|
@ -158,9 +158,7 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.tka != nil || nm.TKAEnabled {
|
|
||||||
b.logf("tkaSyncIfNeeded: enabled=%v, head=%v", nm.TKAEnabled, nm.TKAHead)
|
b.logf("tkaSyncIfNeeded: enabled=%v, head=%v", nm.TKAEnabled, nm.TKAHead)
|
||||||
}
|
|
||||||
|
|
||||||
ourNodeKey := prefs.Persist().PublicNodeKey()
|
ourNodeKey := prefs.Persist().PublicNodeKey()
|
||||||
|
|
||||||
|
@ -199,7 +197,7 @@ func (b *LocalBackend) tkaSyncIfNeeded(nm *netmap.NetworkMap, prefs ipn.PrefsVie
|
||||||
health.SetTKAHealth(nil)
|
health.SetTKAHealth(nil)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("[bug] unreachable invariant of wantEnabled w/ isEnabled")
|
return fmt.Errorf("[bug] unreachable invariant of wantEnabled /w isEnabled")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,8 +449,6 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
|
||||||
filtered[i] = b.tka.filtered[i].Clone()
|
filtered[i] = b.tka.filtered[i].Clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
stateID1, _ := b.tka.authority.StateIDs()
|
|
||||||
|
|
||||||
return &ipnstate.NetworkLockStatus{
|
return &ipnstate.NetworkLockStatus{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Head: &head,
|
Head: &head,
|
||||||
|
@ -461,7 +457,6 @@ func (b *LocalBackend) NetworkLockStatus() *ipnstate.NetworkLockStatus {
|
||||||
NodeKeySigned: selfAuthorized,
|
NodeKeySigned: selfAuthorized,
|
||||||
TrustedKeys: outKeys,
|
TrustedKeys: outKeys,
|
||||||
FilteredPeers: filtered,
|
FilteredPeers: filtered,
|
||||||
StateID: stateID1,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -889,18 +884,6 @@ func (b *LocalBackend) NetworkLockWrapPreauthKey(preauthKey string, tkaKey key.N
|
||||||
return fmt.Sprintf("%s--TL%s-%s", preauthKey, tkaSuffixEncoder.EncodeToString(sig.Serialize()), tkaSuffixEncoder.EncodeToString(priv)), nil
|
return fmt.Sprintf("%s--TL%s-%s", preauthKey, tkaSuffixEncoder.EncodeToString(sig.Serialize()), tkaSuffixEncoder.EncodeToString(priv)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkLockVerifySigningDeeplink asks the authority to verify the given deeplink
|
|
||||||
// URL. See the comment for ValidateDeeplink for details.
|
|
||||||
func (b *LocalBackend) NetworkLockVerifySigningDeeplink(url string) tka.DeeplinkValidationResult {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
if b.tka == nil {
|
|
||||||
return tka.DeeplinkValidationResult{IsValid: false, Error: errNetworkLockNotActive.Error()}
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.tka.authority.ValidateDeeplink(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func signNodeKey(nodeInfo tailcfg.TKASignInfo, signer key.NLPrivate) (*tka.NodeKeySignature, error) {
|
func signNodeKey(nodeInfo tailcfg.TKASignInfo, signer key.NLPrivate) (*tka.NodeKeySignature, error) {
|
||||||
p, err := nodeInfo.NodePublic.MarshalBinary()
|
p, err := nodeInfo.NodePublic.MarshalBinary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -780,7 +780,7 @@ func (h *peerAPIHandler) handleServeIngress(w http.ResponseWriter, r *http.Reque
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
getConnOrReset := func() (net.Conn, bool) {
|
getConn := func() (net.Conn, bool) {
|
||||||
conn, _, err := w.(http.Hijacker).Hijack()
|
conn, _, err := w.(http.Hijacker).Hijack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logf("ingress: failed hijacking conn")
|
h.logf("ingress: failed hijacking conn")
|
||||||
|
@ -798,7 +798,7 @@ func (h *peerAPIHandler) handleServeIngress(w http.ResponseWriter, r *http.Reque
|
||||||
http.Error(w, "denied", http.StatusForbidden)
|
http.Error(w, "denied", http.StatusForbidden)
|
||||||
}
|
}
|
||||||
|
|
||||||
h.ps.b.HandleIngressTCPConn(h.peerNode, target, srcAddr, getConnOrReset, sendRST)
|
h.ps.b.HandleIngressTCPConn(h.peerNode, target, srcAddr, getConn, sendRST)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *peerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Request) {
|
func (h *peerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -24,8 +24,6 @@ import (
|
||||||
|
|
||||||
var errAlreadyMigrated = errors.New("profile migration already completed")
|
var errAlreadyMigrated = errors.New("profile migration already completed")
|
||||||
|
|
||||||
var debug = envknob.RegisterBool("TS_DEBUG_PROFILES")
|
|
||||||
|
|
||||||
// profileManager is a wrapper around a StateStore that manages
|
// profileManager is a wrapper around a StateStore that manages
|
||||||
// multiple profiles and the current profile.
|
// multiple profiles and the current profile.
|
||||||
type profileManager struct {
|
type profileManager struct {
|
||||||
|
@ -44,13 +42,6 @@ type profileManager struct {
|
||||||
isNewProfile bool
|
isNewProfile bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *profileManager) dlogf(format string, args ...any) {
|
|
||||||
if !debug() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pm.logf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentUserID returns the current user ID. It is only non-empty on
|
// CurrentUserID returns the current user ID. It is only non-empty on
|
||||||
// Windows where we have a multi-user system.
|
// Windows where we have a multi-user system.
|
||||||
func (pm *profileManager) CurrentUserID() ipn.WindowsUserID {
|
func (pm *profileManager) CurrentUserID() ipn.WindowsUserID {
|
||||||
|
@ -75,10 +66,8 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) error {
|
||||||
// Read the CurrentProfileKey from the store which stores
|
// Read the CurrentProfileKey from the store which stores
|
||||||
// the selected profile for the current user.
|
// the selected profile for the current user.
|
||||||
b, err := pm.store.ReadState(ipn.CurrentProfileKey(string(uid)))
|
b, err := pm.store.ReadState(ipn.CurrentProfileKey(string(uid)))
|
||||||
pm.dlogf("SetCurrentUserID: ReadState(%q) = %v, %v", string(uid), len(b), err)
|
|
||||||
if err == ipn.ErrStateNotExist || len(b) == 0 {
|
if err == ipn.ErrStateNotExist || len(b) == 0 {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
pm.dlogf("SetCurrentUserID: windows: migrating from legacy preferences")
|
|
||||||
if err := pm.migrateFromLegacyPrefs(); err != nil && !errors.Is(err, errAlreadyMigrated) {
|
if err := pm.migrateFromLegacyPrefs(); err != nil && !errors.Is(err, errAlreadyMigrated) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -92,7 +81,6 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) error {
|
||||||
pk := ipn.StateKey(string(b))
|
pk := ipn.StateKey(string(b))
|
||||||
prof := pm.findProfileByKey(pk)
|
prof := pm.findProfileByKey(pk)
|
||||||
if prof == nil {
|
if prof == nil {
|
||||||
pm.dlogf("SetCurrentUserID: no profile found for key: %q", pk)
|
|
||||||
pm.NewProfile()
|
pm.NewProfile()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -567,7 +555,6 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, goos stri
|
||||||
// and runtime must be valid Windows security identifier structures.
|
// and runtime must be valid Windows security identifier structures.
|
||||||
} else if len(knownProfiles) == 0 && goos != "windows" && runtime.GOOS != "windows" {
|
} else if len(knownProfiles) == 0 && goos != "windows" && runtime.GOOS != "windows" {
|
||||||
// No known profiles, try a migration.
|
// No known profiles, try a migration.
|
||||||
pm.dlogf("no known profiles; trying to migrate from legacy prefs")
|
|
||||||
if err := pm.migrateFromLegacyPrefs(); err != nil {
|
if err := pm.migrateFromLegacyPrefs(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -586,13 +573,11 @@ func (pm *profileManager) migrateFromLegacyPrefs() error {
|
||||||
metricMigrationError.Add(1)
|
metricMigrationError.Add(1)
|
||||||
return fmt.Errorf("load legacy prefs: %w", err)
|
return fmt.Errorf("load legacy prefs: %w", err)
|
||||||
}
|
}
|
||||||
pm.dlogf("loaded legacy preferences; sentinel=%q", sentinel)
|
|
||||||
if err := pm.SetPrefs(prefs); err != nil {
|
if err := pm.SetPrefs(prefs); err != nil {
|
||||||
metricMigrationError.Add(1)
|
metricMigrationError.Add(1)
|
||||||
return fmt.Errorf("migrating _daemon profile: %w", err)
|
return fmt.Errorf("migrating _daemon profile: %w", err)
|
||||||
}
|
}
|
||||||
pm.completeMigration(sentinel)
|
pm.completeMigration(sentinel)
|
||||||
pm.dlogf("completed legacy preferences migration with sentinel=%q", sentinel)
|
|
||||||
metricMigrationSuccess.Add(1)
|
metricMigrationSuccess.Add(1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,6 @@ func legacyPrefsDir(uid ipn.WindowsUserID) (string, error) {
|
||||||
func (pm *profileManager) loadLegacyPrefs() (string, ipn.PrefsView, error) {
|
func (pm *profileManager) loadLegacyPrefs() (string, ipn.PrefsView, error) {
|
||||||
userLegacyPrefsDir, err := legacyPrefsDir(pm.currentUserID)
|
userLegacyPrefsDir, err := legacyPrefsDir(pm.currentUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pm.dlogf("no legacy preferences directory for %q: %v", pm.currentUserID, err)
|
|
||||||
return "", ipn.PrefsView{}, err
|
return "", ipn.PrefsView{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,17 +47,14 @@ func (pm *profileManager) loadLegacyPrefs() (string, ipn.PrefsView, error) {
|
||||||
// verify that migration sentinel is not present
|
// verify that migration sentinel is not present
|
||||||
_, err = os.Stat(migrationSentinel)
|
_, err = os.Stat(migrationSentinel)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pm.dlogf("migration sentinel %q already exists", migrationSentinel)
|
|
||||||
return "", ipn.PrefsView{}, errAlreadyMigrated
|
return "", ipn.PrefsView{}, errAlreadyMigrated
|
||||||
}
|
}
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
pm.dlogf("os.Stat(%q) = %v", migrationSentinel, err)
|
|
||||||
return "", ipn.PrefsView{}, err
|
return "", ipn.PrefsView{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
prefsPath := filepath.Join(userLegacyPrefsDir, legacyPrefsFile+legacyPrefsExt)
|
prefsPath := filepath.Join(userLegacyPrefsDir, legacyPrefsFile+legacyPrefsExt)
|
||||||
prefs, err := ipn.LoadPrefs(prefsPath)
|
prefs, err := ipn.LoadPrefs(prefsPath)
|
||||||
pm.dlogf("ipn.LoadPrefs(%q) = %v, %v", prefsPath, prefs, err)
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
return "", ipn.PrefsView{}, errAlreadyMigrated
|
return "", ipn.PrefsView{}, errAlreadyMigrated
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,13 +162,12 @@ func (s *serveListener) handleServeListenersAccept(ln net.Listener) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
srcAddr := conn.RemoteAddr().(*net.TCPAddr).AddrPort()
|
srcAddr := conn.RemoteAddr().(*net.TCPAddr).AddrPort()
|
||||||
handler := s.b.tcpHandlerForServe(s.ap.Port(), srcAddr)
|
getConn := func() (net.Conn, bool) { return conn, true }
|
||||||
if handler == nil {
|
sendRST := func() {
|
||||||
s.b.logf("serve RST for %v", srcAddr)
|
s.b.logf("serve RST for %v", srcAddr)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
go handler(conn)
|
go s.b.HandleInterceptedTCPConn(s.ap.Port(), srcAddr, getConn, sendRST)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +256,7 @@ func (b *LocalBackend) ServeConfig() ipn.ServeConfigView {
|
||||||
return b.serveConfig
|
return b.serveConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) HandleIngressTCPConn(ingressPeer *tailcfg.Node, target ipn.HostPort, srcAddr netip.AddrPort, getConnOrReset func() (net.Conn, bool), sendRST func()) {
|
func (b *LocalBackend) HandleIngressTCPConn(ingressPeer *tailcfg.Node, target ipn.HostPort, srcAddr netip.AddrPort, getConn func() (net.Conn, bool), sendRST func()) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
sc := b.serveConfig
|
sc := b.serveConfig
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
|
@ -290,7 +289,7 @@ func (b *LocalBackend) HandleIngressTCPConn(ingressPeer *tailcfg.Node, target ip
|
||||||
if b.getTCPHandlerForFunnelFlow != nil {
|
if b.getTCPHandlerForFunnelFlow != nil {
|
||||||
handler := b.getTCPHandlerForFunnelFlow(srcAddr, dport)
|
handler := b.getTCPHandlerForFunnelFlow(srcAddr, dport)
|
||||||
if handler != nil {
|
if handler != nil {
|
||||||
c, ok := getConnOrReset()
|
c, ok := getConn()
|
||||||
if !ok {
|
if !ok {
|
||||||
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
|
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
|
||||||
return
|
return
|
||||||
|
@ -299,41 +298,39 @@ func (b *LocalBackend) HandleIngressTCPConn(ingressPeer *tailcfg.Node, target ip
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO(bradfitz): pass ingressPeer etc in context to tcpHandlerForServe,
|
// TODO(bradfitz): pass ingressPeer etc in context to HandleInterceptedTCPConn,
|
||||||
// extend serveHTTPContext or similar.
|
// extend serveHTTPContext or similar.
|
||||||
handler := b.tcpHandlerForServe(dport, srcAddr)
|
b.HandleInterceptedTCPConn(dport, srcAddr, getConn, sendRST)
|
||||||
if handler == nil {
|
|
||||||
sendRST()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c, ok := getConnOrReset()
|
|
||||||
if !ok {
|
|
||||||
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
handler(c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// tcpHandlerForServe returns a handler for a TCP connection to be served via
|
func (b *LocalBackend) HandleInterceptedTCPConn(dport uint16, srcAddr netip.AddrPort, getConn func() (net.Conn, bool), sendRST func()) {
|
||||||
// the ipn.ServeConfig.
|
|
||||||
func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort) (handler func(net.Conn) error) {
|
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
sc := b.serveConfig
|
sc := b.serveConfig
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
|
|
||||||
if !sc.Valid() {
|
if !sc.Valid() {
|
||||||
b.logf("[unexpected] localbackend: got TCP conn w/o serveConfig; from %v to port %v", srcAddr, dport)
|
b.logf("[unexpected] localbackend: got TCP conn w/o serveConfig; from %v to port %v", srcAddr, dport)
|
||||||
return nil
|
sendRST()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tcph, ok := sc.TCP().GetOk(dport)
|
tcph, ok := sc.TCP().GetOk(dport)
|
||||||
if !ok {
|
if !ok {
|
||||||
b.logf("[unexpected] localbackend: got TCP conn without TCP config for port %v; from %v", dport, srcAddr)
|
b.logf("[unexpected] localbackend: got TCP conn without TCP config for port %v; from %v", dport, srcAddr)
|
||||||
return nil
|
sendRST()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if tcph.HTTPS() || tcph.HTTP() {
|
if tcph.HTTPS() {
|
||||||
|
conn, ok := getConn()
|
||||||
|
if !ok {
|
||||||
|
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
|
||||||
|
return
|
||||||
|
}
|
||||||
hs := &http.Server{
|
hs := &http.Server{
|
||||||
|
TLSConfig: &tls.Config{
|
||||||
|
GetCertificate: b.getTLSServeCertForPort(dport),
|
||||||
|
},
|
||||||
Handler: http.HandlerFunc(b.serveWebHandler),
|
Handler: http.HandlerFunc(b.serveWebHandler),
|
||||||
BaseContext: func(_ net.Listener) context.Context {
|
BaseContext: func(_ net.Listener) context.Context {
|
||||||
return context.WithValue(context.Background(), serveHTTPContextKey{}, &serveHTTPContext{
|
return context.WithValue(context.Background(), serveHTTPContextKey{}, &serveHTTPContext{
|
||||||
|
@ -342,31 +339,28 @@ func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if tcph.HTTPS() {
|
hs.ServeTLS(netutil.NewOneConnListener(conn, nil), "", "")
|
||||||
hs.TLSConfig = &tls.Config{
|
return
|
||||||
GetCertificate: b.getTLSServeCertForPort(dport),
|
|
||||||
}
|
|
||||||
return func(c net.Conn) error {
|
|
||||||
return hs.ServeTLS(netutil.NewOneConnListener(c, nil), "", "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(c net.Conn) error {
|
|
||||||
return hs.Serve(netutil.NewOneConnListener(c, nil))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if backDst := tcph.TCPForward(); backDst != "" {
|
if backDst := tcph.TCPForward(); backDst != "" {
|
||||||
return func(conn net.Conn) error {
|
|
||||||
defer conn.Close()
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
backConn, err := b.dialer.SystemDial(ctx, "tcp", backDst)
|
backConn, err := b.dialer.SystemDial(ctx, "tcp", backDst)
|
||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.logf("localbackend: failed to TCP proxy port %v (from %v) to %s: %v", dport, srcAddr, backDst, err)
|
b.logf("localbackend: failed to TCP proxy port %v (from %v) to %s: %v", dport, srcAddr, backDst, err)
|
||||||
return nil
|
sendRST()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
conn, ok := getConn()
|
||||||
|
if !ok {
|
||||||
|
b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
|
||||||
|
backConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
defer backConn.Close()
|
defer backConn.Close()
|
||||||
|
|
||||||
if sni := tcph.TerminateTLS(); sni != "" {
|
if sni := tcph.TerminateTLS(); sni != "" {
|
||||||
conn = tls.Server(conn, &tls.Config{
|
conn = tls.Server(conn, &tls.Config{
|
||||||
GetCertificate: func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
GetCertificate: func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
@ -387,6 +381,7 @@ func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort)
|
||||||
|
|
||||||
// TODO(bradfitz): do the RegisterIPPortIdentity and
|
// TODO(bradfitz): do the RegisterIPPortIdentity and
|
||||||
// UnregisterIPPortIdentity stuff that netstack does
|
// UnregisterIPPortIdentity stuff that netstack does
|
||||||
|
|
||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
_, err := io.Copy(backConn, conn)
|
_, err := io.Copy(backConn, conn)
|
||||||
|
@ -396,38 +391,27 @@ func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort)
|
||||||
_, err := io.Copy(conn, backConn)
|
_, err := io.Copy(conn, backConn)
|
||||||
errc <- err
|
errc <- err
|
||||||
}()
|
}()
|
||||||
return <-errc
|
<-errc
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b.logf("closing TCP conn to port %v (from %v) with actionless TCPPortHandler", dport, srcAddr)
|
b.logf("closing TCP conn to port %v (from %v) with actionless TCPPortHandler", dport, srcAddr)
|
||||||
return nil
|
sendRST()
|
||||||
}
|
|
||||||
|
|
||||||
func getServeHTTPContext(r *http.Request) (c *serveHTTPContext, ok bool) {
|
|
||||||
c, ok = r.Context().Value(serveHTTPContextKey{}).(*serveHTTPContext)
|
|
||||||
return c, ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView, at string, ok bool) {
|
func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView, at string, ok bool) {
|
||||||
var z ipn.HTTPHandlerView // zero value
|
var z ipn.HTTPHandlerView // zero value
|
||||||
|
|
||||||
hostname := r.Host
|
|
||||||
if r.TLS == nil {
|
if r.TLS == nil {
|
||||||
tcd := "." + b.Status().CurrentTailnet.MagicDNSSuffix
|
return z, "", false
|
||||||
if !strings.HasSuffix(hostname, tcd) {
|
|
||||||
hostname += tcd
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hostname = r.TLS.ServerName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sctx, ok := getServeHTTPContext(r)
|
sctx, ok := r.Context().Value(serveHTTPContextKey{}).(*serveHTTPContext)
|
||||||
if !ok {
|
if !ok {
|
||||||
b.logf("[unexpected] localbackend: no serveHTTPContext in request")
|
b.logf("[unexpected] localbackend: no serveHTTPContext in request")
|
||||||
return z, "", false
|
return z, "", false
|
||||||
}
|
}
|
||||||
wsc, ok := b.webServerConfig(hostname, sctx.DestPort)
|
wsc, ok := b.webServerConfig(r.TLS.ServerName, sctx.DestPort)
|
||||||
if !ok {
|
if !ok {
|
||||||
return z, "", false
|
return z, "", false
|
||||||
}
|
}
|
||||||
|
@ -463,8 +447,9 @@ func (b *LocalBackend) proxyHandlerForBackend(backend string) (*httputil.Reverse
|
||||||
Rewrite: func(r *httputil.ProxyRequest) {
|
Rewrite: func(r *httputil.ProxyRequest) {
|
||||||
r.SetURL(u)
|
r.SetURL(u)
|
||||||
r.Out.Host = r.In.Host
|
r.Out.Host = r.In.Host
|
||||||
addProxyForwardedHeaders(r)
|
if c, ok := r.Out.Context().Value(serveHTTPContextKey{}).(*serveHTTPContext); ok {
|
||||||
b.addTailscaleIdentityHeaders(r)
|
r.Out.Header.Set("X-Forwarded-For", c.SrcAddr.Addr().String())
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DialContext: b.dialer.SystemDial,
|
DialContext: b.dialer.SystemDial,
|
||||||
|
@ -482,40 +467,6 @@ func (b *LocalBackend) proxyHandlerForBackend(backend string) (*httputil.Reverse
|
||||||
return rp, nil
|
return rp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addProxyForwardedHeaders(r *httputil.ProxyRequest) {
|
|
||||||
r.Out.Header.Set("X-Forwarded-Host", r.In.Host)
|
|
||||||
if r.In.TLS != nil {
|
|
||||||
r.Out.Header.Set("X-Forwarded-Proto", "https")
|
|
||||||
}
|
|
||||||
if c, ok := getServeHTTPContext(r.Out); ok {
|
|
||||||
r.Out.Header.Set("X-Forwarded-For", c.SrcAddr.Addr().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *LocalBackend) addTailscaleIdentityHeaders(r *httputil.ProxyRequest) {
|
|
||||||
// Clear any incoming values squatting in the headers.
|
|
||||||
r.Out.Header.Del("Tailscale-User-Login")
|
|
||||||
r.Out.Header.Del("Tailscale-User-Name")
|
|
||||||
r.Out.Header.Del("Tailscale-Headers-Info")
|
|
||||||
|
|
||||||
c, ok := getServeHTTPContext(r.Out)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
node, user, ok := b.WhoIs(c.SrcAddr)
|
|
||||||
if !ok {
|
|
||||||
return // traffic from outside of Tailnet (funneled)
|
|
||||||
}
|
|
||||||
if node.IsTagged() {
|
|
||||||
// 2023-06-14: Not setting identity headers for tagged nodes.
|
|
||||||
// Only currently set for nodes with user identities.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.Out.Header.Set("Tailscale-User-Login", user.LoginName)
|
|
||||||
r.Out.Header.Set("Tailscale-User-Name", user.DisplayName)
|
|
||||||
r.Out.Header.Set("Tailscale-Headers-Info", "https://tailscale.com/s/serve-headers")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) {
|
func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h, mountPoint, ok := b.getServeHandler(r)
|
h, mountPoint, ok := b.getServeHandler(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -648,8 +599,8 @@ func allNumeric(s string) bool {
|
||||||
return s != ""
|
return s != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) webServerConfig(hostname string, port uint16) (c ipn.WebServerConfigView, ok bool) {
|
func (b *LocalBackend) webServerConfig(sniName string, port uint16) (c ipn.WebServerConfigView, ok bool) {
|
||||||
key := ipn.HostPort(fmt.Sprintf("%s:%v", hostname, port))
|
key := ipn.HostPort(fmt.Sprintf("%s:%v", sniName, port))
|
||||||
|
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
|
@ -10,22 +10,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/netip"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/store/mem"
|
|
||||||
"tailscale.com/tailcfg"
|
|
||||||
"tailscale.com/tsd"
|
|
||||||
"tailscale.com/types/logid"
|
|
||||||
"tailscale.com/types/netmap"
|
|
||||||
"tailscale.com/util/cmpx"
|
|
||||||
"tailscale.com/util/must"
|
|
||||||
"tailscale.com/wgengine"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExpandProxyArg(t *testing.T) {
|
func TestExpandProxyArg(t *testing.T) {
|
||||||
|
@ -150,7 +140,10 @@ func TestGetServeHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
TLS: &tls.ConnectionState{ServerName: serverName},
|
TLS: &tls.ConnectionState{ServerName: serverName},
|
||||||
}
|
}
|
||||||
port := cmpx.Or(tt.port, 443)
|
port := tt.port
|
||||||
|
if port == 0 {
|
||||||
|
port = 443
|
||||||
|
}
|
||||||
req = req.WithContext(context.WithValue(req.Context(), serveHTTPContextKey{}, &serveHTTPContext{
|
req = req.WithContext(context.WithValue(req.Context(), serveHTTPContextKey{}, &serveHTTPContext{
|
||||||
DestPort: port,
|
DestPort: port,
|
||||||
}))
|
}))
|
||||||
|
@ -169,142 +162,6 @@ func TestGetServeHandler(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServeHTTPProxy(t *testing.T) {
|
|
||||||
sys := &tsd.System{}
|
|
||||||
e, err := wgengine.NewUserspaceEngine(t.Logf, wgengine.Config{SetSubsystem: sys.Set})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
sys.Set(e)
|
|
||||||
sys.Set(new(mem.Store))
|
|
||||||
b, err := NewLocalBackend(t.Logf, logid.PublicID{}, sys, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer b.Shutdown()
|
|
||||||
dir := t.TempDir()
|
|
||||||
b.SetVarRoot(dir)
|
|
||||||
|
|
||||||
pm := must.Get(newProfileManager(new(mem.Store), t.Logf))
|
|
||||||
pm.currentProfile = &ipn.LoginProfile{ID: "id0"}
|
|
||||||
b.pm = pm
|
|
||||||
|
|
||||||
b.netMap = &netmap.NetworkMap{
|
|
||||||
SelfNode: &tailcfg.Node{
|
|
||||||
Name: "example.ts.net",
|
|
||||||
},
|
|
||||||
UserProfiles: map[tailcfg.UserID]tailcfg.UserProfile{
|
|
||||||
tailcfg.UserID(1): {
|
|
||||||
LoginName: "someone@example.com",
|
|
||||||
DisplayName: "Some One",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b.nodeByAddr = map[netip.Addr]*tailcfg.Node{
|
|
||||||
netip.MustParseAddr("100.150.151.152"): {
|
|
||||||
ComputedName: "some-peer",
|
|
||||||
User: tailcfg.UserID(1),
|
|
||||||
},
|
|
||||||
netip.MustParseAddr("100.150.151.153"): {
|
|
||||||
ComputedName: "some-tagged-peer",
|
|
||||||
Tags: []string{"tag:server", "tag:test"},
|
|
||||||
User: tailcfg.UserID(1),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start test serve endpoint.
|
|
||||||
testServ := httptest.NewServer(http.HandlerFunc(
|
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Piping all the headers through the response writer
|
|
||||||
// so we can check their values in tests below.
|
|
||||||
for key, val := range r.Header {
|
|
||||||
w.Header().Add(key, strings.Join(val, ","))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
))
|
|
||||||
defer testServ.Close()
|
|
||||||
|
|
||||||
conf := &ipn.ServeConfig{
|
|
||||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
|
||||||
"example.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
|
||||||
"/": {Proxy: testServ.URL},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if err := b.SetServeConfig(conf); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
type headerCheck struct {
|
|
||||||
header string
|
|
||||||
want string
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
srcIP string
|
|
||||||
wantHeaders []headerCheck
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "request-from-user-within-tailnet",
|
|
||||||
srcIP: "100.150.151.152",
|
|
||||||
wantHeaders: []headerCheck{
|
|
||||||
{"X-Forwarded-Proto", "https"},
|
|
||||||
{"X-Forwarded-For", "100.150.151.152"},
|
|
||||||
{"Tailscale-User-Login", "someone@example.com"},
|
|
||||||
{"Tailscale-User-Name", "Some One"},
|
|
||||||
{"Tailscale-Headers-Info", "https://tailscale.com/s/serve-headers"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "request-from-tagged-node-within-tailnet",
|
|
||||||
srcIP: "100.150.151.153",
|
|
||||||
wantHeaders: []headerCheck{
|
|
||||||
{"X-Forwarded-Proto", "https"},
|
|
||||||
{"X-Forwarded-For", "100.150.151.153"},
|
|
||||||
{"Tailscale-User-Login", ""},
|
|
||||||
{"Tailscale-User-Name", ""},
|
|
||||||
{"Tailscale-Headers-Info", ""},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "request-from-outside-tailnet",
|
|
||||||
srcIP: "100.160.161.162",
|
|
||||||
wantHeaders: []headerCheck{
|
|
||||||
{"X-Forwarded-Proto", "https"},
|
|
||||||
{"X-Forwarded-For", "100.160.161.162"},
|
|
||||||
{"Tailscale-User-Login", ""},
|
|
||||||
{"Tailscale-User-Name", ""},
|
|
||||||
{"Tailscale-Headers-Info", ""},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
req := &http.Request{
|
|
||||||
URL: &url.URL{Path: "/"},
|
|
||||||
TLS: &tls.ConnectionState{ServerName: "example.ts.net"},
|
|
||||||
}
|
|
||||||
req = req.WithContext(context.WithValue(req.Context(), serveHTTPContextKey{}, &serveHTTPContext{
|
|
||||||
DestPort: 443,
|
|
||||||
SrcAddr: netip.MustParseAddrPort(tt.srcIP + ":1234"), // random src port for tests
|
|
||||||
}))
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
b.serveWebHandler(w, req)
|
|
||||||
|
|
||||||
// Verify the headers.
|
|
||||||
h := w.Result().Header
|
|
||||||
for _, c := range tt.wantHeaders {
|
|
||||||
if got := h.Get(c.header); got != c.want {
|
|
||||||
t.Errorf("invalid %q header; want=%q, got=%q", c.header, c.want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServeFileOrDirectory(t *testing.T) {
|
func TestServeFileOrDirectory(t *testing.T) {
|
||||||
td := t.TempDir()
|
td := t.TempDir()
|
||||||
writeFile := func(suffix, contents string) {
|
writeFile := func(suffix, contents string) {
|
||||||
|
|
|
@ -121,11 +121,6 @@ type NetworkLockStatus struct {
|
||||||
// (i.e. no connectivity) because they failed tailnet lock
|
// (i.e. no connectivity) because they failed tailnet lock
|
||||||
// checks.
|
// checks.
|
||||||
FilteredPeers []*TKAFilteredPeer
|
FilteredPeers []*TKAFilteredPeer
|
||||||
|
|
||||||
// StateID is a nonce associated with the network lock authority,
|
|
||||||
// generated upon enablement. This field is not populated if the
|
|
||||||
// network lock is disabled.
|
|
||||||
StateID uint64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkLockUpdate describes a change to network-lock state.
|
// NetworkLockUpdate describes a change to network-lock state.
|
||||||
|
@ -223,6 +218,7 @@ type PeerStatus struct {
|
||||||
LastSeen time.Time // last seen to tailcontrol; only present if offline
|
LastSeen time.Time // last seen to tailcontrol; only present if offline
|
||||||
LastHandshake time.Time // with local wireguard
|
LastHandshake time.Time // with local wireguard
|
||||||
Online bool // whether node is connected to the control plane
|
Online bool // whether node is connected to the control plane
|
||||||
|
KeepAlive bool
|
||||||
ExitNode bool // true if this is the currently selected exit node.
|
ExitNode bool // true if this is the currently selected exit node.
|
||||||
ExitNodeOption bool // true if this node can be an exit node (offered && approved)
|
ExitNodeOption bool // true if this node can be an exit node (offered && approved)
|
||||||
|
|
||||||
|
@ -436,6 +432,9 @@ func (sb *StatusBuilder) AddPeer(peer key.NodePublic, st *PeerStatus) {
|
||||||
if st.InEngine {
|
if st.InEngine {
|
||||||
e.InEngine = true
|
e.InEngine = true
|
||||||
}
|
}
|
||||||
|
if st.KeepAlive {
|
||||||
|
e.KeepAlive = true
|
||||||
|
}
|
||||||
if st.ExitNode {
|
if st.ExitNode {
|
||||||
e.ExitNode = true
|
e.ExitNode = true
|
||||||
}
|
}
|
||||||
|
@ -584,8 +583,6 @@ func osEmoji(os string) string {
|
||||||
return "🖥️"
|
return "🖥️"
|
||||||
case "iOS":
|
case "iOS":
|
||||||
return "📱"
|
return "📱"
|
||||||
case "tvOS":
|
|
||||||
return "🍎📺"
|
|
||||||
case "android":
|
case "android":
|
||||||
return "🤖"
|
return "🤖"
|
||||||
case "freebsd":
|
case "freebsd":
|
||||||
|
|
|
@ -104,7 +104,6 @@ var handler = map[string]localAPIHandler{
|
||||||
"tka/force-local-disable": (*Handler).serveTKALocalDisable,
|
"tka/force-local-disable": (*Handler).serveTKALocalDisable,
|
||||||
"tka/affected-sigs": (*Handler).serveTKAAffectedSigs,
|
"tka/affected-sigs": (*Handler).serveTKAAffectedSigs,
|
||||||
"tka/wrap-preauth-key": (*Handler).serveTKAWrapPreauthKey,
|
"tka/wrap-preauth-key": (*Handler).serveTKAWrapPreauthKey,
|
||||||
"tka/verify-deeplink": (*Handler).serveTKAVerifySigningDeeplink,
|
|
||||||
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
|
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
|
||||||
"watch-ipn-bus": (*Handler).serveWatchIPNBus,
|
"watch-ipn-bus": (*Handler).serveWatchIPNBus,
|
||||||
"whois": (*Handler).serveWhoIs,
|
"whois": (*Handler).serveWhoIs,
|
||||||
|
@ -931,8 +930,8 @@ func InUseOtherUserIPNStream(w http.ResponseWriter, r *http.Request, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) {
|
||||||
if !h.PermitRead {
|
if !h.PermitWrite {
|
||||||
http.Error(w, "watch ipn bus access denied", http.StatusForbidden)
|
http.Error(w, "denied", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f, ok := w.(http.Flusher)
|
f, ok := w.(http.Flusher)
|
||||||
|
@ -1331,7 +1330,7 @@ func (h *Handler) servePing(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pingTypeStr := r.FormValue("type")
|
pingTypeStr := r.FormValue("type")
|
||||||
if pingTypeStr == "" {
|
if ipStr == "" {
|
||||||
http.Error(w, "missing 'type' parameter", 400)
|
http.Error(w, "missing 'type' parameter", 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1611,35 +1610,6 @@ func (h *Handler) serveTKAWrapPreauthKey(w http.ResponseWriter, r *http.Request)
|
||||||
w.Write([]byte(wrappedKey))
|
w.Write([]byte(wrappedKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) serveTKAVerifySigningDeeplink(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if !h.PermitRead {
|
|
||||||
http.Error(w, "signing deeplink verification access denied", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Method != httpm.POST {
|
|
||||||
http.Error(w, "use POST", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type verifyRequest struct {
|
|
||||||
URL string
|
|
||||||
}
|
|
||||||
var req verifyRequest
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
http.Error(w, "invalid JSON for verifyRequest body", 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res := h.b.NetworkLockVerifySigningDeeplink(req.URL)
|
|
||||||
j, err := json.MarshalIndent(res, "", "\t")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "JSON encoding error", 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) serveTKADisable(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) serveTKADisable(w http.ResponseWriter, r *http.Request) {
|
||||||
if !h.PermitWrite {
|
if !h.PermitWrite {
|
||||||
http.Error(w, "network-lock modify access denied", http.StatusForbidden)
|
http.Error(w, "network-lock modify access denied", http.StatusForbidden)
|
||||||
|
|
48
ipn/serve.go
48
ipn/serve.go
|
@ -76,12 +76,6 @@ type TCPPortHandler struct {
|
||||||
// It is mutually exclusive with TCPForward.
|
// It is mutually exclusive with TCPForward.
|
||||||
HTTPS bool `json:",omitempty"`
|
HTTPS bool `json:",omitempty"`
|
||||||
|
|
||||||
// HTTP, if true, means that tailscaled should handle this connection as an
|
|
||||||
// HTTP request as configured by ServeConfig.Web.
|
|
||||||
//
|
|
||||||
// It is mutually exclusive with TCPForward.
|
|
||||||
HTTP bool `json:",omitempty"`
|
|
||||||
|
|
||||||
// TCPForward is the IP:port to forward TCP connections to.
|
// TCPForward is the IP:port to forward TCP connections to.
|
||||||
// Whether or not TLS is terminated by tailscaled depends on
|
// Whether or not TLS is terminated by tailscaled depends on
|
||||||
// TerminateTLS.
|
// TerminateTLS.
|
||||||
|
@ -109,7 +103,7 @@ type HTTPHandler struct {
|
||||||
// temporary ones? Error codes? Redirects?
|
// temporary ones? Error codes? Redirects?
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebHandlerExists reports whether if the ServeConfig Web handler exists for
|
// WebHandlerExists checks if the ServeConfig Web handler exists for
|
||||||
// the given host:port and mount point.
|
// the given host:port and mount point.
|
||||||
func (sc *ServeConfig) WebHandlerExists(hp HostPort, mount string) bool {
|
func (sc *ServeConfig) WebHandlerExists(hp HostPort, mount string) bool {
|
||||||
h := sc.GetWebHandler(hp, mount)
|
h := sc.GetWebHandler(hp, mount)
|
||||||
|
@ -134,8 +128,9 @@ func (sc *ServeConfig) GetTCPPortHandler(port uint16) *TCPPortHandler {
|
||||||
return sc.TCP[port]
|
return sc.TCP[port]
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTCPForwardingAny reports whether ServeConfig is currently forwarding in
|
// IsTCPForwardingAny checks if ServeConfig is currently forwarding
|
||||||
// TCPForward mode on any port. This is exclusive of Web/HTTPS serving.
|
// in TCPForward mode on any port.
|
||||||
|
// This is exclusive of Web/HTTPS serving.
|
||||||
func (sc *ServeConfig) IsTCPForwardingAny() bool {
|
func (sc *ServeConfig) IsTCPForwardingAny() bool {
|
||||||
if sc == nil || len(sc.TCP) == 0 {
|
if sc == nil || len(sc.TCP) == 0 {
|
||||||
return false
|
return false
|
||||||
|
@ -148,47 +143,34 @@ func (sc *ServeConfig) IsTCPForwardingAny() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTCPForwardingOnPort reports whether if ServeConfig is currently forwarding
|
// IsTCPForwardingOnPort checks if ServeConfig is currently forwarding
|
||||||
// in TCPForward mode on the given port. This is exclusive of Web/HTTPS serving.
|
// in TCPForward mode on the given port.
|
||||||
|
// This is exclusive of Web/HTTPS serving.
|
||||||
func (sc *ServeConfig) IsTCPForwardingOnPort(port uint16) bool {
|
func (sc *ServeConfig) IsTCPForwardingOnPort(port uint16) bool {
|
||||||
if sc == nil || sc.TCP[port] == nil {
|
if sc == nil || sc.TCP[port] == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return !sc.IsServingWeb(port)
|
return !sc.TCP[port].HTTPS
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsServingWeb reports whether if ServeConfig is currently serving Web
|
// IsServingWeb checks if ServeConfig is currently serving
|
||||||
// (HTTP/HTTPS) on the given port. This is exclusive of TCPForwarding.
|
// Web/HTTPS on the given port.
|
||||||
|
// This is exclusive of TCPForwarding.
|
||||||
func (sc *ServeConfig) IsServingWeb(port uint16) bool {
|
func (sc *ServeConfig) IsServingWeb(port uint16) bool {
|
||||||
return sc.IsServingHTTP(port) || sc.IsServingHTTPS(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsServingHTTPS reports whether if ServeConfig is currently serving HTTPS on
|
|
||||||
// the given port. This is exclusive of HTTP and TCPForwarding.
|
|
||||||
func (sc *ServeConfig) IsServingHTTPS(port uint16) bool {
|
|
||||||
if sc == nil || sc.TCP[port] == nil {
|
if sc == nil || sc.TCP[port] == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return sc.TCP[port].HTTPS
|
return sc.TCP[port].HTTPS
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsServingHTTP reports whether if ServeConfig is currently serving HTTP on the
|
// IsFunnelOn checks if ServeConfig is currently allowing
|
||||||
// given port. This is exclusive of HTTPS and TCPForwarding.
|
// funnel traffic for any host:port.
|
||||||
func (sc *ServeConfig) IsServingHTTP(port uint16) bool {
|
|
||||||
if sc == nil || sc.TCP[port] == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return sc.TCP[port].HTTP
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsFunnelOn reports whether if ServeConfig is currently allowing funnel
|
|
||||||
// traffic for any host:port.
|
|
||||||
//
|
//
|
||||||
// View version of ServeConfig.IsFunnelOn.
|
// View version of ServeConfig.IsFunnelOn.
|
||||||
func (v ServeConfigView) IsFunnelOn() bool { return v.ж.IsFunnelOn() }
|
func (v ServeConfigView) IsFunnelOn() bool { return v.ж.IsFunnelOn() }
|
||||||
|
|
||||||
// IsFunnelOn reports whether if ServeConfig is currently allowing funnel
|
// IsFunnelOn checks if ServeConfig is currently allowing
|
||||||
// traffic for any host:port.
|
// funnel traffic for any host:port.
|
||||||
func (sc *ServeConfig) IsFunnelOn() bool {
|
func (sc *ServeConfig) IsFunnelOn() bool {
|
||||||
if sc == nil {
|
if sc == nil {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -69,14 +69,14 @@ Client][]. See also the dependencies in the [Tailscale CLI][].
|
||||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
|
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
|
||||||
- [go4.org/unsafe/assume-no-moving-gc](https://pkg.go.dev/go4.org/unsafe/assume-no-moving-gc) ([BSD-3-Clause](https://github.com/go4org/unsafe-assume-no-moving-gc/blob/ee73d164e760/LICENSE))
|
- [go4.org/unsafe/assume-no-moving-gc](https://pkg.go.dev/go4.org/unsafe/assume-no-moving-gc) ([BSD-3-Clause](https://github.com/go4org/unsafe-assume-no-moving-gc/blob/ee73d164e760/LICENSE))
|
||||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.9.0:LICENSE))
|
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
|
||||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
||||||
- [golang.org/x/exp/shiny](https://pkg.go.dev/golang.org/x/exp/shiny) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/334a2380:shiny/LICENSE))
|
- [golang.org/x/exp/shiny](https://pkg.go.dev/golang.org/x/exp/shiny) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/334a2380:shiny/LICENSE))
|
||||||
- [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.7.0:LICENSE))
|
- [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.7.0:LICENSE))
|
||||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.10.0:LICENSE))
|
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.9.0:LICENSE))
|
||||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
|
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
|
||||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/5059a07a:LICENSE))
|
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE))
|
||||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.8.0:LICENSE))
|
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.7.0:LICENSE))
|
||||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
||||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
||||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
|
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
|
||||||
|
|
|
@ -31,7 +31,6 @@ and [iOS][]. See also the dependencies in the [Tailscale CLI][].
|
||||||
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.1.0/LICENSE))
|
- [github.com/godbus/dbus/v5](https://pkg.go.dev/github.com/godbus/dbus/v5) ([BSD-2-Clause](https://github.com/godbus/dbus/blob/v5.1.0/LICENSE))
|
||||||
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
||||||
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
|
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
|
||||||
- [github.com/google/nftables](https://pkg.go.dev/github.com/google/nftables) ([Apache-2.0](https://github.com/google/nftables/blob/9aa6fdf5a28c/LICENSE))
|
|
||||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
|
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
|
||||||
- [github.com/illarion/gonotify](https://pkg.go.dev/github.com/illarion/gonotify) ([MIT](https://github.com/illarion/gonotify/blob/v1.0.1/LICENSE))
|
- [github.com/illarion/gonotify](https://pkg.go.dev/github.com/illarion/gonotify) ([MIT](https://github.com/illarion/gonotify/blob/v1.0.1/LICENSE))
|
||||||
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/974c6f05fe16/LICENSE))
|
- [github.com/insomniacslk/dhcp](https://pkg.go.dev/github.com/insomniacslk/dhcp) ([BSD-3-Clause](https://github.com/insomniacslk/dhcp/blob/974c6f05fe16/LICENSE))
|
||||||
|
@ -59,13 +58,13 @@ and [iOS][]. See also the dependencies in the [Tailscale CLI][].
|
||||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
|
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
|
||||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.10.0:LICENSE))
|
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
|
||||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
||||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://github.com/tailscale/golang-x-net/blob/9a58c47922fd/LICENSE))
|
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.9.0:LICENSE))
|
||||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
|
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
|
||||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.9.0:LICENSE))
|
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE))
|
||||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.9.0:LICENSE))
|
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.7.0:LICENSE))
|
||||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.10.0:LICENSE))
|
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
||||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
||||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
|
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
|
||||||
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
|
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
|
||||||
|
|
|
@ -81,11 +81,11 @@ Some packages may only be included on certain architectures or operating systems
|
||||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
|
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
|
||||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
|
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
|
||||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
||||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.10.0:LICENSE))
|
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.9.0:LICENSE))
|
||||||
- [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) ([BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.7.0:LICENSE))
|
- [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2) ([BSD-3-Clause](https://cs.opensource.google/go/x/oauth2/+/v0.7.0:LICENSE))
|
||||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
|
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
|
||||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/5059a07a:LICENSE))
|
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE))
|
||||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.8.0:LICENSE))
|
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.7.0:LICENSE))
|
||||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
||||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
||||||
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2))
|
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2))
|
||||||
|
@ -94,7 +94,7 @@ Some packages may only be included on certain architectures or operating systems
|
||||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
|
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
|
||||||
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
|
- [inet.af/peercred](https://pkg.go.dev/inet.af/peercred) ([BSD-3-Clause](https://github.com/inetaf/peercred/blob/0893ea02156a/LICENSE))
|
||||||
- [inet.af/wf](https://pkg.go.dev/inet.af/wf) ([BSD-3-Clause](https://github.com/inetaf/wf/blob/36129f591884/LICENSE))
|
- [inet.af/wf](https://pkg.go.dev/inet.af/wf) ([BSD-3-Clause](https://github.com/inetaf/wf/blob/36129f591884/LICENSE))
|
||||||
- [k8s.io/client-go/util/homedir](https://pkg.go.dev/k8s.io/client-go/util/homedir) ([Apache-2.0](https://github.com/kubernetes/client-go/blob/v0.27.2/LICENSE))
|
- [k8s.io/client-go/util/homedir](https://pkg.go.dev/k8s.io/client-go/util/homedir) ([Apache-2.0](https://github.com/kubernetes/client-go/blob/v0.26.1/LICENSE))
|
||||||
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))
|
- [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) ([MIT](https://github.com/nhooyr/websocket/blob/v1.8.7/LICENSE.txt))
|
||||||
- [sigs.k8s.io/yaml](https://pkg.go.dev/sigs.k8s.io/yaml) ([MIT](https://github.com/kubernetes-sigs/yaml/blob/v1.3.0/LICENSE))
|
- [sigs.k8s.io/yaml](https://pkg.go.dev/sigs.k8s.io/yaml) ([MIT](https://github.com/kubernetes-sigs/yaml/blob/v1.3.0/LICENSE))
|
||||||
- [software.sslmate.com/src/go-pkcs12](https://pkg.go.dev/software.sslmate.com/src/go-pkcs12) ([BSD-3-Clause](https://github.com/SSLMate/go-pkcs12/blob/v0.2.0/LICENSE))
|
- [software.sslmate.com/src/go-pkcs12](https://pkg.go.dev/software.sslmate.com/src/go-pkcs12) ([BSD-3-Clause](https://github.com/SSLMate/go-pkcs12/blob/v0.2.0/LICENSE))
|
||||||
|
|
|
@ -14,12 +14,10 @@ Windows][]. See also the dependencies in the [Tailscale CLI][].
|
||||||
- [github.com/alexbrainman/sspi](https://pkg.go.dev/github.com/alexbrainman/sspi) ([BSD-3-Clause](https://github.com/alexbrainman/sspi/blob/909beea2cc74/LICENSE))
|
- [github.com/alexbrainman/sspi](https://pkg.go.dev/github.com/alexbrainman/sspi) ([BSD-3-Clause](https://github.com/alexbrainman/sspi/blob/909beea2cc74/LICENSE))
|
||||||
- [github.com/apenwarr/fixconsole](https://pkg.go.dev/github.com/apenwarr/fixconsole) ([Apache-2.0](https://github.com/apenwarr/fixconsole/blob/5a9f6489cc29/LICENSE))
|
- [github.com/apenwarr/fixconsole](https://pkg.go.dev/github.com/apenwarr/fixconsole) ([Apache-2.0](https://github.com/apenwarr/fixconsole/blob/5a9f6489cc29/LICENSE))
|
||||||
- [github.com/apenwarr/w32](https://pkg.go.dev/github.com/apenwarr/w32) ([BSD-3-Clause](https://github.com/apenwarr/w32/blob/aa00fece76ab/LICENSE))
|
- [github.com/apenwarr/w32](https://pkg.go.dev/github.com/apenwarr/w32) ([BSD-3-Clause](https://github.com/apenwarr/w32/blob/aa00fece76ab/LICENSE))
|
||||||
- [github.com/coreos/go-iptables/iptables](https://pkg.go.dev/github.com/coreos/go-iptables/iptables) ([Apache-2.0](https://github.com/coreos/go-iptables/blob/v0.6.0/LICENSE))
|
|
||||||
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/111c8c3b57c8/LICENSE))
|
- [github.com/dblohm7/wingoes](https://pkg.go.dev/github.com/dblohm7/wingoes) ([BSD-3-Clause](https://github.com/dblohm7/wingoes/blob/111c8c3b57c8/LICENSE))
|
||||||
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
|
- [github.com/fxamacker/cbor/v2](https://pkg.go.dev/github.com/fxamacker/cbor/v2) ([MIT](https://github.com/fxamacker/cbor/blob/v2.4.0/LICENSE))
|
||||||
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
- [github.com/golang/groupcache/lru](https://pkg.go.dev/github.com/golang/groupcache/lru) ([Apache-2.0](https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE))
|
||||||
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
|
- [github.com/google/btree](https://pkg.go.dev/github.com/google/btree) ([Apache-2.0](https://github.com/google/btree/blob/v1.1.2/LICENSE))
|
||||||
- [github.com/google/nftables](https://pkg.go.dev/github.com/google/nftables) ([Apache-2.0](https://github.com/google/nftables/blob/9aa6fdf5a28c/LICENSE))
|
|
||||||
- [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.3.0/LICENSE))
|
- [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.3.0/LICENSE))
|
||||||
- [github.com/gregjones/httpcache](https://pkg.go.dev/github.com/gregjones/httpcache) ([MIT](https://github.com/gregjones/httpcache/blob/901d90724c79/LICENSE.txt))
|
- [github.com/gregjones/httpcache](https://pkg.go.dev/github.com/gregjones/httpcache) ([MIT](https://github.com/gregjones/httpcache/blob/901d90724c79/LICENSE.txt))
|
||||||
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
|
- [github.com/hdevalence/ed25519consensus](https://pkg.go.dev/github.com/hdevalence/ed25519consensus) ([BSD-3-Clause](https://github.com/hdevalence/ed25519consensus/blob/v0.1.0/LICENSE))
|
||||||
|
@ -34,29 +32,24 @@ Windows][]. See also the dependencies in the [Tailscale CLI][].
|
||||||
- [github.com/nfnt/resize](https://pkg.go.dev/github.com/nfnt/resize) ([ISC](https://github.com/nfnt/resize/blob/83c6a9932646/LICENSE))
|
- [github.com/nfnt/resize](https://pkg.go.dev/github.com/nfnt/resize) ([ISC](https://github.com/nfnt/resize/blob/83c6a9932646/LICENSE))
|
||||||
- [github.com/peterbourgon/diskv](https://pkg.go.dev/github.com/peterbourgon/diskv) ([MIT](https://github.com/peterbourgon/diskv/blob/v2.0.1/LICENSE))
|
- [github.com/peterbourgon/diskv](https://pkg.go.dev/github.com/peterbourgon/diskv) ([MIT](https://github.com/peterbourgon/diskv/blob/v2.0.1/LICENSE))
|
||||||
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
- [github.com/skip2/go-qrcode](https://pkg.go.dev/github.com/skip2/go-qrcode) ([MIT](https://github.com/skip2/go-qrcode/blob/da1b6568686e/LICENSE))
|
||||||
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/cabfb018fe85/LICENSE))
|
|
||||||
- [github.com/tailscale/walk](https://pkg.go.dev/github.com/tailscale/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/f63dace725d8/LICENSE))
|
- [github.com/tailscale/walk](https://pkg.go.dev/github.com/tailscale/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/f63dace725d8/LICENSE))
|
||||||
- [github.com/tailscale/win](https://pkg.go.dev/github.com/tailscale/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/59dfb47dfef1/LICENSE))
|
- [github.com/tailscale/win](https://pkg.go.dev/github.com/tailscale/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/59dfb47dfef1/LICENSE))
|
||||||
- [github.com/tc-hib/winres](https://pkg.go.dev/github.com/tc-hib/winres) ([0BSD](https://github.com/tc-hib/winres/blob/v0.2.0/LICENSE))
|
- [github.com/tc-hib/winres](https://pkg.go.dev/github.com/tc-hib/winres) ([0BSD](https://github.com/tc-hib/winres/blob/v0.2.0/LICENSE))
|
||||||
- [github.com/vishvananda/netlink/nl](https://pkg.go.dev/github.com/vishvananda/netlink/nl) ([Apache-2.0](https://github.com/vishvananda/netlink/blob/v1.2.1-beta.2/LICENSE))
|
|
||||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/v0.0.4/LICENSE))
|
|
||||||
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
- [github.com/x448/float16](https://pkg.go.dev/github.com/x448/float16) ([MIT](https://github.com/x448/float16/blob/v0.8.4/LICENSE))
|
||||||
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
- [go4.org/mem](https://pkg.go.dev/go4.org/mem) ([Apache-2.0](https://github.com/go4org/mem/blob/4f986261bf13/LICENSE))
|
||||||
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
|
- [go4.org/netipx](https://pkg.go.dev/go4.org/netipx) ([BSD-3-Clause](https://github.com/go4org/netipx/blob/f1b76eb4bb35/LICENSE))
|
||||||
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.10.0:LICENSE))
|
- [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) ([BSD-3-Clause](https://cs.opensource.google/go/x/crypto/+/v0.8.0:LICENSE))
|
||||||
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
- [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/47ecfdc1:LICENSE))
|
||||||
- [golang.org/x/image/bmp](https://pkg.go.dev/golang.org/x/image/bmp) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.7.0:LICENSE))
|
- [golang.org/x/image/bmp](https://pkg.go.dev/golang.org/x/image/bmp) ([BSD-3-Clause](https://cs.opensource.google/go/x/image/+/v0.7.0:LICENSE))
|
||||||
- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.10.0:LICENSE))
|
- [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) ([BSD-3-Clause](https://cs.opensource.google/go/x/mod/+/v0.10.0:LICENSE))
|
||||||
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://github.com/tailscale/golang-x-net/blob/9a58c47922fd/LICENSE))
|
- [golang.org/x/net](https://pkg.go.dev/golang.org/x/net) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.9.0:LICENSE))
|
||||||
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
|
- [golang.org/x/sync/errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) ([BSD-3-Clause](https://cs.opensource.google/go/x/sync/+/v0.2.0:LICENSE))
|
||||||
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.9.0:LICENSE))
|
- [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.8.0:LICENSE))
|
||||||
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.9.0:LICENSE))
|
- [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) ([BSD-3-Clause](https://cs.opensource.google/go/x/term/+/v0.7.0:LICENSE))
|
||||||
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.10.0:LICENSE))
|
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.9.0:LICENSE))
|
||||||
- [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.3.0:LICENSE))
|
|
||||||
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2))
|
- [golang.zx2c4.com/wintun](https://pkg.go.dev/golang.zx2c4.com/wintun) ([MIT](https://git.zx2c4.com/wintun-go/tree/LICENSE?id=0fa3db229ce2))
|
||||||
- [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3))
|
- [golang.zx2c4.com/wireguard/windows/tunnel/winipcfg](https://pkg.go.dev/golang.zx2c4.com/wireguard/windows/tunnel/winipcfg) ([MIT](https://git.zx2c4.com/wireguard-windows/tree/COPYING?h=v0.5.3))
|
||||||
- [gopkg.in/Knetic/govaluate.v3](https://pkg.go.dev/gopkg.in/Knetic/govaluate.v3) ([MIT](https://github.com/Knetic/govaluate/blob/v3.0.0/LICENSE))
|
- [gopkg.in/Knetic/govaluate.v3](https://pkg.go.dev/gopkg.in/Knetic/govaluate.v3) ([MIT](https://github.com/Knetic/govaluate/blob/v3.0.0/LICENSE))
|
||||||
- [gvisor.dev/gvisor/pkg](https://pkg.go.dev/gvisor.dev/gvisor/pkg) ([Apache-2.0](https://github.com/google/gvisor/blob/7b0a1988a28f/LICENSE))
|
|
||||||
- [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE))
|
- [tailscale.com](https://pkg.go.dev/tailscale.com) ([BSD-3-Clause](https://github.com/tailscale/tailscale/blob/HEAD/LICENSE))
|
||||||
|
|
||||||
## Additional Dependencies
|
## Additional Dependencies
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
//go:build !windows && !wasm
|
//go:build !windows && !js
|
||||||
|
|
||||||
package filch
|
package filch
|
||||||
|
|
||||||
|
|
|
@ -45,14 +45,6 @@ func (m *LabelMap) Get(key string) *expvar.Int {
|
||||||
return m.Map.Get(key).(*expvar.Int)
|
return m.Map.Get(key).(*expvar.Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIncrFunc returns a function that increments the expvar.Int named by key.
|
|
||||||
//
|
|
||||||
// Most callers should not need this; it exists to satisfy an
|
|
||||||
// interface elsewhere.
|
|
||||||
func (m *LabelMap) GetIncrFunc(key string) func(delta int64) {
|
|
||||||
return m.Get(key).Add
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFloat returns a direct pointer to the expvar.Float for key, creating it
|
// GetFloat returns a direct pointer to the expvar.Float for key, creating it
|
||||||
// if necessary.
|
// if necessary.
|
||||||
func (m *LabelMap) GetFloat(key string) *expvar.Float {
|
func (m *LabelMap) GetFloat(key string) *expvar.Float {
|
||||||
|
|
|
@ -11,18 +11,6 @@ import (
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLabelMap(t *testing.T) {
|
|
||||||
var m LabelMap
|
|
||||||
m.GetIncrFunc("foo")(1)
|
|
||||||
m.GetIncrFunc("bar")(2)
|
|
||||||
if g, w := m.Get("foo").Value(), int64(1); g != w {
|
|
||||||
t.Errorf("foo = %v; want %v", g, w)
|
|
||||||
}
|
|
||||||
if g, w := m.Get("bar").Value(), int64(2); g != w {
|
|
||||||
t.Errorf("bar = %v; want %v", g, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCurrentFileDescriptors(t *testing.T) {
|
func TestCurrentFileDescriptors(t *testing.T) {
|
||||||
if runtime.GOOS != "linux" {
|
if runtime.GOOS != "linux" {
|
||||||
t.Skipf("skipping on %v", runtime.GOOS)
|
t.Skipf("skipping on %v", runtime.GOOS)
|
||||||
|
|
|
@ -1,636 +0,0 @@
|
||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
// Package recursive implements a simple recursive DNS resolver.
|
|
||||||
package recursive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"golang.org/x/exp/constraints"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"tailscale.com/envknob"
|
|
||||||
"tailscale.com/net/netns"
|
|
||||||
"tailscale.com/types/logger"
|
|
||||||
"tailscale.com/util/dnsname"
|
|
||||||
"tailscale.com/util/mak"
|
|
||||||
"tailscale.com/util/multierr"
|
|
||||||
"tailscale.com/util/slicesx"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// maxDepth is how deep from the root nameservers we'll recurse when
|
|
||||||
// resolving; passing this limit will instead return an error.
|
|
||||||
//
|
|
||||||
// maxDepth must be at least 20 to resolve "console.aws.amazon.com",
|
|
||||||
// which is a domain with a moderately complicated DNS setup. The
|
|
||||||
// current value of 30 was chosen semi-arbitrarily to ensure that we
|
|
||||||
// have about 50% headroom.
|
|
||||||
maxDepth = 30
|
|
||||||
// numStartingServers is the number of root nameservers that we use as
|
|
||||||
// initial candidates for our recursion.
|
|
||||||
numStartingServers = 3
|
|
||||||
// udpQueryTimeout is the amount of time we wait for a UDP response
|
|
||||||
// from a nameserver before falling back to a TCP connection.
|
|
||||||
udpQueryTimeout = 5 * time.Second
|
|
||||||
|
|
||||||
// These constants aren't typed in the DNS package, so we create typed
|
|
||||||
// versions here to avoid having to do repeated type casts.
|
|
||||||
qtypeA dns.Type = dns.Type(dns.TypeA)
|
|
||||||
qtypeAAAA dns.Type = dns.Type(dns.TypeAAAA)
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrMaxDepth is returned when recursive resolving exceeds the maximum
|
|
||||||
// depth limit for this package.
|
|
||||||
ErrMaxDepth = fmt.Errorf("exceeded max depth %d when resolving", maxDepth)
|
|
||||||
|
|
||||||
// ErrAuthoritativeNoResponses is the error returned when an
|
|
||||||
// authoritative nameserver indicates that there are no responses to
|
|
||||||
// the given query.
|
|
||||||
ErrAuthoritativeNoResponses = errors.New("authoritative server returned no responses")
|
|
||||||
|
|
||||||
// ErrNoResponses is returned when our resolution process completes
|
|
||||||
// with no valid responses from any nameserver, but no authoritative
|
|
||||||
// server explicitly returned NXDOMAIN.
|
|
||||||
ErrNoResponses = errors.New("no responses to query")
|
|
||||||
)
|
|
||||||
|
|
||||||
var rootServersV4 = []netip.Addr{
|
|
||||||
netip.MustParseAddr("198.41.0.4"), // a.root-servers.net
|
|
||||||
netip.MustParseAddr("199.9.14.201"), // b.root-servers.net
|
|
||||||
netip.MustParseAddr("192.33.4.12"), // c.root-servers.net
|
|
||||||
netip.MustParseAddr("199.7.91.13"), // d.root-servers.net
|
|
||||||
netip.MustParseAddr("192.203.230.10"), // e.root-servers.net
|
|
||||||
netip.MustParseAddr("192.5.5.241"), // f.root-servers.net
|
|
||||||
netip.MustParseAddr("192.112.36.4"), // g.root-servers.net
|
|
||||||
netip.MustParseAddr("198.97.190.53"), // h.root-servers.net
|
|
||||||
netip.MustParseAddr("192.36.148.17"), // i.root-servers.net
|
|
||||||
netip.MustParseAddr("192.58.128.30"), // j.root-servers.net
|
|
||||||
netip.MustParseAddr("193.0.14.129"), // k.root-servers.net
|
|
||||||
netip.MustParseAddr("199.7.83.42"), // l.root-servers.net
|
|
||||||
netip.MustParseAddr("202.12.27.33"), // m.root-servers.net
|
|
||||||
}
|
|
||||||
|
|
||||||
var rootServersV6 = []netip.Addr{
|
|
||||||
netip.MustParseAddr("2001:503:ba3e::2:30"), // a.root-servers.net
|
|
||||||
netip.MustParseAddr("2001:500:200::b"), // b.root-servers.net
|
|
||||||
netip.MustParseAddr("2001:500:2::c"), // c.root-servers.net
|
|
||||||
netip.MustParseAddr("2001:500:2d::d"), // d.root-servers.net
|
|
||||||
netip.MustParseAddr("2001:500:a8::e"), // e.root-servers.net
|
|
||||||
netip.MustParseAddr("2001:500:2f::f"), // f.root-servers.net
|
|
||||||
netip.MustParseAddr("2001:500:12::d0d"), // g.root-servers.net
|
|
||||||
netip.MustParseAddr("2001:500:1::53"), // h.root-servers.net
|
|
||||||
netip.MustParseAddr("2001:7fe::53"), // i.root-servers.net
|
|
||||||
netip.MustParseAddr("2001:503:c27::2:30"), // j.root-servers.net
|
|
||||||
netip.MustParseAddr("2001:7fd::1"), // k.root-servers.net
|
|
||||||
netip.MustParseAddr("2001:500:9f::42"), // l.root-servers.net
|
|
||||||
netip.MustParseAddr("2001:dc3::35"), // m.root-servers.net
|
|
||||||
}
|
|
||||||
|
|
||||||
var debug = envknob.RegisterBool("TS_DEBUG_RECURSIVE_DNS")
|
|
||||||
|
|
||||||
// Resolver is a recursive DNS resolver that is designed for looking up A and AAAA records.
|
|
||||||
type Resolver struct {
|
|
||||||
// Dialer is used to create outbound connections. If nil, a zero
|
|
||||||
// net.Dialer will be used instead.
|
|
||||||
Dialer netns.Dialer
|
|
||||||
|
|
||||||
// Logf is the logging function to use; if none is specified, then logs
|
|
||||||
// will be dropped.
|
|
||||||
Logf logger.Logf
|
|
||||||
|
|
||||||
// NoIPv6, if set, will prevent this package from querying for AAAA
|
|
||||||
// records and will avoid contacting nameservers over IPv6.
|
|
||||||
NoIPv6 bool
|
|
||||||
|
|
||||||
// Test mocks
|
|
||||||
testQueryHook func(name dnsname.FQDN, nameserver netip.Addr, protocol string, qtype dns.Type) (*dns.Msg, error)
|
|
||||||
testExchangeHook func(nameserver netip.Addr, network string, msg *dns.Msg) (*dns.Msg, error)
|
|
||||||
rootServers []netip.Addr
|
|
||||||
timeNow func() time.Time
|
|
||||||
|
|
||||||
// Caching
|
|
||||||
// NOTE(andrew): if we make resolution parallel, this needs a mutex
|
|
||||||
queryCache map[dnsQuery]dnsMsgWithExpiry
|
|
||||||
|
|
||||||
// Possible future additions:
|
|
||||||
// - Additional nameservers? From the system maybe?
|
|
||||||
// - NoIPv4 for IPv4
|
|
||||||
// - DNS-over-HTTPS or DNS-over-TLS support
|
|
||||||
}
|
|
||||||
|
|
||||||
// queryState stores all state during the course of a single query
|
|
||||||
type queryState struct {
|
|
||||||
// rootServers are the root nameservers to start from
|
|
||||||
rootServers []netip.Addr
|
|
||||||
|
|
||||||
// TODO: metrics?
|
|
||||||
}
|
|
||||||
|
|
||||||
type dnsQuery struct {
|
|
||||||
nameserver netip.Addr
|
|
||||||
name dnsname.FQDN
|
|
||||||
qtype dns.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q dnsQuery) String() string {
|
|
||||||
return fmt.Sprintf("dnsQuery{nameserver:%q,name:%q,qtype:%v}", q.nameserver.String(), q.name, q.qtype)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dnsMsgWithExpiry struct {
|
|
||||||
*dns.Msg
|
|
||||||
expiresAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) now() time.Time {
|
|
||||||
if r.timeNow != nil {
|
|
||||||
return r.timeNow()
|
|
||||||
}
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) logf(format string, args ...any) {
|
|
||||||
if r.Logf == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.Logf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) dlogf(format string, args ...any) {
|
|
||||||
if r.Logf == nil || !debug() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.Logf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) depthlogf(depth int, format string, args ...any) {
|
|
||||||
if r.Logf == nil || !debug() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
prefix := fmt.Sprintf("[%d] %s", depth, strings.Repeat(" ", depth))
|
|
||||||
r.Logf(prefix+format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultDialer net.Dialer
|
|
||||||
|
|
||||||
func (r *Resolver) dialer() netns.Dialer {
|
|
||||||
if r.Dialer != nil {
|
|
||||||
return r.Dialer
|
|
||||||
}
|
|
||||||
|
|
||||||
return &defaultDialer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) newState() *queryState {
|
|
||||||
var rootServers []netip.Addr
|
|
||||||
if len(r.rootServers) > 0 {
|
|
||||||
rootServers = r.rootServers
|
|
||||||
} else {
|
|
||||||
// Select a random subset of root nameservers to start from, since if
|
|
||||||
// we don't get responses from those, something else has probably gone
|
|
||||||
// horribly wrong.
|
|
||||||
roots4 := slices.Clone(rootServersV4)
|
|
||||||
slicesx.Shuffle(roots4)
|
|
||||||
roots4 = roots4[:numStartingServers]
|
|
||||||
|
|
||||||
var roots6 []netip.Addr
|
|
||||||
if !r.NoIPv6 {
|
|
||||||
roots6 = slices.Clone(rootServersV6)
|
|
||||||
slicesx.Shuffle(roots6)
|
|
||||||
roots6 = roots6[:numStartingServers]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interleave the root servers so that we try to contact them over
|
|
||||||
// IPv4, then IPv6, IPv4, IPv6, etc.
|
|
||||||
rootServers = slicesx.Interleave(roots4, roots6)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &queryState{
|
|
||||||
rootServers: rootServers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve will perform a recursive DNS resolution for the provided name,
|
|
||||||
// starting at a randomly-chosen root DNS server, and return the A and AAAA
|
|
||||||
// responses as a slice of netip.Addrs along with the minimum TTL for the
|
|
||||||
// returned records.
|
|
||||||
func (r *Resolver) Resolve(ctx context.Context, name string) (addrs []netip.Addr, minTTL time.Duration, err error) {
|
|
||||||
dnsName, err := dnsname.ToFQDN(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
qstate := r.newState()
|
|
||||||
|
|
||||||
r.logf("querying IPv4 addresses for: %q", name)
|
|
||||||
addrs4, minTTL4, err4 := r.resolveRecursiveFromRoot(ctx, qstate, 0, dnsName, qtypeA)
|
|
||||||
|
|
||||||
var (
|
|
||||||
addrs6 []netip.Addr
|
|
||||||
minTTL6 time.Duration
|
|
||||||
err6 error
|
|
||||||
)
|
|
||||||
if !r.NoIPv6 {
|
|
||||||
r.logf("querying IPv6 addresses for: %q", name)
|
|
||||||
addrs6, minTTL6, err6 = r.resolveRecursiveFromRoot(ctx, qstate, 0, dnsName, qtypeAAAA)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err4 != nil && err6 != nil {
|
|
||||||
if err4 == err6 {
|
|
||||||
return nil, 0, err4
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, 0, multierr.New(err4, err6)
|
|
||||||
}
|
|
||||||
if err4 != nil {
|
|
||||||
return addrs6, minTTL6, nil
|
|
||||||
} else if err6 != nil {
|
|
||||||
return addrs4, minTTL4, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
minTTL = minTTL4
|
|
||||||
if minTTL6 < minTTL {
|
|
||||||
minTTL = minTTL6
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs = append(addrs4, addrs6...)
|
|
||||||
if len(addrs) == 0 {
|
|
||||||
return nil, 0, ErrNoResponses
|
|
||||||
}
|
|
||||||
|
|
||||||
slicesx.Shuffle(addrs)
|
|
||||||
return addrs, minTTL, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) resolveRecursiveFromRoot(
|
|
||||||
ctx context.Context,
|
|
||||||
qstate *queryState,
|
|
||||||
depth int,
|
|
||||||
name dnsname.FQDN, // what we're querying
|
|
||||||
qtype dns.Type,
|
|
||||||
) ([]netip.Addr, time.Duration, error) {
|
|
||||||
r.depthlogf(depth, "resolving %q from root (type: %v)", name, qtype)
|
|
||||||
|
|
||||||
var depthError bool
|
|
||||||
for _, server := range qstate.rootServers {
|
|
||||||
addrs, minTTL, err := r.resolveRecursive(ctx, qstate, depth, name, server, qtype)
|
|
||||||
if err == nil {
|
|
||||||
return addrs, minTTL, err
|
|
||||||
} else if errors.Is(err, ErrAuthoritativeNoResponses) {
|
|
||||||
return nil, 0, ErrAuthoritativeNoResponses
|
|
||||||
} else if errors.Is(err, ErrMaxDepth) {
|
|
||||||
depthError = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if depthError {
|
|
||||||
return nil, 0, ErrMaxDepth
|
|
||||||
}
|
|
||||||
return nil, 0, ErrNoResponses
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) resolveRecursive(
|
|
||||||
ctx context.Context,
|
|
||||||
qstate *queryState,
|
|
||||||
depth int,
|
|
||||||
name dnsname.FQDN, // what we're querying
|
|
||||||
nameserver netip.Addr,
|
|
||||||
qtype dns.Type,
|
|
||||||
) ([]netip.Addr, time.Duration, error) {
|
|
||||||
if depth == maxDepth {
|
|
||||||
r.depthlogf(depth, "not recursing past maximum depth")
|
|
||||||
return nil, 0, ErrMaxDepth
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask this nameserver for an answer.
|
|
||||||
resp, err := r.queryNameserver(ctx, depth, name, nameserver, qtype)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get an actual answer from the nameserver, then return it.
|
|
||||||
var (
|
|
||||||
answers []netip.Addr
|
|
||||||
cnames []dnsname.FQDN
|
|
||||||
minTTL = 24 * 60 * 60 // 24 hours in seconds
|
|
||||||
)
|
|
||||||
for _, answer := range resp.Answer {
|
|
||||||
if crec, ok := answer.(*dns.CNAME); ok {
|
|
||||||
cnameFQDN, err := dnsname.ToFQDN(crec.Target)
|
|
||||||
if err != nil {
|
|
||||||
r.logf("bad CNAME %q returned: %v", crec.Target, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
cnames = append(cnames, cnameFQDN)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := addrFromRecord(answer)
|
|
||||||
if !addr.IsValid() {
|
|
||||||
r.logf("[unexpected] invalid record in %T answer", answer)
|
|
||||||
} else if addr.Is4() && qtype != qtypeA {
|
|
||||||
r.logf("[unexpected] got IPv4 answer but qtype=%v", qtype)
|
|
||||||
} else if addr.Is6() && qtype != qtypeAAAA {
|
|
||||||
r.logf("[unexpected] got IPv6 answer but qtype=%v", qtype)
|
|
||||||
} else {
|
|
||||||
answers = append(answers, addr)
|
|
||||||
minTTL = min(minTTL, int(answer.Header().Ttl))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(answers) > 0 {
|
|
||||||
r.depthlogf(depth, "got answers for %q: %v", name, answers)
|
|
||||||
return answers, time.Duration(minTTL) * time.Second, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r.depthlogf(depth, "no answers for %q", name)
|
|
||||||
|
|
||||||
// If we have a non-zero number of CNAMEs, then try resolving those
|
|
||||||
// (from the root again) and return the first one that succeeds.
|
|
||||||
//
|
|
||||||
// TODO: return the union of all responses?
|
|
||||||
// TODO: parallelism?
|
|
||||||
if len(cnames) > 0 {
|
|
||||||
r.depthlogf(depth, "got CNAME responses for %q: %v", name, cnames)
|
|
||||||
}
|
|
||||||
var cnameDepthError bool
|
|
||||||
for _, cname := range cnames {
|
|
||||||
answers, minTTL, err := r.resolveRecursiveFromRoot(ctx, qstate, depth+1, cname, qtype)
|
|
||||||
if err == nil {
|
|
||||||
return answers, minTTL, nil
|
|
||||||
} else if errors.Is(err, ErrAuthoritativeNoResponses) {
|
|
||||||
return nil, 0, ErrAuthoritativeNoResponses
|
|
||||||
} else if errors.Is(err, ErrMaxDepth) {
|
|
||||||
cnameDepthError = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is an authoritative response, then we know that continuing
|
|
||||||
// to look further is not going to result in any answers and we should
|
|
||||||
// bail out.
|
|
||||||
if resp.MsgHdr.Authoritative {
|
|
||||||
// If we failed to recurse into a CNAME due to a depth limit,
|
|
||||||
// propagate that here.
|
|
||||||
if cnameDepthError {
|
|
||||||
return nil, 0, ErrMaxDepth
|
|
||||||
}
|
|
||||||
|
|
||||||
r.depthlogf(depth, "got authoritative response with no answers; stopping")
|
|
||||||
return nil, 0, ErrAuthoritativeNoResponses
|
|
||||||
}
|
|
||||||
|
|
||||||
r.depthlogf(depth, "got %d NS responses and %d ADDITIONAL responses for %q", len(resp.Ns), len(resp.Extra), name)
|
|
||||||
|
|
||||||
// No CNAMEs and no answers; see if we got any AUTHORITY responses,
|
|
||||||
// which indicate which nameservers to query next.
|
|
||||||
var authorities []dnsname.FQDN
|
|
||||||
for _, rr := range resp.Ns {
|
|
||||||
ns, ok := rr.(*dns.NS)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
nsName, err := dnsname.ToFQDN(ns.Ns)
|
|
||||||
if err != nil {
|
|
||||||
r.logf("unexpected bad NS name %q: %v", ns.Ns, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
authorities = append(authorities, nsName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also check for "glue" records, which are IP addresses provided by
|
|
||||||
// the DNS server for authority responses; these are required when the
|
|
||||||
// authority server is a subdomain of what's being resolved.
|
|
||||||
glueRecords := make(map[dnsname.FQDN][]netip.Addr)
|
|
||||||
for _, rr := range resp.Extra {
|
|
||||||
name, err := dnsname.ToFQDN(rr.Header().Name)
|
|
||||||
if err != nil {
|
|
||||||
r.logf("unexpected bad Name %q in Extra addr: %v", rr.Header().Name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if addr := addrFromRecord(rr); addr.IsValid() {
|
|
||||||
glueRecords[name] = append(glueRecords[name], addr)
|
|
||||||
} else {
|
|
||||||
r.logf("unexpected bad Extra %T addr", rr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try authorities with glue records first, to minimize the number of
|
|
||||||
// additional DNS queries that we need to make.
|
|
||||||
authoritiesGlue, authoritiesNoGlue := slicesx.Partition(authorities, func(aa dnsname.FQDN) bool {
|
|
||||||
return len(glueRecords[aa]) > 0
|
|
||||||
})
|
|
||||||
|
|
||||||
authorityDepthError := false
|
|
||||||
|
|
||||||
r.depthlogf(depth, "authorities with glue records for recursion: %v", authoritiesGlue)
|
|
||||||
for _, authority := range authoritiesGlue {
|
|
||||||
for _, nameserver := range glueRecords[authority] {
|
|
||||||
answers, minTTL, err := r.resolveRecursive(ctx, qstate, depth+1, name, nameserver, qtype)
|
|
||||||
if err == nil {
|
|
||||||
return answers, minTTL, nil
|
|
||||||
} else if errors.Is(err, ErrAuthoritativeNoResponses) {
|
|
||||||
return nil, 0, ErrAuthoritativeNoResponses
|
|
||||||
} else if errors.Is(err, ErrMaxDepth) {
|
|
||||||
authorityDepthError = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.depthlogf(depth, "authorities with no glue records for recursion: %v", authoritiesNoGlue)
|
|
||||||
for _, authority := range authoritiesNoGlue {
|
|
||||||
// First, resolve the IP for the authority server from the
|
|
||||||
// root, querying for both IPv4 and IPv6 addresses regardless
|
|
||||||
// of what the current question type is.
|
|
||||||
//
|
|
||||||
// TODO: check for infinite recursion; it'll get caught by our
|
|
||||||
// recursion depth, but we want to bail early.
|
|
||||||
for _, authorityQtype := range []dns.Type{qtypeAAAA, qtypeA} {
|
|
||||||
answers, _, err := r.resolveRecursiveFromRoot(ctx, qstate, depth+1, authority, authorityQtype)
|
|
||||||
if err != nil {
|
|
||||||
r.depthlogf(depth, "error querying authority %q: %v", authority, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.depthlogf(depth, "resolved authority %q (type %v) to: %v", authority, authorityQtype, answers)
|
|
||||||
|
|
||||||
// Now, query this authority for the final address.
|
|
||||||
for _, nameserver := range answers {
|
|
||||||
answers, minTTL, err := r.resolveRecursive(ctx, qstate, depth+1, name, nameserver, qtype)
|
|
||||||
if err == nil {
|
|
||||||
return answers, minTTL, nil
|
|
||||||
} else if errors.Is(err, ErrAuthoritativeNoResponses) {
|
|
||||||
return nil, 0, ErrAuthoritativeNoResponses
|
|
||||||
} else if errors.Is(err, ErrMaxDepth) {
|
|
||||||
authorityDepthError = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if authorityDepthError {
|
|
||||||
return nil, 0, ErrMaxDepth
|
|
||||||
}
|
|
||||||
return nil, 0, ErrNoResponses
|
|
||||||
}
|
|
||||||
|
|
||||||
func min[T constraints.Ordered](a, b T) T {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// queryNameserver sends a query for "name" to the nameserver "nameserver" for
|
|
||||||
// records of type "qtype", trying both UDP and TCP connections as
|
|
||||||
// appropriate.
|
|
||||||
func (r *Resolver) queryNameserver(
|
|
||||||
ctx context.Context,
|
|
||||||
depth int,
|
|
||||||
name dnsname.FQDN, // what we're querying
|
|
||||||
nameserver netip.Addr, // destination of query
|
|
||||||
qtype dns.Type,
|
|
||||||
) (*dns.Msg, error) {
|
|
||||||
// TODO(andrew): we should QNAME minimisation here to avoid sending the
|
|
||||||
// full name to intermediate/root nameservers. See:
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc7816
|
|
||||||
|
|
||||||
// Handle the case where UDP is blocked by adding an explicit timeout
|
|
||||||
// for the UDP portion of this query.
|
|
||||||
udpCtx, udpCtxCancel := context.WithTimeout(ctx, udpQueryTimeout)
|
|
||||||
defer udpCtxCancel()
|
|
||||||
|
|
||||||
msg, err := r.queryNameserverProto(udpCtx, depth, name, nameserver, "udp", qtype)
|
|
||||||
if err == nil {
|
|
||||||
return msg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err2 := r.queryNameserverProto(ctx, depth, name, nameserver, "tcp", qtype)
|
|
||||||
if err2 == nil {
|
|
||||||
return msg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, multierr.New(err, err2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// queryNameserverProto sends a query for "name" to the nameserver "nameserver"
|
|
||||||
// for records of type "qtype" over the provided protocol (either "udp"
|
|
||||||
// or "tcp"), and returns the DNS response or an error.
|
|
||||||
func (r *Resolver) queryNameserverProto(
|
|
||||||
ctx context.Context,
|
|
||||||
depth int,
|
|
||||||
name dnsname.FQDN, // what we're querying
|
|
||||||
nameserver netip.Addr, // destination of query
|
|
||||||
protocol string,
|
|
||||||
qtype dns.Type,
|
|
||||||
) (resp *dns.Msg, err error) {
|
|
||||||
if r.testQueryHook != nil {
|
|
||||||
return r.testQueryHook(name, nameserver, protocol, qtype)
|
|
||||||
}
|
|
||||||
|
|
||||||
now := r.now()
|
|
||||||
nameserverStr := nameserver.String()
|
|
||||||
|
|
||||||
cacheKey := dnsQuery{
|
|
||||||
nameserver: nameserver,
|
|
||||||
name: name,
|
|
||||||
qtype: qtype,
|
|
||||||
}
|
|
||||||
cacheEntry, ok := r.queryCache[cacheKey]
|
|
||||||
if ok && cacheEntry.expiresAt.Before(now) {
|
|
||||||
r.depthlogf(depth, "using cached response from %s about %q (type: %v)", nameserverStr, name, qtype)
|
|
||||||
return cacheEntry.Msg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var network string
|
|
||||||
if nameserver.Is4() {
|
|
||||||
network = protocol + "4"
|
|
||||||
} else {
|
|
||||||
network = protocol + "6"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare a message asking for an appropriately-typed record
|
|
||||||
// for the name we're querying.
|
|
||||||
m := new(dns.Msg)
|
|
||||||
m.SetQuestion(name.WithTrailingDot(), uint16(qtype))
|
|
||||||
|
|
||||||
// Allow mocking out the network components with our exchange hook.
|
|
||||||
if r.testExchangeHook != nil {
|
|
||||||
resp, err = r.testExchangeHook(nameserver, network, m)
|
|
||||||
} else {
|
|
||||||
// Dial the current nameserver using our dialer.
|
|
||||||
var nconn net.Conn
|
|
||||||
nconn, err = r.dialer().DialContext(ctx, network, net.JoinHostPort(nameserverStr, "53"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var c dns.Client // TODO: share?
|
|
||||||
conn := &dns.Conn{
|
|
||||||
Conn: nconn,
|
|
||||||
UDPSize: c.UDPSize,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the DNS request to the current nameserver.
|
|
||||||
r.depthlogf(depth, "asking %s over %s about %q (type: %v)", nameserverStr, protocol, name, qtype)
|
|
||||||
resp, _, err = c.ExchangeWithConnContext(ctx, m, conn)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the message was truncated and we're using UDP, re-run with TCP.
|
|
||||||
if resp.MsgHdr.Truncated && protocol == "udp" {
|
|
||||||
r.depthlogf(depth, "response message truncated; re-running query with TCP")
|
|
||||||
resp, err = r.queryNameserverProto(ctx, depth, name, nameserver, "tcp", qtype)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find minimum expiry for all records in this message.
|
|
||||||
var minTTL int
|
|
||||||
for _, rr := range resp.Answer {
|
|
||||||
minTTL = min(minTTL, int(rr.Header().Ttl))
|
|
||||||
}
|
|
||||||
for _, rr := range resp.Ns {
|
|
||||||
minTTL = min(minTTL, int(rr.Header().Ttl))
|
|
||||||
}
|
|
||||||
for _, rr := range resp.Extra {
|
|
||||||
minTTL = min(minTTL, int(rr.Header().Ttl))
|
|
||||||
}
|
|
||||||
|
|
||||||
mak.Set(&r.queryCache, cacheKey, dnsMsgWithExpiry{
|
|
||||||
Msg: resp,
|
|
||||||
expiresAt: now.Add(time.Duration(minTTL) * time.Second),
|
|
||||||
})
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addrFromRecord(rr dns.RR) netip.Addr {
|
|
||||||
switch v := rr.(type) {
|
|
||||||
case *dns.A:
|
|
||||||
ip, ok := netip.AddrFromSlice(v.A)
|
|
||||||
if !ok || !ip.Is4() {
|
|
||||||
return netip.Addr{}
|
|
||||||
}
|
|
||||||
return ip
|
|
||||||
case *dns.AAAA:
|
|
||||||
ip, ok := netip.AddrFromSlice(v.AAAA)
|
|
||||||
if !ok || !ip.Is6() {
|
|
||||||
return netip.Addr{}
|
|
||||||
}
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
return netip.Addr{}
|
|
||||||
}
|
|
|
@ -1,741 +0,0 @@
|
||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
package recursive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"tailscale.com/envknob"
|
|
||||||
"tailscale.com/tstest"
|
|
||||||
)
|
|
||||||
|
|
||||||
const testDomain = "tailscale.com"
|
|
||||||
|
|
||||||
// Recursively resolving the AWS console requires being able to handle CNAMEs,
|
|
||||||
// glue records, falling back from UDP to TCP for oversize queries, and more;
|
|
||||||
// it's a great integration test for DNS resolution and they can handle the
|
|
||||||
// traffic :)
|
|
||||||
const complicatedTestDomain = "console.aws.amazon.com"
|
|
||||||
|
|
||||||
var flagNetworkAccess = flag.Bool("enable-network-access", false, "run tests that need external network access")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
envknob.Setenv("TS_DEBUG_RECURSIVE_DNS", "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newResolver(tb testing.TB) *Resolver {
|
|
||||||
clock := tstest.NewClock(tstest.ClockOpts{
|
|
||||||
Step: 50 * time.Millisecond,
|
|
||||||
})
|
|
||||||
return &Resolver{
|
|
||||||
Logf: tb.Logf,
|
|
||||||
timeNow: clock.Now,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResolve(t *testing.T) {
|
|
||||||
if !*flagNetworkAccess {
|
|
||||||
t.SkipNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
r := newResolver(t)
|
|
||||||
addrs, minTTL, err := r.Resolve(ctx, testDomain)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("addrs: %+v", addrs)
|
|
||||||
t.Logf("minTTL: %v", minTTL)
|
|
||||||
if len(addrs) < 1 {
|
|
||||||
t.Fatalf("expected at least one address")
|
|
||||||
}
|
|
||||||
|
|
||||||
if minTTL <= 10*time.Second || minTTL >= 24*time.Hour {
|
|
||||||
t.Errorf("invalid minimum TTL: %v", minTTL)
|
|
||||||
}
|
|
||||||
|
|
||||||
var has4, has6 bool
|
|
||||||
for _, addr := range addrs {
|
|
||||||
has4 = has4 || addr.Is4()
|
|
||||||
has6 = has6 || addr.Is6()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !has4 {
|
|
||||||
t.Errorf("expected at least one IPv4 address")
|
|
||||||
}
|
|
||||||
if !has6 {
|
|
||||||
t.Errorf("expected at least one IPv6 address")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResolveComplicated(t *testing.T) {
|
|
||||||
if !*flagNetworkAccess {
|
|
||||||
t.SkipNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
r := newResolver(t)
|
|
||||||
addrs, minTTL, err := r.Resolve(ctx, complicatedTestDomain)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("addrs: %+v", addrs)
|
|
||||||
t.Logf("minTTL: %v", minTTL)
|
|
||||||
if len(addrs) < 1 {
|
|
||||||
t.Fatalf("expected at least one address")
|
|
||||||
}
|
|
||||||
|
|
||||||
if minTTL <= 10*time.Second || minTTL >= 24*time.Hour {
|
|
||||||
t.Errorf("invalid minimum TTL: %v", minTTL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResolveNoIPv6(t *testing.T) {
|
|
||||||
if !*flagNetworkAccess {
|
|
||||||
t.SkipNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
r := newResolver(t)
|
|
||||||
r.NoIPv6 = true
|
|
||||||
|
|
||||||
addrs, _, err := r.Resolve(context.Background(), testDomain)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("addrs: %+v", addrs)
|
|
||||||
if len(addrs) < 1 {
|
|
||||||
t.Fatalf("expected at least one address")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range addrs {
|
|
||||||
if addr.Is6() {
|
|
||||||
t.Errorf("got unexpected IPv6 address: %v", addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResolveFallbackToTCP(t *testing.T) {
|
|
||||||
var udpCalls, tcpCalls int
|
|
||||||
hook := func(nameserver netip.Addr, network string, req *dns.Msg) (*dns.Msg, error) {
|
|
||||||
if strings.HasPrefix(network, "udp") {
|
|
||||||
t.Logf("got %q query; returning truncated result", network)
|
|
||||||
udpCalls++
|
|
||||||
resp := &dns.Msg{}
|
|
||||||
resp.SetReply(req)
|
|
||||||
resp.Truncated = true
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("got %q query; returning real result", network)
|
|
||||||
tcpCalls++
|
|
||||||
resp := &dns.Msg{}
|
|
||||||
resp.SetReply(req)
|
|
||||||
resp.Answer = append(resp.Answer, &dns.A{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: req.Question[0].Name,
|
|
||||||
Rrtype: req.Question[0].Qtype,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 300,
|
|
||||||
},
|
|
||||||
A: net.IPv4(1, 2, 3, 4),
|
|
||||||
})
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r := newResolver(t)
|
|
||||||
r.testExchangeHook = hook
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
resp, err := r.queryNameserverProto(ctx, 0, "tailscale.com", netip.MustParseAddr("9.9.9.9"), "udp", dns.Type(dns.TypeA))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resp.Answer) < 1 {
|
|
||||||
t.Fatalf("no answers in response: %v", resp)
|
|
||||||
}
|
|
||||||
rrA, ok := resp.Answer[0].(*dns.A)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("invalid RR type: %T", resp.Answer[0])
|
|
||||||
}
|
|
||||||
if !rrA.A.Equal(net.IPv4(1, 2, 3, 4)) {
|
|
||||||
t.Errorf("wanted A response 1.2.3.4, got: %v", rrA.A)
|
|
||||||
}
|
|
||||||
if tcpCalls != 1 {
|
|
||||||
t.Errorf("got %d, want 1 TCP calls", tcpCalls)
|
|
||||||
}
|
|
||||||
if udpCalls != 1 {
|
|
||||||
t.Errorf("got %d, want 1 UDP calls", udpCalls)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that we're cached and re-run to fetch from the cache.
|
|
||||||
if len(r.queryCache) < 1 {
|
|
||||||
t.Errorf("wanted entries in the query cache")
|
|
||||||
}
|
|
||||||
|
|
||||||
resp2, err := r.queryNameserverProto(ctx, 0, "tailscale.com", netip.MustParseAddr("9.9.9.9"), "udp", dns.Type(dns.TypeA))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(resp, resp2) {
|
|
||||||
t.Errorf("expected equal responses; old=%+v new=%+v", resp, resp2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We didn't make any more network requests since we loaded from the cache.
|
|
||||||
if tcpCalls != 1 {
|
|
||||||
t.Errorf("got %d, want 1 TCP calls", tcpCalls)
|
|
||||||
}
|
|
||||||
if udpCalls != 1 {
|
|
||||||
t.Errorf("got %d, want 1 UDP calls", udpCalls)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dnsIPRR(name string, addr netip.Addr) dns.RR {
|
|
||||||
if addr.Is4() {
|
|
||||||
return &dns.A{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: name,
|
|
||||||
Rrtype: dns.TypeA,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 300,
|
|
||||||
},
|
|
||||||
A: net.IP(addr.AsSlice()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &dns.AAAA{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: name,
|
|
||||||
Rrtype: dns.TypeAAAA,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 300,
|
|
||||||
},
|
|
||||||
AAAA: net.IP(addr.AsSlice()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cnameRR(name, target string) dns.RR {
|
|
||||||
return &dns.CNAME{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: name,
|
|
||||||
Rrtype: dns.TypeCNAME,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 300,
|
|
||||||
},
|
|
||||||
Target: target,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func nsRR(name, target string) dns.RR {
|
|
||||||
return &dns.NS{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: name,
|
|
||||||
Rrtype: dns.TypeNS,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 300,
|
|
||||||
},
|
|
||||||
Ns: target,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockReply struct {
|
|
||||||
name string
|
|
||||||
qtype dns.Type
|
|
||||||
resp *dns.Msg
|
|
||||||
}
|
|
||||||
|
|
||||||
type replyMock struct {
|
|
||||||
tb testing.TB
|
|
||||||
replies map[netip.Addr][]mockReply
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *replyMock) exchangeHook(nameserver netip.Addr, network string, req *dns.Msg) (*dns.Msg, error) {
|
|
||||||
if len(req.Question) != 1 {
|
|
||||||
r.tb.Fatalf("unsupported multiple or empty question: %v", req.Question)
|
|
||||||
}
|
|
||||||
question := req.Question[0]
|
|
||||||
|
|
||||||
replies := r.replies[nameserver]
|
|
||||||
if len(replies) == 0 {
|
|
||||||
r.tb.Fatalf("no configured replies for nameserver: %v", nameserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, reply := range replies {
|
|
||||||
if reply.name == question.Name && reply.qtype == dns.Type(question.Qtype) {
|
|
||||||
return reply.resp.Copy(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.tb.Fatalf("no replies found for query %q of type %v to %v", question.Name, question.Qtype, nameserver)
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// responses for mocking, shared between the following tests
|
|
||||||
var (
|
|
||||||
rootServerAddr = netip.MustParseAddr("198.41.0.4") // a.root-servers.net.
|
|
||||||
comNSAddr = netip.MustParseAddr("192.5.6.30") // a.gtld-servers.net.
|
|
||||||
|
|
||||||
// DNS response from the root nameservers for a .com nameserver
|
|
||||||
comRecord = &dns.Msg{
|
|
||||||
Ns: []dns.RR{nsRR("com.", "a.gtld-servers.net.")},
|
|
||||||
Extra: []dns.RR{dnsIPRR("a.gtld-servers.net.", comNSAddr)},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Random Amazon nameservers that we use in glue records
|
|
||||||
amazonNS = netip.MustParseAddr("205.251.192.197")
|
|
||||||
amazonNSv6 = netip.MustParseAddr("2600:9000:5306:1600::1")
|
|
||||||
|
|
||||||
// Nameservers for the tailscale.com domain
|
|
||||||
tailscaleNameservers = &dns.Msg{
|
|
||||||
Ns: []dns.RR{
|
|
||||||
nsRR("tailscale.com.", "ns-197.awsdns-24.com."),
|
|
||||||
nsRR("tailscale.com.", "ns-557.awsdns-05.net."),
|
|
||||||
nsRR("tailscale.com.", "ns-1558.awsdns-02.co.uk."),
|
|
||||||
nsRR("tailscale.com.", "ns-1359.awsdns-41.org."),
|
|
||||||
},
|
|
||||||
Extra: []dns.RR{
|
|
||||||
dnsIPRR("ns-197.awsdns-24.com.", amazonNS),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBasicRecursion(t *testing.T) {
|
|
||||||
mock := &replyMock{
|
|
||||||
tb: t,
|
|
||||||
replies: map[netip.Addr][]mockReply{
|
|
||||||
// Query to the root server returns the .com server + a glue record
|
|
||||||
rootServerAddr: {
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeA), resp: comRecord},
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: comRecord},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Query to the ".com" server return the nameservers for tailscale.com
|
|
||||||
comNSAddr: {
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeA), resp: tailscaleNameservers},
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: tailscaleNameservers},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Query to the actual nameserver works.
|
|
||||||
amazonNS: {
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{
|
|
||||||
dnsIPRR("tailscale.com.", netip.MustParseAddr("13.248.141.131")),
|
|
||||||
dnsIPRR("tailscale.com.", netip.MustParseAddr("76.223.15.28")),
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{
|
|
||||||
dnsIPRR("tailscale.com.", netip.MustParseAddr("2600:9000:a602:b1e6:86d:8165:5e8c:295b")),
|
|
||||||
dnsIPRR("tailscale.com.", netip.MustParseAddr("2600:9000:a51d:27c1:1530:b9ef:2a6:b9e5")),
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
r := newResolver(t)
|
|
||||||
r.testExchangeHook = mock.exchangeHook
|
|
||||||
r.rootServers = []netip.Addr{rootServerAddr}
|
|
||||||
|
|
||||||
// Query for tailscale.com, verify we get the right responses
|
|
||||||
ctx := context.Background()
|
|
||||||
addrs, minTTL, err := r.Resolve(ctx, "tailscale.com")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
wantAddrs := []netip.Addr{
|
|
||||||
netip.MustParseAddr("13.248.141.131"),
|
|
||||||
netip.MustParseAddr("76.223.15.28"),
|
|
||||||
netip.MustParseAddr("2600:9000:a602:b1e6:86d:8165:5e8c:295b"),
|
|
||||||
netip.MustParseAddr("2600:9000:a51d:27c1:1530:b9ef:2a6:b9e5"),
|
|
||||||
}
|
|
||||||
slices.SortFunc(addrs, func(x, y netip.Addr) bool { return x.String() < y.String() })
|
|
||||||
slices.SortFunc(wantAddrs, func(x, y netip.Addr) bool { return x.String() < y.String() })
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(addrs, wantAddrs) {
|
|
||||||
t.Errorf("got addrs=%+v; want %+v", addrs, wantAddrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
const wantMinTTL = 5 * time.Minute
|
|
||||||
if minTTL != wantMinTTL {
|
|
||||||
t.Errorf("got minTTL=%+v; want %+v", minTTL, wantMinTTL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoAnswers(t *testing.T) {
|
|
||||||
mock := &replyMock{
|
|
||||||
tb: t,
|
|
||||||
replies: map[netip.Addr][]mockReply{
|
|
||||||
// Query to the root server returns the .com server + a glue record
|
|
||||||
rootServerAddr: {
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeA), resp: comRecord},
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: comRecord},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Query to the ".com" server return the nameservers for tailscale.com
|
|
||||||
comNSAddr: {
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeA), resp: tailscaleNameservers},
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: tailscaleNameservers},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Query to the actual nameserver returns no responses, authoritatively.
|
|
||||||
amazonNS: {
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{},
|
|
||||||
}},
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
r := &Resolver{
|
|
||||||
Logf: t.Logf,
|
|
||||||
testExchangeHook: mock.exchangeHook,
|
|
||||||
rootServers: []netip.Addr{rootServerAddr},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query for tailscale.com, verify we get the right responses
|
|
||||||
_, _, err := r.Resolve(context.Background(), "tailscale.com")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("got no error, want error")
|
|
||||||
}
|
|
||||||
if !errors.Is(err, ErrAuthoritativeNoResponses) {
|
|
||||||
t.Fatalf("got err=%v, want %v", err, ErrAuthoritativeNoResponses)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecursionCNAME(t *testing.T) {
|
|
||||||
mock := &replyMock{
|
|
||||||
tb: t,
|
|
||||||
replies: map[netip.Addr][]mockReply{
|
|
||||||
// Query to the root server returns the .com server + a glue record
|
|
||||||
rootServerAddr: {
|
|
||||||
{name: "subdomain.otherdomain.com.", qtype: dns.Type(dns.TypeA), resp: comRecord},
|
|
||||||
{name: "subdomain.otherdomain.com.", qtype: dns.Type(dns.TypeAAAA), resp: comRecord},
|
|
||||||
|
|
||||||
{name: "subdomain.tailscale.com.", qtype: dns.Type(dns.TypeA), resp: comRecord},
|
|
||||||
{name: "subdomain.tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: comRecord},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Query to the ".com" server return the nameservers for tailscale.com
|
|
||||||
comNSAddr: {
|
|
||||||
{name: "subdomain.otherdomain.com.", qtype: dns.Type(dns.TypeA), resp: tailscaleNameservers},
|
|
||||||
{name: "subdomain.otherdomain.com.", qtype: dns.Type(dns.TypeAAAA), resp: tailscaleNameservers},
|
|
||||||
|
|
||||||
{name: "subdomain.tailscale.com.", qtype: dns.Type(dns.TypeA), resp: tailscaleNameservers},
|
|
||||||
{name: "subdomain.tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: tailscaleNameservers},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Query to the actual nameserver works.
|
|
||||||
amazonNS: {
|
|
||||||
{name: "subdomain.otherdomain.com.", qtype: dns.Type(dns.TypeA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{cnameRR("subdomain.otherdomain.com.", "subdomain.tailscale.com.")},
|
|
||||||
}},
|
|
||||||
{name: "subdomain.otherdomain.com.", qtype: dns.Type(dns.TypeAAAA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{cnameRR("subdomain.otherdomain.com.", "subdomain.tailscale.com.")},
|
|
||||||
}},
|
|
||||||
|
|
||||||
{name: "subdomain.tailscale.com.", qtype: dns.Type(dns.TypeA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{dnsIPRR("tailscale.com.", netip.MustParseAddr("13.248.141.131"))},
|
|
||||||
}},
|
|
||||||
{name: "subdomain.tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{dnsIPRR("tailscale.com.", netip.MustParseAddr("2600:9000:a602:b1e6:86d:8165:5e8c:295b"))},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
r := &Resolver{
|
|
||||||
Logf: t.Logf,
|
|
||||||
testExchangeHook: mock.exchangeHook,
|
|
||||||
rootServers: []netip.Addr{rootServerAddr},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query for tailscale.com, verify we get the right responses
|
|
||||||
addrs, minTTL, err := r.Resolve(context.Background(), "subdomain.otherdomain.com")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
wantAddrs := []netip.Addr{
|
|
||||||
netip.MustParseAddr("13.248.141.131"),
|
|
||||||
netip.MustParseAddr("2600:9000:a602:b1e6:86d:8165:5e8c:295b"),
|
|
||||||
}
|
|
||||||
slices.SortFunc(addrs, func(x, y netip.Addr) bool { return x.String() < y.String() })
|
|
||||||
slices.SortFunc(wantAddrs, func(x, y netip.Addr) bool { return x.String() < y.String() })
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(addrs, wantAddrs) {
|
|
||||||
t.Errorf("got addrs=%+v; want %+v", addrs, wantAddrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
const wantMinTTL = 5 * time.Minute
|
|
||||||
if minTTL != wantMinTTL {
|
|
||||||
t.Errorf("got minTTL=%+v; want %+v", minTTL, wantMinTTL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecursionNoGlue(t *testing.T) {
|
|
||||||
coukNS := netip.MustParseAddr("213.248.216.1")
|
|
||||||
coukRecord := &dns.Msg{
|
|
||||||
Ns: []dns.RR{nsRR("com.", "dns1.nic.uk.")},
|
|
||||||
Extra: []dns.RR{dnsIPRR("dns1.nic.uk.", coukNS)},
|
|
||||||
}
|
|
||||||
|
|
||||||
intermediateNS := netip.MustParseAddr("205.251.193.66") // g-ns-322.awsdns-02.co.uk.
|
|
||||||
intermediateRecord := &dns.Msg{
|
|
||||||
Ns: []dns.RR{nsRR("awsdns-02.co.uk.", "g-ns-322.awsdns-02.co.uk.")},
|
|
||||||
Extra: []dns.RR{dnsIPRR("g-ns-322.awsdns-02.co.uk.", intermediateNS)},
|
|
||||||
}
|
|
||||||
|
|
||||||
const amazonNameserver = "ns-1558.awsdns-02.co.uk."
|
|
||||||
tailscaleNameservers := &dns.Msg{
|
|
||||||
Ns: []dns.RR{
|
|
||||||
nsRR("tailscale.com.", amazonNameserver),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
tailscaleResponses := []mockReply{
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{dnsIPRR("tailscale.com.", netip.MustParseAddr("13.248.141.131"))},
|
|
||||||
}},
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{dnsIPRR("tailscale.com.", netip.MustParseAddr("2600:9000:a602:b1e6:86d:8165:5e8c:295b"))},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
|
|
||||||
mock := &replyMock{
|
|
||||||
tb: t,
|
|
||||||
replies: map[netip.Addr][]mockReply{
|
|
||||||
rootServerAddr: {
|
|
||||||
// Query to the root server returns the .com server + a glue record
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeA), resp: comRecord},
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: comRecord},
|
|
||||||
|
|
||||||
// Querying the .co.uk nameserver returns the .co.uk nameserver + a glue record.
|
|
||||||
{name: amazonNameserver, qtype: dns.Type(dns.TypeA), resp: coukRecord},
|
|
||||||
{name: amazonNameserver, qtype: dns.Type(dns.TypeAAAA), resp: coukRecord},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Queries to the ".com" server return the nameservers
|
|
||||||
// for tailscale.com, which don't contain a glue
|
|
||||||
// record.
|
|
||||||
comNSAddr: {
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeA), resp: tailscaleNameservers},
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: tailscaleNameservers},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Queries to the ".co.uk" nameserver returns the
|
|
||||||
// address of the intermediate Amazon nameserver.
|
|
||||||
coukNS: {
|
|
||||||
{name: amazonNameserver, qtype: dns.Type(dns.TypeA), resp: intermediateRecord},
|
|
||||||
{name: amazonNameserver, qtype: dns.Type(dns.TypeAAAA), resp: intermediateRecord},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Queries to the intermediate nameserver returns an
|
|
||||||
// answer for the final Amazon nameserver.
|
|
||||||
intermediateNS: {
|
|
||||||
{name: amazonNameserver, qtype: dns.Type(dns.TypeA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{dnsIPRR(amazonNameserver, amazonNS)},
|
|
||||||
}},
|
|
||||||
{name: amazonNameserver, qtype: dns.Type(dns.TypeAAAA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{dnsIPRR(amazonNameserver, amazonNSv6)},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Queries to the actual nameserver work and return
|
|
||||||
// responses to the query.
|
|
||||||
amazonNS: tailscaleResponses,
|
|
||||||
amazonNSv6: tailscaleResponses,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
r := newResolver(t)
|
|
||||||
r.testExchangeHook = mock.exchangeHook
|
|
||||||
r.rootServers = []netip.Addr{rootServerAddr}
|
|
||||||
|
|
||||||
// Query for tailscale.com, verify we get the right responses
|
|
||||||
addrs, minTTL, err := r.Resolve(context.Background(), "tailscale.com")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
wantAddrs := []netip.Addr{
|
|
||||||
netip.MustParseAddr("13.248.141.131"),
|
|
||||||
netip.MustParseAddr("2600:9000:a602:b1e6:86d:8165:5e8c:295b"),
|
|
||||||
}
|
|
||||||
slices.SortFunc(addrs, func(x, y netip.Addr) bool { return x.String() < y.String() })
|
|
||||||
slices.SortFunc(wantAddrs, func(x, y netip.Addr) bool { return x.String() < y.String() })
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(addrs, wantAddrs) {
|
|
||||||
t.Errorf("got addrs=%+v; want %+v", addrs, wantAddrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
const wantMinTTL = 5 * time.Minute
|
|
||||||
if minTTL != wantMinTTL {
|
|
||||||
t.Errorf("got minTTL=%+v; want %+v", minTTL, wantMinTTL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecursionLimit(t *testing.T) {
|
|
||||||
mock := &replyMock{
|
|
||||||
tb: t,
|
|
||||||
replies: map[netip.Addr][]mockReply{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill out a CNAME chain equal to our recursion limit; we won't get
|
|
||||||
// this far since each CNAME is more than 1 level "deep", but this
|
|
||||||
// ensures that we have more than the limit.
|
|
||||||
for i := 0; i < maxDepth+1; i++ {
|
|
||||||
curr := fmt.Sprintf("%d-tailscale.com.", i)
|
|
||||||
|
|
||||||
tailscaleNameservers := &dns.Msg{
|
|
||||||
Ns: []dns.RR{nsRR(curr, "ns-197.awsdns-24.com.")},
|
|
||||||
Extra: []dns.RR{dnsIPRR("ns-197.awsdns-24.com.", amazonNS)},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query to the root server returns the .com server + a glue record
|
|
||||||
mock.replies[rootServerAddr] = append(mock.replies[rootServerAddr],
|
|
||||||
mockReply{name: curr, qtype: dns.Type(dns.TypeA), resp: comRecord},
|
|
||||||
mockReply{name: curr, qtype: dns.Type(dns.TypeAAAA), resp: comRecord},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Query to the ".com" server return the nameservers for NN-tailscale.com
|
|
||||||
mock.replies[comNSAddr] = append(mock.replies[comNSAddr],
|
|
||||||
mockReply{name: curr, qtype: dns.Type(dns.TypeA), resp: tailscaleNameservers},
|
|
||||||
mockReply{name: curr, qtype: dns.Type(dns.TypeAAAA), resp: tailscaleNameservers},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Queries to the nameserver return a CNAME for the n+1th server.
|
|
||||||
next := fmt.Sprintf("%d-tailscale.com.", i+1)
|
|
||||||
mock.replies[amazonNS] = append(mock.replies[amazonNS],
|
|
||||||
mockReply{
|
|
||||||
name: curr,
|
|
||||||
qtype: dns.Type(dns.TypeA),
|
|
||||||
resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{cnameRR(curr, next)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mockReply{
|
|
||||||
name: curr,
|
|
||||||
qtype: dns.Type(dns.TypeAAAA),
|
|
||||||
resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{cnameRR(curr, next)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := newResolver(t)
|
|
||||||
r.testExchangeHook = mock.exchangeHook
|
|
||||||
r.rootServers = []netip.Addr{rootServerAddr}
|
|
||||||
|
|
||||||
// Query for the first node in the chain, 0-tailscale.com, and verify
|
|
||||||
// we get a max-depth error.
|
|
||||||
ctx := context.Background()
|
|
||||||
_, _, err := r.Resolve(ctx, "0-tailscale.com")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected error, got nil")
|
|
||||||
} else if !errors.Is(err, ErrMaxDepth) {
|
|
||||||
t.Fatalf("got err=%v, want ErrMaxDepth", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInvalidResponses(t *testing.T) {
|
|
||||||
mock := &replyMock{
|
|
||||||
tb: t,
|
|
||||||
replies: map[netip.Addr][]mockReply{
|
|
||||||
// Query to the root server returns the .com server + a glue record
|
|
||||||
rootServerAddr: {
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeA), resp: comRecord},
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: comRecord},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Query to the ".com" server return the nameservers for tailscale.com
|
|
||||||
comNSAddr: {
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeA), resp: tailscaleNameservers},
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: tailscaleNameservers},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Query to the actual nameserver returns an invalid IP address
|
|
||||||
amazonNS: {
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
Answer: []dns.RR{&dns.A{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: "tailscale.com.",
|
|
||||||
Rrtype: dns.TypeA,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 300,
|
|
||||||
},
|
|
||||||
// Note: this is an IPv6 addr in an IPv4 response
|
|
||||||
A: net.IP(netip.MustParseAddr("2600:9000:a51d:27c1:1530:b9ef:2a6:b9e5").AsSlice()),
|
|
||||||
}},
|
|
||||||
}},
|
|
||||||
{name: "tailscale.com.", qtype: dns.Type(dns.TypeAAAA), resp: &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{Authoritative: true},
|
|
||||||
// This an IPv4 response to an IPv6 query
|
|
||||||
Answer: []dns.RR{&dns.A{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: "tailscale.com.",
|
|
||||||
Rrtype: dns.TypeA,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 300,
|
|
||||||
},
|
|
||||||
A: net.IP(netip.MustParseAddr("13.248.141.131").AsSlice()),
|
|
||||||
}},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
r := &Resolver{
|
|
||||||
Logf: t.Logf,
|
|
||||||
testExchangeHook: mock.exchangeHook,
|
|
||||||
rootServers: []netip.Addr{rootServerAddr},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query for tailscale.com, verify we get no responses since the
|
|
||||||
// addresses are invalid.
|
|
||||||
_, _, err := r.Resolve(context.Background(), "tailscale.com")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("got no error, want error")
|
|
||||||
}
|
|
||||||
if !errors.Is(err, ErrAuthoritativeNoResponses) {
|
|
||||||
t.Fatalf("got err=%v, want %v", err, ErrAuthoritativeNoResponses)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(andrew): test for more edge cases that aren't currently covered:
|
|
||||||
// * Nameservers that cross between IPv4 and IPv6
|
|
||||||
// * Authoritative no replies after following CNAME
|
|
||||||
// * Authoritative no replies after following non-glue NS record
|
|
||||||
// * Error querying non-glue NS record followed by success
|
|
|
@ -724,7 +724,7 @@ func (r *Resolver) parseViaDomain(domain dnsname.FQDN, typ dns.Type) (netip.Addr
|
||||||
return netip.Addr{}, false // badly formed, don't respond
|
return netip.Addr{}, false // badly formed, don't respond
|
||||||
}
|
}
|
||||||
|
|
||||||
// MapVia will never error when given an IPv4 netip.Prefix.
|
// MapVia will never error when given an ipv4 netip.Prefix.
|
||||||
out, _ := tsaddr.MapVia(uint32(prefix), netip.PrefixFrom(ip4, ip4.BitLen()))
|
out, _ := tsaddr.MapVia(uint32(prefix), netip.PrefixFrom(ip4, ip4.BitLen()))
|
||||||
return out.Addr(), true
|
return out.Addr(), true
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,10 @@ func (r *Resolver) cloudHostResolver() (v *net.Resolver, ok bool) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "android", "ios", "darwin":
|
case "android", "ios", "darwin":
|
||||||
return nil, false
|
return nil, false
|
||||||
|
case "windows":
|
||||||
|
// TODO(bradfitz): remove this restriction once we're using Go 1.19
|
||||||
|
// which supports net.Resolver.PreferGo on Windows.
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
ip := cloudenv.Get().ResolverIP()
|
ip := cloudenv.Get().ResolverIP()
|
||||||
if ip == "" {
|
if ip == "" {
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
|
|
||||||
"github.com/golang/groupcache/lru"
|
"github.com/golang/groupcache/lru"
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
"tailscale.com/util/cmpx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MessageCache is a cache that works at the DNS message layer,
|
// MessageCache is a cache that works at the DNS message layer,
|
||||||
|
@ -60,7 +59,10 @@ func (c *MessageCache) Flush() {
|
||||||
// pruneLocked prunes down the cache size to the configured (or
|
// pruneLocked prunes down the cache size to the configured (or
|
||||||
// default) max size.
|
// default) max size.
|
||||||
func (c *MessageCache) pruneLocked() {
|
func (c *MessageCache) pruneLocked() {
|
||||||
max := cmpx.Or(c.cacheSizeSet, 500)
|
max := c.cacheSizeSet
|
||||||
|
if max == 0 {
|
||||||
|
max = 500
|
||||||
|
}
|
||||||
for c.cache.Len() > max {
|
for c.cache.Len() > max {
|
||||||
c.cache.RemoveOldest()
|
c.cache.RemoveOldest()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMessageCache(t *testing.T) {
|
func TestMessageCache(t *testing.T) {
|
||||||
clock := tstest.NewClock(tstest.ClockOpts{
|
clock := &tstest.Clock{
|
||||||
Start: time.Date(1987, 11, 1, 0, 0, 0, 0, time.UTC),
|
Start: time.Date(1987, 11, 1, 0, 0, 0, 0, time.UTC),
|
||||||
})
|
}
|
||||||
mc := &MessageCache{Clock: clock.Now}
|
mc := &MessageCache{Clock: clock.Now}
|
||||||
mc.SetMaxCacheSize(2)
|
mc.SetMaxCacheSize(2)
|
||||||
clock.Advance(time.Second)
|
clock.Advance(time.Second)
|
||||||
|
|
|
@ -22,10 +22,6 @@ type Listener struct {
|
||||||
ch chan Conn
|
ch chan Conn
|
||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
closed chan struct{}
|
closed chan struct{}
|
||||||
|
|
||||||
// NewConn, if non-nil, is called to create a new pair of connections
|
|
||||||
// when dialing. If nil, NewConn is used.
|
|
||||||
NewConn func(network, addr string, maxBuf int) (Conn, Conn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen returns a new Listener for the provided address.
|
// Listen returns a new Listener for the provided address.
|
||||||
|
@ -74,14 +70,7 @@ func (l *Listener) Dial(ctx context.Context, network, addr string) (_ net.Conn,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c, s := NewConn(addr, bufferSize)
|
||||||
newConn := l.NewConn
|
|
||||||
if newConn == nil {
|
|
||||||
newConn = func(network, addr string, maxBuf int) (Conn, Conn) {
|
|
||||||
return NewConn(addr, maxBuf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c, s := newConn(network, addr, bufferSize)
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Close()
|
c.Close()
|
||||||
|
|
|
@ -42,7 +42,6 @@ import (
|
||||||
"tailscale.com/types/opt"
|
"tailscale.com/types/opt"
|
||||||
"tailscale.com/types/ptr"
|
"tailscale.com/types/ptr"
|
||||||
"tailscale.com/util/clientmetric"
|
"tailscale.com/util/clientmetric"
|
||||||
"tailscale.com/util/cmpx"
|
|
||||||
"tailscale.com/util/mak"
|
"tailscale.com/util/mak"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -451,9 +450,10 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *interfaces.State, last *Report)
|
||||||
do6 = false
|
do6 = false
|
||||||
}
|
}
|
||||||
n := reg.Nodes[try%len(reg.Nodes)]
|
n := reg.Nodes[try%len(reg.Nodes)]
|
||||||
prevLatency := cmpx.Or(
|
prevLatency := last.RegionLatency[reg.RegionID] * 120 / 100
|
||||||
last.RegionLatency[reg.RegionID]*120/100,
|
if prevLatency == 0 {
|
||||||
defaultActiveRetransmitTime)
|
prevLatency = defaultActiveRetransmitTime
|
||||||
|
}
|
||||||
delay := time.Duration(try) * prevLatency
|
delay := time.Duration(try) * prevLatency
|
||||||
if try > 1 {
|
if try > 1 {
|
||||||
delay += time.Duration(try) * 50 * time.Millisecond
|
delay += time.Duration(try) * 50 * time.Millisecond
|
||||||
|
@ -1589,7 +1589,10 @@ func (rs *reportState) runProbe(ctx context.Context, dm *tailcfg.DERPMap, probe
|
||||||
// proto is 4 or 6
|
// proto is 4 or 6
|
||||||
// If it returns nil, the node is skipped.
|
// If it returns nil, the node is skipped.
|
||||||
func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeProto) (ap netip.AddrPort) {
|
func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeProto) (ap netip.AddrPort) {
|
||||||
port := cmpx.Or(n.STUNPort, 3478)
|
port := n.STUNPort
|
||||||
|
if port == 0 {
|
||||||
|
port = 3478
|
||||||
|
}
|
||||||
if port < 0 || port > 1<<16-1 {
|
if port < 0 || port > 1<<16-1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ func (c *nlConn) Receive() (message, error) {
|
||||||
typ = "RTM_DELADDR"
|
typ = "RTM_DELADDR"
|
||||||
}
|
}
|
||||||
|
|
||||||
// label attributes are seemingly only populated for IPv4 addresses in the wild.
|
// label attributes are seemingly only populated for ipv4 addresses in the wild.
|
||||||
label := rmsg.Attributes.Label
|
label := rmsg.Attributes.Label
|
||||||
if label == "" {
|
if label == "" {
|
||||||
itf, err := net.InterfaceByIndex(int(rmsg.Index))
|
itf, err := net.InterfaceByIndex(int(rmsg.Index))
|
||||||
|
|
|
@ -17,9 +17,16 @@ import (
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/interfaces"
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/linuxfw"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// tailscaleBypassMark is the mark indicating that packets originating
|
||||||
|
// from a socket should bypass Tailscale-managed routes during routing
|
||||||
|
// table lookups.
|
||||||
|
//
|
||||||
|
// Keep this in sync with tailscaleBypassMark in
|
||||||
|
// wgengine/router/router_linux.go.
|
||||||
|
const tailscaleBypassMark = 0x80000
|
||||||
|
|
||||||
// socketMarkWorksOnce is the sync.Once & cached value for useSocketMark.
|
// socketMarkWorksOnce is the sync.Once & cached value for useSocketMark.
|
||||||
var socketMarkWorksOnce struct {
|
var socketMarkWorksOnce struct {
|
||||||
sync.Once
|
sync.Once
|
||||||
|
@ -112,7 +119,7 @@ func controlC(network, address string, c syscall.RawConn) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setBypassMark(fd uintptr) error {
|
func setBypassMark(fd uintptr) error {
|
||||||
if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, linuxfw.TailscaleBypassMarkNum); err != nil {
|
if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, tailscaleBypassMark); err != nil {
|
||||||
return fmt.Errorf("setting SO_MARK bypass: %w", err)
|
return fmt.Errorf("setting SO_MARK bypass: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -4,9 +4,51 @@
|
||||||
package netns
|
package netns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// verifies tailscaleBypassMark is in sync with wgengine.
|
||||||
|
func TestBypassMarkInSync(t *testing.T) {
|
||||||
|
want := fmt.Sprintf("%q", fmt.Sprintf("0x%x", tailscaleBypassMark))
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
f, err := parser.ParseFile(fset, "../../wgengine/router/router_linux.go", nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, decl := range f.Decls {
|
||||||
|
gd, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok || gd.Tok != token.CONST {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, spec := range gd.Specs {
|
||||||
|
vs, ok := spec.(*ast.ValueSpec)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i, ident := range vs.Names {
|
||||||
|
if ident.Name != "tailscaleBypassMark" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
valExpr := vs.Values[i]
|
||||||
|
lit, ok := valExpr.(*ast.BasicLit)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("tailscaleBypassMark = %T, expected *ast.BasicLit", valExpr)
|
||||||
|
}
|
||||||
|
if lit.Value == want {
|
||||||
|
// Pass.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatalf("router_linux.go's tailscaleBypassMark = %s; not in sync with netns's %s", lit.Value, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Errorf("tailscaleBypassMark not found in router_linux.go")
|
||||||
|
}
|
||||||
|
|
||||||
func TestSocketMarkWorks(t *testing.T) {
|
func TestSocketMarkWorks(t *testing.T) {
|
||||||
_ = socketMarkWorks()
|
_ = socketMarkWorks()
|
||||||
// we cannot actually assert whether the test runner has SO_MARK available
|
// we cannot actually assert whether the test runner has SO_MARK available
|
||||||
|
|
|
@ -212,16 +212,9 @@ func ipForwardingEnabledLinux(p protocol, iface string) (bool, error) {
|
||||||
}
|
}
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
on, err := strconv.ParseBool(string(bytes.TrimSpace(bs)))
|
||||||
val, err := strconv.ParseInt(string(bytes.TrimSpace(bs)), 10, 32)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("couldn't parse %s: %w", k, err)
|
return false, fmt.Errorf("couldn't parse %s: %w", k, err)
|
||||||
}
|
}
|
||||||
// 0 = disabled, 1 = enabled, 2 = enabled (but uncommon)
|
|
||||||
// https://github.com/tailscale/tailscale/issues/8375
|
|
||||||
if val < 0 || val > 2 {
|
|
||||||
return false, fmt.Errorf("unexpected value %d for %s", val, k)
|
|
||||||
}
|
|
||||||
on := val == 1 || val == 2
|
|
||||||
return on, nil
|
return on, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
// Package tcpinfo provides platform-agnostic accessors to information about a
|
|
||||||
// TCP connection (e.g. RTT, MSS, etc.).
|
|
||||||
package tcpinfo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNotTCP = errors.New("tcpinfo: not a TCP conn")
|
|
||||||
ErrUnimplemented = errors.New("tcpinfo: unimplemented")
|
|
||||||
)
|
|
||||||
|
|
||||||
// RTT returns the RTT for the given net.Conn.
|
|
||||||
//
|
|
||||||
// If the net.Conn is not a *net.TCPConn and cannot be unwrapped into one, then
|
|
||||||
// ErrNotTCP will be returned. If retrieving the RTT is not supported on the
|
|
||||||
// current platform, ErrUnimplemented will be returned.
|
|
||||||
func RTT(conn net.Conn) (time.Duration, error) {
|
|
||||||
tcpConn, err := unwrap(conn)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rttImpl(tcpConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// netConner is implemented by crypto/tls.Conn to unwrap into an underlying
|
|
||||||
// net.Conn.
|
|
||||||
type netConner interface {
|
|
||||||
NetConn() net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// unwrap attempts to unwrap a net.Conn into an underlying *net.TCPConn
|
|
||||||
func unwrap(nc net.Conn) (*net.TCPConn, error) {
|
|
||||||
for {
|
|
||||||
switch v := nc.(type) {
|
|
||||||
case *net.TCPConn:
|
|
||||||
return v, nil
|
|
||||||
case netConner:
|
|
||||||
nc = v.NetConn()
|
|
||||||
default:
|
|
||||||
return nil, ErrNotTCP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
package tcpinfo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func rttImpl(conn *net.TCPConn) (time.Duration, error) {
|
|
||||||
rawConn, err := conn.SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
tcpInfo *unix.TCPConnectionInfo
|
|
||||||
sysErr error
|
|
||||||
)
|
|
||||||
err = rawConn.Control(func(fd uintptr) {
|
|
||||||
tcpInfo, sysErr = unix.GetsockoptTCPConnectionInfo(int(fd), unix.IPPROTO_TCP, unix.TCP_CONNECTION_INFO)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
} else if sysErr != nil {
|
|
||||||
return 0, sysErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Duration(tcpInfo.Rttcur) * time.Millisecond, nil
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
package tcpinfo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func rttImpl(conn *net.TCPConn) (time.Duration, error) {
|
|
||||||
rawConn, err := conn.SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
tcpInfo *unix.TCPInfo
|
|
||||||
sysErr error
|
|
||||||
)
|
|
||||||
err = rawConn.Control(func(fd uintptr) {
|
|
||||||
tcpInfo, sysErr = unix.GetsockoptTCPInfo(int(fd), unix.IPPROTO_TCP, unix.TCP_INFO)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
} else if sysErr != nil {
|
|
||||||
return 0, sysErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Duration(tcpInfo.Rtt) * time.Microsecond, nil
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
//go:build !linux && !darwin
|
|
||||||
|
|
||||||
package tcpinfo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func rttImpl(conn *net.TCPConn) (time.Duration, error) {
|
|
||||||
return 0, ErrUnimplemented
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
package tcpinfo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRTT(t *testing.T) {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux", "darwin":
|
|
||||||
default:
|
|
||||||
t.Skipf("not currently supported on %s", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
ln, err := net.Listen("tcp4", "localhost:0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer ln.Close()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
c, err := ln.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Cleanup(func() { c.Close() })
|
|
||||||
|
|
||||||
// Copy from the client to nowhere
|
|
||||||
go io.Copy(io.Discard, c)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
conn, err := net.Dial("tcp4", ln.Addr().String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write a bunch of data to the conn to force TCP session establishment
|
|
||||||
// and a few packets.
|
|
||||||
junkData := bytes.Repeat([]byte("hello world\n"), 1024*1024)
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
if _, err := conn.Write(junkData); err != nil {
|
|
||||||
t.Fatalf("error writing junk data [%d]: %v", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the RTT now
|
|
||||||
rtt, err := RTT(conn)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting RTT: %v", err)
|
|
||||||
}
|
|
||||||
if rtt == 0 {
|
|
||||||
t.Errorf("expected RTT > 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("TCP rtt: %v", rtt)
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
//go:build !wasm
|
//go:build !js
|
||||||
|
|
||||||
// Package tun creates a tuntap device, working around OS-specific
|
// Package tun creates a tuntap device, working around OS-specific
|
||||||
// quirks if necessary.
|
// quirks if necessary.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
//go:build !windows && !js && !wasip1
|
//go:build !windows && !js
|
||||||
|
|
||||||
package paths
|
package paths
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
package portlist
|
package portlist
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -17,25 +17,14 @@ import (
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var pollInterval = 5 * time.Second // default; changed by some OS-specific init funcs
|
||||||
newOSImpl func(includeLocalhost bool) osImpl // if non-nil, constructs a new osImpl.
|
|
||||||
pollInterval = 5 * time.Second // default; changed by some OS-specific init funcs
|
|
||||||
debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST")
|
|
||||||
)
|
|
||||||
|
|
||||||
// PollInterval is the recommended OS-specific interval
|
var debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST")
|
||||||
// to wait between *Poller.Poll method calls.
|
|
||||||
func PollInterval() time.Duration {
|
|
||||||
return pollInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
// Poller scans the systems for listening ports periodically and sends
|
// Poller scans the systems for listening ports periodically and sends
|
||||||
// the results to C.
|
// the results to C.
|
||||||
type Poller struct {
|
type Poller struct {
|
||||||
// IncludeLocalhost controls whether services bound to localhost are included.
|
c chan List // unbuffered
|
||||||
//
|
|
||||||
// This field should only be changed before calling Run.
|
|
||||||
IncludeLocalhost bool
|
|
||||||
|
|
||||||
// os, if non-nil, is an OS-specific implementation of the portlist getting
|
// os, if non-nil, is an OS-specific implementation of the portlist getting
|
||||||
// code. When non-nil, it's responsible for getting the complete list of
|
// code. When non-nil, it's responsible for getting the complete list of
|
||||||
|
@ -44,13 +33,22 @@ type Poller struct {
|
||||||
// A nil values means we don't have code for getting the list on the current
|
// A nil values means we don't have code for getting the list on the current
|
||||||
// operating system.
|
// operating system.
|
||||||
os osImpl
|
os osImpl
|
||||||
initOnce sync.Once // guards init of os
|
osOnce sync.Once // guards init of os
|
||||||
initErr error
|
|
||||||
|
// closeCtx is the context that's canceled on Close.
|
||||||
|
closeCtx context.Context
|
||||||
|
closeCtxCancel context.CancelFunc
|
||||||
|
|
||||||
|
runDone chan struct{} // closed when Run completes
|
||||||
|
|
||||||
// scatch is memory for Poller.getList to reuse between calls.
|
// scatch is memory for Poller.getList to reuse between calls.
|
||||||
scratch []Port
|
scratch []Port
|
||||||
|
|
||||||
prev List // most recent data, not aliasing scratch
|
prev List // most recent data, not aliasing scratch
|
||||||
|
|
||||||
|
// caller options fields
|
||||||
|
includeLocalhost bool
|
||||||
|
pollInterval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// osImpl is the OS-specific implementation of getting the open listening ports.
|
// osImpl is the OS-specific implementation of getting the open listening ports.
|
||||||
|
@ -67,55 +65,142 @@ type osImpl interface {
|
||||||
AppendListeningPorts(base []Port) ([]Port, error)
|
AppendListeningPorts(base []Port) ([]Port, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newOSImpl, if non-nil, constructs a new osImpl.
|
||||||
|
var newOSImpl func(includeLocalhost bool) osImpl
|
||||||
|
|
||||||
|
var errUnimplemented = errors.New("portlist poller not implemented on " + runtime.GOOS)
|
||||||
|
|
||||||
|
// PollerOptions for customizing the behavior
|
||||||
|
// of the Poller. The zero value uses each
|
||||||
|
// of the options' defaults.
|
||||||
|
type PollerOptions struct {
|
||||||
|
// IncludeLocalhost controls whether services bound to localhost are included.
|
||||||
|
//
|
||||||
|
// This field should only be changed before calling Run.
|
||||||
|
IncludeLocalhost bool
|
||||||
|
|
||||||
|
// PollInterval sets the interval for checking the underlying OS
|
||||||
|
// for port updates.
|
||||||
|
PollInterval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPoller returns a new portlist Poller. It returns an error
|
||||||
|
// if the portlist couldn't be obtained.
|
||||||
|
func NewPoller(opts PollerOptions) (*Poller, error) {
|
||||||
|
if debugDisablePortlist() {
|
||||||
|
return nil, errors.New("portlist disabled by envknob")
|
||||||
|
}
|
||||||
|
if opts.PollInterval == 0 {
|
||||||
|
opts.PollInterval = pollInterval
|
||||||
|
}
|
||||||
|
p := &Poller{
|
||||||
|
c: make(chan List),
|
||||||
|
runDone: make(chan struct{}),
|
||||||
|
includeLocalhost: opts.IncludeLocalhost,
|
||||||
|
pollInterval: opts.PollInterval,
|
||||||
|
}
|
||||||
|
p.closeCtx, p.closeCtxCancel = context.WithCancel(context.Background())
|
||||||
|
p.osOnce.Do(p.initOSField)
|
||||||
|
if p.os == nil {
|
||||||
|
return nil, errUnimplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do one initial poll synchronously so we can return an error
|
||||||
|
// early.
|
||||||
|
if pl, err := p.getList(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
p.setPrev(pl)
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Poller) setPrev(pl List) {
|
func (p *Poller) setPrev(pl List) {
|
||||||
// Make a copy, as the pass in pl slice aliases pl.scratch and we don't want
|
// Make a copy, as the pass in pl slice aliases pl.scratch and we don't want
|
||||||
// that to except to the caller.
|
// that to except to the caller.
|
||||||
p.prev = slices.Clone(pl)
|
p.prev = slices.Clone(pl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// init initializes the Poller by ensuring it has an underlying
|
func (p *Poller) initOSField() {
|
||||||
// OS implementation and is not turned off by envknob.
|
if newOSImpl != nil {
|
||||||
func (p *Poller) init() {
|
p.os = newOSImpl(p.includeLocalhost)
|
||||||
switch {
|
|
||||||
case debugDisablePortlist():
|
|
||||||
p.initErr = errors.New("portlist disabled by envknob")
|
|
||||||
case newOSImpl == nil:
|
|
||||||
p.initErr = errors.New("portlist poller not implemented on " + runtime.GOOS)
|
|
||||||
default:
|
|
||||||
p.os = newOSImpl(p.IncludeLocalhost)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updates return the channel that receives port list updates.
|
||||||
|
//
|
||||||
|
// The channel is closed when the Poller is closed.
|
||||||
|
func (p *Poller) Updates() <-chan List { return p.c }
|
||||||
|
|
||||||
// Close closes the Poller.
|
// Close closes the Poller.
|
||||||
|
// Run will return with a nil error.
|
||||||
func (p *Poller) Close() error {
|
func (p *Poller) Close() error {
|
||||||
if p.initErr != nil {
|
p.closeCtxCancel()
|
||||||
return p.initErr
|
<-p.runDone
|
||||||
|
if p.os != nil {
|
||||||
|
p.os.Close()
|
||||||
}
|
}
|
||||||
if p.os == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return p.os.Close()
|
|
||||||
|
// send sends pl to p.c and returns whether it was successfully sent.
|
||||||
|
func (p *Poller) send(ctx context.Context, pl List) (sent bool, err error) {
|
||||||
|
select {
|
||||||
|
case p.c <- pl:
|
||||||
|
return true, nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
return false, ctx.Err()
|
||||||
|
case <-p.closeCtx.Done():
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll returns the list of listening ports, if changed from
|
// Run runs the Poller periodically until either the context
|
||||||
// a previous call as indicated by the changed result.
|
// is done, or the Close is called.
|
||||||
func (p *Poller) Poll() (ports []Port, changed bool, err error) {
|
//
|
||||||
p.initOnce.Do(p.init)
|
// Run may only be called once.
|
||||||
if p.initErr != nil {
|
func (p *Poller) Run(ctx context.Context) error {
|
||||||
return nil, false, fmt.Errorf("error initializing poller: %w", p.initErr)
|
tick := time.NewTicker(p.pollInterval)
|
||||||
|
defer tick.Stop()
|
||||||
|
return p.runWithTickChan(ctx, tick.C)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Poller) runWithTickChan(ctx context.Context, tickChan <-chan time.Time) error {
|
||||||
|
defer close(p.runDone)
|
||||||
|
defer close(p.c)
|
||||||
|
|
||||||
|
// Send out the pre-generated initial value.
|
||||||
|
if sent, err := p.send(ctx, p.prev); !sent {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tickChan:
|
||||||
pl, err := p.getList()
|
pl, err := p.getList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return err
|
||||||
}
|
}
|
||||||
if pl.equal(p.prev) {
|
if pl.equal(p.prev) {
|
||||||
return nil, false, nil
|
continue
|
||||||
}
|
}
|
||||||
p.setPrev(pl)
|
p.setPrev(pl)
|
||||||
return p.prev, true, nil
|
if sent, err := p.send(ctx, p.prev); !sent {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-p.closeCtx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Poller) getList() (List, error) {
|
func (p *Poller) getList() (List, error) {
|
||||||
|
if debugDisablePortlist() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
p.osOnce.Do(p.initOSField)
|
||||||
var err error
|
var err error
|
||||||
p.scratch, err = p.os.AppendListeningPorts(p.scratch[:0])
|
p.scratch, err = p.os.AppendListeningPorts(p.scratch[:0])
|
||||||
return p.scratch, err
|
return p.scratch, err
|
||||||
|
|
|
@ -4,8 +4,11 @@
|
||||||
package portlist
|
package portlist
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
)
|
)
|
||||||
|
@ -14,14 +17,14 @@ func TestGetList(t *testing.T) {
|
||||||
tstest.ResourceCheck(t)
|
tstest.ResourceCheck(t)
|
||||||
|
|
||||||
var p Poller
|
var p Poller
|
||||||
pl, _, err := p.Poll()
|
pl, err := p.getList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for i, p := range pl {
|
for i, p := range pl {
|
||||||
t.Logf("[%d] %+v", i, p)
|
t.Logf("[%d] %+v", i, p)
|
||||||
}
|
}
|
||||||
t.Logf("As String: %s", List(pl))
|
t.Logf("As String: %v", pl.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIgnoreLocallyBoundPorts(t *testing.T) {
|
func TestIgnoreLocallyBoundPorts(t *testing.T) {
|
||||||
|
@ -35,7 +38,7 @@ func TestIgnoreLocallyBoundPorts(t *testing.T) {
|
||||||
ta := ln.Addr().(*net.TCPAddr)
|
ta := ln.Addr().(*net.TCPAddr)
|
||||||
port := ta.Port
|
port := ta.Port
|
||||||
var p Poller
|
var p Poller
|
||||||
pl, _, err := p.Poll()
|
pl, err := p.getList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -46,16 +49,16 @@ func TestIgnoreLocallyBoundPorts(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoller(t *testing.T) {
|
func TestChangesOverTime(t *testing.T) {
|
||||||
var p Poller
|
var p Poller
|
||||||
p.IncludeLocalhost = true
|
p.includeLocalhost = true
|
||||||
get := func(t *testing.T) []Port {
|
get := func(t *testing.T) []Port {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
s, _, err := p.Poll()
|
s, err := p.getList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
return s
|
return append([]Port(nil), s...)
|
||||||
}
|
}
|
||||||
|
|
||||||
p1 := get(t)
|
p1 := get(t)
|
||||||
|
@ -172,21 +175,68 @@ func TestEqualLessThan(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClose(t *testing.T) {
|
func TestPoller(t *testing.T) {
|
||||||
var p Poller
|
p, err := NewPoller(PollerOptions{})
|
||||||
err := p.Close()
|
if err != nil {
|
||||||
|
t.Skipf("not running test: %v", err)
|
||||||
|
}
|
||||||
|
defer p.Close()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
gotUpdate := make(chan bool, 16)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for pl := range p.Updates() {
|
||||||
|
// Look at all the pl slice memory to maximize
|
||||||
|
// chance of race detector seeing violations.
|
||||||
|
for _, v := range pl {
|
||||||
|
if v == (Port{}) {
|
||||||
|
// Force use
|
||||||
|
panic("empty port")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case gotUpdate <- true:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
tick := make(chan time.Time, 16)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := p.runWithTickChan(context.Background(), tick); err != nil {
|
||||||
|
t.Error("runWithTickChan:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
ln, err := net.Listen("tcp", ":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
p = Poller{}
|
defer ln.Close()
|
||||||
_, _, err = p.Poll()
|
tick <- time.Time{}
|
||||||
if err != nil {
|
|
||||||
t.Skipf("skipping due to poll error: %v", err)
|
select {
|
||||||
|
case <-gotUpdate:
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatal("timed out waiting for update")
|
||||||
}
|
}
|
||||||
err = p.Close()
|
}
|
||||||
if err != nil {
|
|
||||||
|
// And a bunch of ticks without waiting for updates,
|
||||||
|
// to make race tests more likely to fail, if any present.
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
tick <- time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.Close(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkGetList(b *testing.B) {
|
func BenchmarkGetList(b *testing.B) {
|
||||||
|
@ -200,11 +250,6 @@ func BenchmarkGetListIncremental(b *testing.B) {
|
||||||
func benchmarkGetList(b *testing.B, incremental bool) {
|
func benchmarkGetList(b *testing.B, incremental bool) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
var p Poller
|
var p Poller
|
||||||
p.init()
|
|
||||||
if p.initErr != nil {
|
|
||||||
b.Skip(p.initErr)
|
|
||||||
}
|
|
||||||
b.Cleanup(func() { p.Close() })
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
pl, err := p.getList()
|
pl, err := p.getList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -188,9 +188,6 @@ func (d *derpProber) updateMap(ctx context.Context) error {
|
||||||
if existing, ok := d.nodes[n.HostName]; ok {
|
if existing, ok := d.nodes[n.HostName]; ok {
|
||||||
return fmt.Errorf("derpmap has duplicate nodes: %+v and %+v", existing, n)
|
return fmt.Errorf("derpmap has duplicate nodes: %+v and %+v", existing, n)
|
||||||
}
|
}
|
||||||
// Allow the prober to monitor nodes marked as
|
|
||||||
// STUN only in the default map
|
|
||||||
n.STUNOnly = false
|
|
||||||
d.nodes[n.HostName] = n
|
d.nodes[n.HostName] = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,11 +124,10 @@ func runBuild(ctx context.Context, filters []string, targets []dist.Target) erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting absolute path of manifest: %w", err)
|
return fmt.Errorf("getting absolute path of manifest: %w", err)
|
||||||
}
|
}
|
||||||
|
fmt.Println(manifest)
|
||||||
|
fmt.Println(filepath.Join(b.Out, out[0]))
|
||||||
for i := range out {
|
for i := range out {
|
||||||
if !filepath.IsAbs(out[i]) {
|
rel, err := filepath.Rel(filepath.Dir(manifest), filepath.Join(b.Out, out[i]))
|
||||||
out[i] = filepath.Join(b.Out, out[i])
|
|
||||||
}
|
|
||||||
rel, err := filepath.Rel(filepath.Dir(manifest), out[i])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("making path relative: %w", err)
|
return fmt.Errorf("making path relative: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"tailscale.com/util/multierr"
|
"tailscale.com/util/multierr"
|
||||||
"tailscale.com/version/mkversion"
|
"tailscale.com/version/mkversion"
|
||||||
|
@ -45,8 +44,6 @@ type Build struct {
|
||||||
Go string
|
Go string
|
||||||
// Version is the version info of the build.
|
// Version is the version info of the build.
|
||||||
Version mkversion.VersionInfo
|
Version mkversion.VersionInfo
|
||||||
// Time is the timestamp of the build.
|
|
||||||
Time time.Time
|
|
||||||
|
|
||||||
// once is a cache of function invocations that should run once per process
|
// once is a cache of function invocations that should run once per process
|
||||||
// (for example building a helper docker container)
|
// (for example building a helper docker container)
|
||||||
|
@ -89,7 +86,6 @@ func NewBuild(repo, out string) (*Build, error) {
|
||||||
Out: out,
|
Out: out,
|
||||||
Go: goTool,
|
Go: goTool,
|
||||||
Version: mkversion.Info(),
|
Version: mkversion.Info(),
|
||||||
Time: time.Now().UTC(),
|
|
||||||
extra: map[any]any{},
|
extra: map[any]any{},
|
||||||
goBuildLimit: make(chan struct{}, runtime.NumCPU()),
|
goBuildLimit: make(chan struct{}, runtime.NumCPU()),
|
||||||
}
|
}
|
||||||
|
@ -118,9 +114,6 @@ func (b *Build) Build(targets []Target) (files []string, err error) {
|
||||||
go func(i int, t Target) {
|
go func(i int, t Target) {
|
||||||
var err error
|
var err error
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("%s: %w", t, err)
|
|
||||||
}
|
|
||||||
errs[i] = err
|
errs[i] = err
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
@ -184,17 +177,6 @@ func (b *Build) TmpDir() string {
|
||||||
// binary. Builds are cached by path and env, so each build only happens once
|
// binary. Builds are cached by path and env, so each build only happens once
|
||||||
// per process execution.
|
// per process execution.
|
||||||
func (b *Build) BuildGoBinary(path string, env map[string]string) (string, error) {
|
func (b *Build) BuildGoBinary(path string, env map[string]string) (string, error) {
|
||||||
return b.BuildGoBinaryWithTags(path, env, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildGoBinaryWithTags builds the Go binary at path and returns the
|
|
||||||
// path to the binary. Builds are cached by path, env and tags, so
|
|
||||||
// each build only happens once per process execution.
|
|
||||||
//
|
|
||||||
// The passed in tags override gocross's automatic selection of build
|
|
||||||
// tags, so you will have to figure out and specify all the tags
|
|
||||||
// relevant to your build.
|
|
||||||
func (b *Build) BuildGoBinaryWithTags(path string, env map[string]string, tags []string) (string, error) {
|
|
||||||
err := b.Once("init-go", func() error {
|
err := b.Once("init-go", func() error {
|
||||||
log.Printf("Initializing Go toolchain")
|
log.Printf("Initializing Go toolchain")
|
||||||
// If the build is using a tool/go, it may need to download a toolchain
|
// If the build is using a tool/go, it may need to download a toolchain
|
||||||
|
@ -208,7 +190,7 @@ func (b *Build) BuildGoBinaryWithTags(path string, env map[string]string, tags [
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
buildKey := []any{"go-build", path, env, tags}
|
buildKey := []any{"go-build", path, env}
|
||||||
return b.goBuilds.Do(buildKey, func() (string, error) {
|
return b.goBuilds.Do(buildKey, func() (string, error) {
|
||||||
b.goBuildLimit <- struct{}{}
|
b.goBuildLimit <- struct{}{}
|
||||||
defer func() { <-b.goBuildLimit }()
|
defer func() { <-b.goBuildLimit }()
|
||||||
|
@ -218,17 +200,9 @@ func (b *Build) BuildGoBinaryWithTags(path string, env map[string]string, tags [
|
||||||
envStrs = append(envStrs, k+"="+v)
|
envStrs = append(envStrs, k+"="+v)
|
||||||
}
|
}
|
||||||
sort.Strings(envStrs)
|
sort.Strings(envStrs)
|
||||||
buildDir := b.TmpDir()
|
|
||||||
args := []string{"build", "-v", "-o", buildDir}
|
|
||||||
if len(tags) > 0 {
|
|
||||||
tagsStr := strings.Join(tags, ",")
|
|
||||||
log.Printf("Building %s (with env %s, tags %s)", path, strings.Join(envStrs, " "), tagsStr)
|
|
||||||
args = append(args, "-tags="+tagsStr)
|
|
||||||
} else {
|
|
||||||
log.Printf("Building %s (with env %s)", path, strings.Join(envStrs, " "))
|
log.Printf("Building %s (with env %s)", path, strings.Join(envStrs, " "))
|
||||||
}
|
buildDir := b.TmpDir()
|
||||||
args = append(args, path)
|
cmd := b.Command(b.Repo, b.Go, "build", "-v", "-o", buildDir, path)
|
||||||
cmd := b.Command(b.Repo, b.Go, args...)
|
|
||||||
for k, v := range env {
|
for k, v := range env {
|
||||||
cmd.Cmd.Env = append(cmd.Cmd.Env, k+"="+v)
|
cmd.Cmd.Env = append(cmd.Cmd.Env, k+"="+v)
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 3.9 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue