Compare commits
30 Commits
irbekrm/pr
...
gitops-1.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13f8a669d5 | ||
|
|
cce189bde1 | ||
|
|
fbfc3b7e51 | ||
|
|
0f3b2e7b86 | ||
|
|
fd94d96e2b | ||
|
|
75f1d3e7d7 | ||
|
|
6ee956333f | ||
|
|
8b47322acc | ||
|
|
0e2cb76abe | ||
|
|
ce4553b988 | ||
|
|
370ec6b46b | ||
|
|
b45089ad85 | ||
|
|
4e822c031f | ||
|
|
b787c27c00 | ||
|
|
7e3bcd297e | ||
|
|
17eae5b0d3 | ||
|
|
ae79b2e784 | ||
|
|
213d696db0 | ||
|
|
62b056d677 | ||
|
|
5b4eb47300 | ||
|
|
457102d070 | ||
|
|
7a0392a8a3 | ||
|
|
832e5c781d | ||
|
|
2ce596ea7a | ||
|
|
2ac7c0161b | ||
|
|
2aec4f2c43 | ||
|
|
8250582fe6 | ||
|
|
38a1cf748a | ||
|
|
32f01acc79 | ||
|
|
24df1ef1ee |
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -47,6 +47,12 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Install a more recent Go that understands modern go.mod content.
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
|
||||
4
.github/workflows/kubemanifests.yaml
vendored
4
.github/workflows/kubemanifests.yaml
vendored
@@ -2,8 +2,8 @@ name: "Kubernetes manifests"
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- './cmd/k8s-operator/'
|
||||
- './k8s-operator/'
|
||||
- './cmd/k8s-operator/**'
|
||||
- './k8s-operator/**'
|
||||
- '.github/workflows/kubemanifests.yaml'
|
||||
|
||||
# Cancel workflow run if there is a newer push to the same PR for which it is
|
||||
|
||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -241,9 +241,9 @@ jobs:
|
||||
goarch: amd64
|
||||
- goos: openbsd
|
||||
goarch: amd64
|
||||
# Plan9
|
||||
- goos: plan9
|
||||
goarch: amd64
|
||||
# Plan9 (disabled until 3p dependencies are fixed)
|
||||
# - goos: plan9
|
||||
# goarch: amd64
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.57.0
|
||||
1.59.0
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
package appc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -20,14 +21,18 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/execqueue"
|
||||
)
|
||||
|
||||
// RouteAdvertiser is an interface that allows the AppConnector to advertise
|
||||
// newly discovered routes that need to be served through the AppConnector.
|
||||
type RouteAdvertiser interface {
|
||||
// AdvertiseRoute adds a new route advertisement if the route is not already
|
||||
// being advertised.
|
||||
AdvertiseRoute(netip.Prefix) error
|
||||
// AdvertiseRoute adds one or more route advertisements skipping any that
|
||||
// are already advertised.
|
||||
AdvertiseRoute(...netip.Prefix) error
|
||||
|
||||
// UnadvertiseRoute removes any matching route advertisements.
|
||||
UnadvertiseRoute(...netip.Prefix) error
|
||||
}
|
||||
|
||||
// AppConnector is an implementation of an AppConnector that performs
|
||||
@@ -45,12 +50,19 @@ type AppConnector struct {
|
||||
|
||||
// mu guards the fields that follow
|
||||
mu sync.Mutex
|
||||
|
||||
// domains is a map of lower case domain names with no trailing dot, to a
|
||||
// list of resolved IP addresses.
|
||||
domains map[string][]netip.Addr
|
||||
|
||||
// controlRoutes is the list of routes that were last supplied by control.
|
||||
controlRoutes []netip.Prefix
|
||||
|
||||
// wildcards is the list of domain strings that match subdomains.
|
||||
wildcards []string
|
||||
|
||||
// queue provides ordering for update operations
|
||||
queue execqueue.ExecQueue
|
||||
}
|
||||
|
||||
// NewAppConnector creates a new AppConnector.
|
||||
@@ -61,11 +73,33 @@ func NewAppConnector(logf logger.Logf, routeAdvertiser RouteAdvertiser) *AppConn
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateDomains replaces the current set of configured domains with the
|
||||
// supplied set of domains. Domains must not contain a trailing dot, and should
|
||||
// be lower case. If the domain contains a leading '*' label it matches all
|
||||
// subdomains of a domain.
|
||||
// UpdateDomainsAndRoutes starts an asynchronous update of the configuration
|
||||
// given the new domains and routes.
|
||||
func (e *AppConnector) UpdateDomainsAndRoutes(domains []string, routes []netip.Prefix) {
|
||||
e.queue.Add(func() {
|
||||
// Add the new routes first.
|
||||
e.updateRoutes(routes)
|
||||
e.updateDomains(domains)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateDomains asynchronously replaces the current set of configured domains
|
||||
// with the supplied set of domains. Domains must not contain a trailing dot,
|
||||
// and should be lower case. If the domain contains a leading '*' label it
|
||||
// matches all subdomains of a domain.
|
||||
func (e *AppConnector) UpdateDomains(domains []string) {
|
||||
e.queue.Add(func() {
|
||||
e.updateDomains(domains)
|
||||
})
|
||||
}
|
||||
|
||||
// Wait waits for the currently scheduled asynchronous configuration changes to
|
||||
// complete.
|
||||
func (e *AppConnector) Wait(ctx context.Context) {
|
||||
e.queue.Wait(ctx)
|
||||
}
|
||||
|
||||
func (e *AppConnector) updateDomains(domains []string) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
@@ -97,6 +131,46 @@ func (e *AppConnector) UpdateDomains(domains []string) {
|
||||
e.logf("handling domains: %v and wildcards: %v", xmaps.Keys(e.domains), e.wildcards)
|
||||
}
|
||||
|
||||
// updateRoutes merges the supplied routes into the currently configured routes. The routes supplied
|
||||
// by control for UpdateRoutes are supplemental to the routes discovered by DNS resolution, but are
|
||||
// also more often whole ranges. UpdateRoutes will remove any single address routes that are now
|
||||
// covered by new ranges.
|
||||
func (e *AppConnector) updateRoutes(routes []netip.Prefix) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
// If there was no change since the last update, no work to do.
|
||||
if slices.Equal(e.controlRoutes, routes) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := e.routeAdvertiser.AdvertiseRoute(routes...); err != nil {
|
||||
e.logf("failed to advertise routes: %v: %v", routes, err)
|
||||
return
|
||||
}
|
||||
|
||||
var toRemove []netip.Prefix
|
||||
|
||||
nextRoute:
|
||||
for _, r := range routes {
|
||||
for _, addr := range e.domains {
|
||||
for _, a := range addr {
|
||||
if r.Contains(a) && netip.PrefixFrom(a, a.BitLen()) != r {
|
||||
pfx := netip.PrefixFrom(a, a.BitLen())
|
||||
toRemove = append(toRemove, pfx)
|
||||
continue nextRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.routeAdvertiser.UnadvertiseRoute(toRemove...); err != nil {
|
||||
e.logf("failed to unadvertise routes: %v: %v", toRemove, err)
|
||||
}
|
||||
|
||||
e.controlRoutes = routes
|
||||
}
|
||||
|
||||
// Domains returns the currently configured domain list.
|
||||
func (e *AppConnector) Domains() views.Slice[string] {
|
||||
e.mu.Lock()
|
||||
@@ -132,6 +206,7 @@ func (e *AppConnector) ObserveDNSResponse(res []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
nextAnswer:
|
||||
for {
|
||||
h, err := p.AnswerHeader()
|
||||
if err == dnsmessage.ErrSectionDone {
|
||||
@@ -206,6 +281,16 @@ func (e *AppConnector) ObserveDNSResponse(res []byte) {
|
||||
if slices.Contains(addrs, addr) {
|
||||
continue
|
||||
}
|
||||
for _, route := range e.controlRoutes {
|
||||
if route.Contains(addr) {
|
||||
// record the new address associated with the domain for faster matching in subsequent
|
||||
// requests and for diagnostic records.
|
||||
e.mu.Lock()
|
||||
e.domains[domain] = append(addrs, addr)
|
||||
e.mu.Unlock()
|
||||
continue nextAnswer
|
||||
}
|
||||
}
|
||||
if err := e.routeAdvertiser.AdvertiseRoute(netip.PrefixFrom(addr, addr.BitLen())); err != nil {
|
||||
e.logf("failed to advertise route for %s: %v: %v", domain, addr, err)
|
||||
continue
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package appc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"slices"
|
||||
@@ -11,12 +12,17 @@ import (
|
||||
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/appc/appctest"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/util/must"
|
||||
)
|
||||
|
||||
func TestUpdateDomains(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
a := NewAppConnector(t.Logf, nil)
|
||||
a.UpdateDomains([]string{"example.com"})
|
||||
|
||||
a.Wait(ctx)
|
||||
if got, want := a.Domains().AsSlice(), []string{"example.com"}; !slices.Equal(got, want) {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
}
|
||||
@@ -24,6 +30,7 @@ func TestUpdateDomains(t *testing.T) {
|
||||
addr := netip.MustParseAddr("192.0.0.8")
|
||||
a.domains["example.com"] = append(a.domains["example.com"], addr)
|
||||
a.UpdateDomains([]string{"example.com"})
|
||||
a.Wait(ctx)
|
||||
|
||||
if got, want := a.domains["example.com"], []netip.Addr{addr}; !slices.Equal(got, want) {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
@@ -31,15 +38,66 @@ func TestUpdateDomains(t *testing.T) {
|
||||
|
||||
// domains are explicitly downcased on set.
|
||||
a.UpdateDomains([]string{"UP.EXAMPLE.COM"})
|
||||
a.Wait(ctx)
|
||||
if got, want := xmaps.Keys(a.domains), []string{"up.example.com"}; !slices.Equal(got, want) {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainRoutes(t *testing.T) {
|
||||
rc := &routeCollector{}
|
||||
func TestUpdateRoutes(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
rc := &appctest.RouteCollector{}
|
||||
a := NewAppConnector(t.Logf, rc)
|
||||
a.UpdateDomains([]string{"example.com"})
|
||||
a.updateDomains([]string{"*.example.com"})
|
||||
|
||||
// This route should be collapsed into the range
|
||||
a.ObserveDNSResponse(dnsResponse("a.example.com.", "192.0.2.1"))
|
||||
a.Wait(ctx)
|
||||
|
||||
if !slices.Equal(rc.Routes(), []netip.Prefix{netip.MustParsePrefix("192.0.2.1/32")}) {
|
||||
t.Fatalf("got %v, want %v", rc.Routes(), []netip.Prefix{netip.MustParsePrefix("192.0.2.1/32")})
|
||||
}
|
||||
|
||||
// This route should not be collapsed or removed
|
||||
a.ObserveDNSResponse(dnsResponse("b.example.com.", "192.0.0.1"))
|
||||
a.Wait(ctx)
|
||||
|
||||
routes := []netip.Prefix{netip.MustParsePrefix("192.0.2.0/24"), netip.MustParsePrefix("192.0.0.1/32")}
|
||||
a.updateRoutes(routes)
|
||||
|
||||
slices.SortFunc(rc.Routes(), prefixCompare)
|
||||
rc.SetRoutes(slices.Compact(rc.Routes()))
|
||||
slices.SortFunc(routes, prefixCompare)
|
||||
|
||||
// Ensure that the non-matching /32 is preserved, even though it's in the domains table.
|
||||
if !slices.EqualFunc(routes, rc.Routes(), prefixEqual) {
|
||||
t.Errorf("added routes: got %v, want %v", rc.Routes(), routes)
|
||||
}
|
||||
|
||||
// Ensure that the contained /32 is removed, replaced by the /24.
|
||||
wantRemoved := []netip.Prefix{netip.MustParsePrefix("192.0.2.1/32")}
|
||||
if !slices.EqualFunc(rc.RemovedRoutes(), wantRemoved, prefixEqual) {
|
||||
t.Fatalf("unexpected removed routes: %v", rc.RemovedRoutes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRoutesUnadvertisesContainedRoutes(t *testing.T) {
|
||||
rc := &appctest.RouteCollector{}
|
||||
a := NewAppConnector(t.Logf, rc)
|
||||
mak.Set(&a.domains, "example.com", []netip.Addr{netip.MustParseAddr("192.0.2.1")})
|
||||
rc.SetRoutes([]netip.Prefix{netip.MustParsePrefix("192.0.2.1/32")})
|
||||
routes := []netip.Prefix{netip.MustParsePrefix("192.0.2.0/24")}
|
||||
a.updateRoutes(routes)
|
||||
|
||||
if !slices.EqualFunc(routes, rc.Routes(), prefixEqual) {
|
||||
t.Fatalf("got %v, want %v", rc.Routes(), routes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainRoutes(t *testing.T) {
|
||||
rc := &appctest.RouteCollector{}
|
||||
a := NewAppConnector(t.Logf, rc)
|
||||
a.updateDomains([]string{"example.com"})
|
||||
a.ObserveDNSResponse(dnsResponse("example.com.", "192.0.0.8"))
|
||||
|
||||
want := map[string][]netip.Addr{
|
||||
@@ -52,51 +110,63 @@ func TestDomainRoutes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestObserveDNSResponse(t *testing.T) {
|
||||
rc := &routeCollector{}
|
||||
rc := &appctest.RouteCollector{}
|
||||
a := NewAppConnector(t.Logf, rc)
|
||||
|
||||
// a has no domains configured, so it should not advertise any routes
|
||||
a.ObserveDNSResponse(dnsResponse("example.com.", "192.0.0.8"))
|
||||
if got, want := rc.routes, ([]netip.Prefix)(nil); !slices.Equal(got, want) {
|
||||
if got, want := rc.Routes(), ([]netip.Prefix)(nil); !slices.Equal(got, want) {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
}
|
||||
|
||||
wantRoutes := []netip.Prefix{netip.MustParsePrefix("192.0.0.8/32")}
|
||||
|
||||
a.UpdateDomains([]string{"example.com"})
|
||||
a.updateDomains([]string{"example.com"})
|
||||
a.ObserveDNSResponse(dnsResponse("example.com.", "192.0.0.8"))
|
||||
if got, want := rc.routes, wantRoutes; !slices.Equal(got, want) {
|
||||
if got, want := rc.Routes(), wantRoutes; !slices.Equal(got, want) {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
}
|
||||
|
||||
wantRoutes = append(wantRoutes, netip.MustParsePrefix("2001:db8::1/128"))
|
||||
|
||||
a.ObserveDNSResponse(dnsResponse("example.com.", "2001:db8::1"))
|
||||
if got, want := rc.routes, wantRoutes; !slices.Equal(got, want) {
|
||||
if got, want := rc.Routes(), wantRoutes; !slices.Equal(got, want) {
|
||||
t.Errorf("got %v; want %v", got, want)
|
||||
}
|
||||
|
||||
// don't re-advertise routes that have already been advertised
|
||||
a.ObserveDNSResponse(dnsResponse("example.com.", "2001:db8::1"))
|
||||
if !slices.Equal(rc.routes, wantRoutes) {
|
||||
t.Errorf("got %v; want %v", rc.routes, wantRoutes)
|
||||
if !slices.Equal(rc.Routes(), wantRoutes) {
|
||||
t.Errorf("rc.Routes(): got %v; want %v", rc.Routes(), wantRoutes)
|
||||
}
|
||||
|
||||
// don't advertise addresses that are already in a control provided route
|
||||
pfx := netip.MustParsePrefix("192.0.2.0/24")
|
||||
a.updateRoutes([]netip.Prefix{pfx})
|
||||
wantRoutes = append(wantRoutes, pfx)
|
||||
a.ObserveDNSResponse(dnsResponse("example.com.", "192.0.2.1"))
|
||||
if !slices.Equal(rc.Routes(), wantRoutes) {
|
||||
t.Errorf("rc.Routes(): got %v; want %v", rc.Routes(), wantRoutes)
|
||||
}
|
||||
if !slices.Contains(a.domains["example.com"], netip.MustParseAddr("192.0.2.1")) {
|
||||
t.Errorf("missing %v from %v", "192.0.2.1", a.domains["exmaple.com"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcardDomains(t *testing.T) {
|
||||
rc := &routeCollector{}
|
||||
rc := &appctest.RouteCollector{}
|
||||
a := NewAppConnector(t.Logf, rc)
|
||||
|
||||
a.UpdateDomains([]string{"*.example.com"})
|
||||
a.updateDomains([]string{"*.example.com"})
|
||||
a.ObserveDNSResponse(dnsResponse("foo.example.com.", "192.0.0.8"))
|
||||
if got, want := rc.routes, []netip.Prefix{netip.MustParsePrefix("192.0.0.8/32")}; !slices.Equal(got, want) {
|
||||
if got, want := rc.Routes(), []netip.Prefix{netip.MustParsePrefix("192.0.0.8/32")}; !slices.Equal(got, want) {
|
||||
t.Errorf("routes: got %v; want %v", got, want)
|
||||
}
|
||||
if got, want := a.wildcards, []string{"example.com"}; !slices.Equal(got, want) {
|
||||
t.Errorf("wildcards: got %v; want %v", got, want)
|
||||
}
|
||||
|
||||
a.UpdateDomains([]string{"*.example.com", "example.com"})
|
||||
a.updateDomains([]string{"*.example.com", "example.com"})
|
||||
if _, ok := a.domains["foo.example.com"]; !ok {
|
||||
t.Errorf("expected foo.example.com to be preserved in domains due to wildcard")
|
||||
}
|
||||
@@ -105,7 +175,7 @@ func TestWildcardDomains(t *testing.T) {
|
||||
}
|
||||
|
||||
// There was an early regression where the wildcard domain was added repeatedly, this guards against that.
|
||||
a.UpdateDomains([]string{"*.example.com", "example.com"})
|
||||
a.updateDomains([]string{"*.example.com", "example.com"})
|
||||
if len(a.wildcards) != 1 {
|
||||
t.Errorf("expected only one wildcard domain, got %v", a.wildcards)
|
||||
}
|
||||
@@ -148,15 +218,13 @@ func dnsResponse(domain, address string) []byte {
|
||||
return must.Get(b.Finish())
|
||||
}
|
||||
|
||||
// routeCollector is a test helper that collects the list of routes advertised
|
||||
type routeCollector struct {
|
||||
routes []netip.Prefix
|
||||
func prefixEqual(a, b netip.Prefix) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
// routeCollector implements RouteAdvertiser
|
||||
var _ RouteAdvertiser = (*routeCollector)(nil)
|
||||
|
||||
func (rc *routeCollector) AdvertiseRoute(pfx netip.Prefix) error {
|
||||
rc.routes = append(rc.routes, pfx)
|
||||
return nil
|
||||
func prefixCompare(a, b netip.Prefix) int {
|
||||
if a.Addr().Compare(b.Addr()) == 0 {
|
||||
return a.Bits() - b.Bits()
|
||||
}
|
||||
return a.Addr().Compare(b.Addr())
|
||||
}
|
||||
|
||||
49
appc/appctest/appctest.go
Normal file
49
appc/appctest/appctest.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package appctest
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// RouteCollector is a test helper that collects the list of routes advertised
|
||||
type RouteCollector struct {
|
||||
routes []netip.Prefix
|
||||
removedRoutes []netip.Prefix
|
||||
}
|
||||
|
||||
func (rc *RouteCollector) AdvertiseRoute(pfx ...netip.Prefix) error {
|
||||
rc.routes = append(rc.routes, pfx...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *RouteCollector) UnadvertiseRoute(toRemove ...netip.Prefix) error {
|
||||
routes := rc.routes
|
||||
rc.routes = rc.routes[:0]
|
||||
for _, r := range routes {
|
||||
if !slices.Contains(toRemove, r) {
|
||||
rc.routes = append(rc.routes, r)
|
||||
} else {
|
||||
rc.removedRoutes = append(rc.removedRoutes, r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovedRoutes returns the list of routes that were removed.
|
||||
func (rc *RouteCollector) RemovedRoutes() []netip.Prefix {
|
||||
return rc.removedRoutes
|
||||
}
|
||||
|
||||
// Routes returns the ordered list of routes that were added, including
|
||||
// possible duplicates.
|
||||
func (rc *RouteCollector) Routes() []netip.Prefix {
|
||||
return rc.routes
|
||||
}
|
||||
|
||||
func (rc *RouteCollector) SetRoutes(routes []netip.Prefix) error {
|
||||
rc.routes = routes
|
||||
return nil
|
||||
}
|
||||
@@ -11,7 +11,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
W 💣 github.com/dblohm7/wingoes from tailscale.com/util/winutil
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||
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+
|
||||
@@ -23,8 +22,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
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/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
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/nlenc from github.com/jsimonetti/rtnetlink+
|
||||
L github.com/mdlayher/netlink/nltest from github.com/google/nftables
|
||||
@@ -51,8 +48,9 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
💣 go4.org/mem from tailscale.com/client/tailscale+
|
||||
go4.org/netipx from tailscale.com/wgengine/filter+
|
||||
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
|
||||
google.golang.org/protobuf/encoding/prototext from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/encoding/protowire from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/encoding/protodelim from github.com/prometheus/common/expfmt
|
||||
google.golang.org/protobuf/encoding/prototext from github.com/prometheus/common/expfmt+
|
||||
google.golang.org/protobuf/encoding/protowire from google.golang.org/protobuf/encoding/protodelim+
|
||||
google.golang.org/protobuf/internal/descfmt from google.golang.org/protobuf/internal/filedesc
|
||||
google.golang.org/protobuf/internal/descopts from google.golang.org/protobuf/internal/filedesc+
|
||||
google.golang.org/protobuf/internal/detrand from google.golang.org/protobuf/internal/descfmt+
|
||||
@@ -71,16 +69,15 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
google.golang.org/protobuf/internal/set from google.golang.org/protobuf/encoding/prototext
|
||||
💣 google.golang.org/protobuf/internal/strs from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/internal/version from google.golang.org/protobuf/runtime/protoimpl
|
||||
google.golang.org/protobuf/proto from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/reflect/protodesc from github.com/golang/protobuf/proto
|
||||
💣 google.golang.org/protobuf/reflect/protoreflect from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/reflect/protoregistry from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/runtime/protoiface 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/proto from github.com/prometheus/client_golang/prometheus+
|
||||
💣 google.golang.org/protobuf/reflect/protoreflect from github.com/prometheus/client_model/go+
|
||||
google.golang.org/protobuf/reflect/protoregistry from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/runtime/protoiface from google.golang.org/protobuf/internal/impl+
|
||||
google.golang.org/protobuf/runtime/protoimpl from github.com/prometheus/client_model/go+
|
||||
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+
|
||||
nhooyr.io/websocket from tailscale.com/cmd/derper+
|
||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/util from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
|
||||
tailscale.com from tailscale.com/version
|
||||
tailscale.com/atomicfile from tailscale.com/cmd/derper+
|
||||
|
||||
@@ -158,7 +158,7 @@ func main() {
|
||||
if !ok && (!oiok || !osok) {
|
||||
log.Fatal("set envvar TS_API_KEY to your Tailscale API key or TS_OAUTH_ID and TS_OAUTH_SECRET to your Tailscale OAuth ID and Secret")
|
||||
}
|
||||
if ok && (oiok || osok) {
|
||||
if apiKey != "" && (oauthId != "" || oauthSecret != "") {
|
||||
log.Fatal("set either the envvar TS_API_KEY or TS_OAUTH_ID and TS_OAUTH_SECRET")
|
||||
}
|
||||
var client *http.Client
|
||||
|
||||
@@ -49,6 +49,8 @@ spec:
|
||||
image: {{ .Values.operatorConfig.image.repo }}{{- if .Values.operatorConfig.image.digest -}}{{ printf "@%s" .Values.operatorConfig.image.digest}}{{- else -}}{{ printf "%s" $operatorTag }}{{- end }}
|
||||
imagePullPolicy: {{ .Values.operatorConfig.image.pullPolicy }}
|
||||
env:
|
||||
- name: OPERATOR_INITIAL_TAGS
|
||||
value: {{ join "," .Values.operatorConfig.defaultTags }}
|
||||
- name: OPERATOR_HOSTNAME
|
||||
value: {{ .Values.operatorConfig.hostname }}
|
||||
- name: OPERATOR_SECRET
|
||||
|
||||
@@ -22,7 +22,7 @@ rules:
|
||||
resources: ["ingressclasses"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["tailscale.com"]
|
||||
resources: ["connectors", "connectors/status", "proxyclasses", "proxyclasses/status"]
|
||||
resources: ["connectors", "connectors/status"]
|
||||
verbs: ["get", "list", "watch", "update"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
||||
@@ -15,6 +15,13 @@ oauth: {}
|
||||
installCRDs: "true"
|
||||
|
||||
operatorConfig:
|
||||
# ACL tag that operator will be tagged with. Operator must be made owner of
|
||||
# these tags
|
||||
# https://tailscale.com/kb/1236/kubernetes-operator/?q=operator#setting-up-the-kubernetes-operator
|
||||
# Multiple tags are defined as array items and passed to the operator as a comma-separated string
|
||||
defaultTags:
|
||||
- "tag:k8s-operator"
|
||||
|
||||
image:
|
||||
repo: tailscale/k8s-operator
|
||||
# Digest will be prioritized over tag. If neither are set appVersion will be
|
||||
|
||||
@@ -1,465 +0,0 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.13.0
|
||||
name: proxyclasses.tailscale.com
|
||||
spec:
|
||||
group: tailscale.com
|
||||
names:
|
||||
kind: ProxyClass
|
||||
listKind: ProxyClassList
|
||||
plural: proxyclasses
|
||||
shortNames:
|
||||
- pc
|
||||
singular: proxyclass
|
||||
scope: Cluster
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
service:
|
||||
description: Configuration for the headless Service, not actually used in this prototype, but is here to better illustrate the API structure
|
||||
type: object
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
statefulSet:
|
||||
type: object
|
||||
properties:
|
||||
annotations:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
labels:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
pod:
|
||||
type: object
|
||||
properties:
|
||||
annotations:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
imagePullSecrets:
|
||||
type: array
|
||||
items:
|
||||
description: LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
x-kubernetes-map-type: atomic
|
||||
labels:
|
||||
description: Or should we just sync statefulset.labels, statefulset.annotations?
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
nodeName:
|
||||
type: string
|
||||
nodeSelector:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
patches:
|
||||
type: array
|
||||
items:
|
||||
description: RFC 6902 JSON patch
|
||||
type: object
|
||||
required:
|
||||
- op
|
||||
- path
|
||||
properties:
|
||||
op:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
podSecurityContext:
|
||||
description: PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.
|
||||
type: object
|
||||
properties:
|
||||
fsGroup:
|
||||
description: "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: \n 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- \n If unset, the Kubelet will not modify the ownership and permissions of any volume. Note that this field cannot be set when spec.os.name is windows."
|
||||
type: integer
|
||||
format: int64
|
||||
fsGroupChangePolicy:
|
||||
description: 'fsGroupChangePolicy defines behavior of changing ownership and permission of the volume before being exposed inside Pod. This field will only apply to volume types which support fsGroup based ownership(and permissions). It will have no effect on ephemeral volume types such as: secret, configmaps and emptydir. Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. Note that this field cannot be set when spec.os.name is windows.'
|
||||
type: string
|
||||
runAsGroup:
|
||||
description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: integer
|
||||
format: int64
|
||||
runAsNonRoot:
|
||||
description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: boolean
|
||||
runAsUser:
|
||||
description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: integer
|
||||
format: int64
|
||||
seLinuxOptions:
|
||||
description: The SELinux context to be applied to all containers. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: object
|
||||
properties:
|
||||
level:
|
||||
description: Level is SELinux level label that applies to the container.
|
||||
type: string
|
||||
role:
|
||||
description: Role is a SELinux role label that applies to the container.
|
||||
type: string
|
||||
type:
|
||||
description: Type is a SELinux type label that applies to the container.
|
||||
type: string
|
||||
user:
|
||||
description: User is a SELinux user label that applies to the container.
|
||||
type: string
|
||||
seccompProfile:
|
||||
description: The seccomp options to use by the containers in this pod. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must be set if type is "Localhost". Must NOT be set for any other type.
|
||||
type: string
|
||||
type:
|
||||
description: "type indicates which kind of seccomp profile will be applied. Valid options are: \n Localhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied."
|
||||
type: string
|
||||
supplementalGroups:
|
||||
description: A list of groups applied to the first process run in each container, in addition to the container's primary GID, the fsGroup (if specified), and group memberships defined in the container image for the uid of the container process. If unspecified, no additional groups are added to any container. Note that group memberships defined in the container image for the uid of the container process are still effective, even if they are not included in this list. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
format: int64
|
||||
sysctls:
|
||||
description: Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported sysctls (by the container runtime) might fail to launch. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: array
|
||||
items:
|
||||
description: Sysctl defines a kernel parameter to be set
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- value
|
||||
properties:
|
||||
name:
|
||||
description: Name of a property to set
|
||||
type: string
|
||||
value:
|
||||
description: Value of a property to set
|
||||
type: string
|
||||
windowsOptions:
|
||||
description: The Windows specific settings applied to all containers. If unspecified, the options within a container's SecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is linux.
|
||||
type: object
|
||||
properties:
|
||||
gmsaCredentialSpec:
|
||||
description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field.
|
||||
type: string
|
||||
gmsaCredentialSpecName:
|
||||
description: GMSACredentialSpecName is the name of the GMSA credential spec to use.
|
||||
type: string
|
||||
hostProcess:
|
||||
description: HostProcess determines if a container should be run as a 'Host Process' container. All of a Pod's containers must have the same effective HostProcess value (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). In addition, if HostProcess is true then HostNetwork must also be set to true.
|
||||
type: boolean
|
||||
runAsUserName:
|
||||
description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: string
|
||||
tailscaleContainer:
|
||||
type: object
|
||||
properties:
|
||||
resources:
|
||||
description: ResourceRequirements describes the compute resource requirements.
|
||||
type: object
|
||||
properties:
|
||||
claims:
|
||||
description: "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers."
|
||||
type: array
|
||||
items:
|
||||
description: ResourceClaim references one entry in PodSpec.ResourceClaims.
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
description: Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container.
|
||||
type: string
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
limits:
|
||||
description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
|
||||
type: object
|
||||
additionalProperties:
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
x-kubernetes-int-or-string: true
|
||||
requests:
|
||||
description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
|
||||
type: object
|
||||
additionalProperties:
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
x-kubernetes-int-or-string: true
|
||||
securityContext:
|
||||
description: SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.
|
||||
type: object
|
||||
properties:
|
||||
allowPrivilegeEscalation:
|
||||
description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.'
|
||||
type: boolean
|
||||
capabilities:
|
||||
description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: object
|
||||
properties:
|
||||
add:
|
||||
description: Added capabilities
|
||||
type: array
|
||||
items:
|
||||
description: Capability represent POSIX capabilities type
|
||||
type: string
|
||||
drop:
|
||||
description: Removed capabilities
|
||||
type: array
|
||||
items:
|
||||
description: Capability represent POSIX capabilities type
|
||||
type: string
|
||||
privileged:
|
||||
description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
procMount:
|
||||
description: procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: string
|
||||
readOnlyRootFilesystem:
|
||||
description: Whether this container has a read-only root filesystem. Default is false. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
runAsGroup:
|
||||
description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: integer
|
||||
format: int64
|
||||
runAsNonRoot:
|
||||
description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: boolean
|
||||
runAsUser:
|
||||
description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: integer
|
||||
format: int64
|
||||
seLinuxOptions:
|
||||
description: The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: object
|
||||
properties:
|
||||
level:
|
||||
description: Level is SELinux level label that applies to the container.
|
||||
type: string
|
||||
role:
|
||||
description: Role is a SELinux role label that applies to the container.
|
||||
type: string
|
||||
type:
|
||||
description: Type is a SELinux type label that applies to the container.
|
||||
type: string
|
||||
user:
|
||||
description: User is a SELinux user label that applies to the container.
|
||||
type: string
|
||||
seccompProfile:
|
||||
description: The seccomp options to use by this container. If seccomp options are provided at both the pod & container level, the container options override the pod options. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must be set if type is "Localhost". Must NOT be set for any other type.
|
||||
type: string
|
||||
type:
|
||||
description: "type indicates which kind of seccomp profile will be applied. Valid options are: \n Localhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied."
|
||||
type: string
|
||||
windowsOptions:
|
||||
description: The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is linux.
|
||||
type: object
|
||||
properties:
|
||||
gmsaCredentialSpec:
|
||||
description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field.
|
||||
type: string
|
||||
gmsaCredentialSpecName:
|
||||
description: GMSACredentialSpecName is the name of the GMSA credential spec to use.
|
||||
type: string
|
||||
hostProcess:
|
||||
description: HostProcess determines if a container should be run as a 'Host Process' container. All of a Pod's containers must have the same effective HostProcess value (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). In addition, if HostProcess is true then HostNetwork must also be set to true.
|
||||
type: boolean
|
||||
runAsUserName:
|
||||
description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: string
|
||||
tailscaleInitContainer:
|
||||
type: object
|
||||
properties:
|
||||
resources:
|
||||
description: ResourceRequirements describes the compute resource requirements.
|
||||
type: object
|
||||
properties:
|
||||
claims:
|
||||
description: "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers."
|
||||
type: array
|
||||
items:
|
||||
description: ResourceClaim references one entry in PodSpec.ResourceClaims.
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
description: Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container.
|
||||
type: string
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
limits:
|
||||
description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
|
||||
type: object
|
||||
additionalProperties:
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
x-kubernetes-int-or-string: true
|
||||
requests:
|
||||
description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
|
||||
type: object
|
||||
additionalProperties:
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
x-kubernetes-int-or-string: true
|
||||
securityContext:
|
||||
description: SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.
|
||||
type: object
|
||||
properties:
|
||||
allowPrivilegeEscalation:
|
||||
description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.'
|
||||
type: boolean
|
||||
capabilities:
|
||||
description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: object
|
||||
properties:
|
||||
add:
|
||||
description: Added capabilities
|
||||
type: array
|
||||
items:
|
||||
description: Capability represent POSIX capabilities type
|
||||
type: string
|
||||
drop:
|
||||
description: Removed capabilities
|
||||
type: array
|
||||
items:
|
||||
description: Capability represent POSIX capabilities type
|
||||
type: string
|
||||
privileged:
|
||||
description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
procMount:
|
||||
description: procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: string
|
||||
readOnlyRootFilesystem:
|
||||
description: Whether this container has a read-only root filesystem. Default is false. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
runAsGroup:
|
||||
description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: integer
|
||||
format: int64
|
||||
runAsNonRoot:
|
||||
description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: boolean
|
||||
runAsUser:
|
||||
description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: integer
|
||||
format: int64
|
||||
seLinuxOptions:
|
||||
description: The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: object
|
||||
properties:
|
||||
level:
|
||||
description: Level is SELinux level label that applies to the container.
|
||||
type: string
|
||||
role:
|
||||
description: Role is a SELinux role label that applies to the container.
|
||||
type: string
|
||||
type:
|
||||
description: Type is a SELinux type label that applies to the container.
|
||||
type: string
|
||||
user:
|
||||
description: User is a SELinux user label that applies to the container.
|
||||
type: string
|
||||
seccompProfile:
|
||||
description: The seccomp options to use by this container. If seccomp options are provided at both the pod & container level, the container options override the pod options. Note that this field cannot be set when spec.os.name is windows.
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must be set if type is "Localhost". Must NOT be set for any other type.
|
||||
type: string
|
||||
type:
|
||||
description: "type indicates which kind of seccomp profile will be applied. Valid options are: \n Localhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied."
|
||||
type: string
|
||||
windowsOptions:
|
||||
description: The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is linux.
|
||||
type: object
|
||||
properties:
|
||||
gmsaCredentialSpec:
|
||||
description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field.
|
||||
type: string
|
||||
gmsaCredentialSpecName:
|
||||
description: GMSACredentialSpecName is the name of the GMSA credential spec to use.
|
||||
type: string
|
||||
hostProcess:
|
||||
description: HostProcess determines if a container should be run as a 'Host Process' container. All of a Pod's containers must have the same effective HostProcess value (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). In addition, if HostProcess is true then HostNetwork must also be set to true.
|
||||
type: boolean
|
||||
runAsUserName:
|
||||
description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: string
|
||||
tolerations:
|
||||
type: array
|
||||
items:
|
||||
description: The pod this Toleration is attached to tolerates any taint that matches the triple <key,value,effect> using the matching operator <operator>.
|
||||
type: object
|
||||
properties:
|
||||
effect:
|
||||
description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.
|
||||
type: string
|
||||
key:
|
||||
description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys.
|
||||
type: string
|
||||
operator:
|
||||
description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category.
|
||||
type: string
|
||||
tolerationSeconds:
|
||||
description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system.
|
||||
type: integer
|
||||
format: int64
|
||||
value:
|
||||
description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string.
|
||||
type: string
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -1,11 +0,0 @@
|
||||
apiVersion: tailscale.com/v1alpha1
|
||||
kind: ProxyClass
|
||||
metadata:
|
||||
name: prod
|
||||
spec:
|
||||
statefulSet:
|
||||
pod:
|
||||
nodeSelector:
|
||||
beta.kubernetes.io/os: "linux"
|
||||
imagePullSecrets:
|
||||
- name: "foo"
|
||||
@@ -1,10 +0,0 @@
|
||||
apiVersion: tailscale.com/v1alpha1
|
||||
kind: ProxyClass
|
||||
metadata:
|
||||
name: removeinit
|
||||
spec:
|
||||
statefulSet:
|
||||
pod:
|
||||
patches:
|
||||
- op: remove
|
||||
path: "/spec/initContainers/0"
|
||||
@@ -186,8 +186,6 @@ rules:
|
||||
resources:
|
||||
- connectors
|
||||
- connectors/status
|
||||
- proxyclasses
|
||||
- proxyclasses/status
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
@@ -286,6 +284,8 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: OPERATOR_INITIAL_TAGS
|
||||
value: tag:k8s-operator
|
||||
- name: OPERATOR_HOSTNAME
|
||||
value: tailscale-operator
|
||||
- name: OPERATOR_SECRET
|
||||
|
||||
@@ -230,6 +230,12 @@ func (a *IngressReconciler) maybeProvision(ctx context.Context, logger *zap.Suga
|
||||
}
|
||||
}
|
||||
|
||||
if len(web.Handlers) == 0 {
|
||||
logger.Warn("Ingress contains no valid backends")
|
||||
a.recorder.Eventf(ing, corev1.EventTypeWarning, "NoValidBackends", "no valid backends")
|
||||
return nil
|
||||
}
|
||||
|
||||
crl := childResourceLabels(ing.Name, ing.Namespace, "ingress")
|
||||
var tags []string
|
||||
if tstr, ok := ing.Annotations[AnnotationTags]; ok {
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"go.uber.org/zap"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -28,7 +27,6 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/ipn"
|
||||
tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tsnet"
|
||||
@@ -54,9 +52,6 @@ const (
|
||||
//MagicDNS name of tailnet node.
|
||||
AnnotationTailnetTargetFQDN = "tailscale.com/tailnet-fqdn"
|
||||
|
||||
// Users can set this on a Service or Ingress. This prototype only looks at Services
|
||||
AnnotationProxyClass = "tailscale.com/proxy-class"
|
||||
|
||||
// Annotations settable by users on ingresses.
|
||||
AnnotationFunnel = "tailscale.com/funnel"
|
||||
|
||||
@@ -92,8 +87,6 @@ type tailscaleSTSConfig struct {
|
||||
// Connector specifies a configuration of a Connector instance if that's
|
||||
// what this StatefulSet should be created for.
|
||||
Connector *connector
|
||||
|
||||
ProxyClass string
|
||||
}
|
||||
|
||||
type connector struct {
|
||||
@@ -404,102 +397,7 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S
|
||||
}
|
||||
}
|
||||
}
|
||||
pod := &ss.Spec.Template
|
||||
container := &pod.Spec.Containers[0]
|
||||
// if proxyclass is set
|
||||
// get the proxy class
|
||||
// get the pod template thing from there
|
||||
// how to merge?
|
||||
if sts.ProxyClass != "" {
|
||||
logger.Infof("looking at proxy class %s", sts.ProxyClass)
|
||||
proxyClass := &tsapi.ProxyClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: sts.ProxyClass,
|
||||
},
|
||||
}
|
||||
if err := a.Get(ctx, client.ObjectKeyFromObject(proxyClass), proxyClass); err != nil {
|
||||
return nil, fmt.Errorf("failed to get ProxyClass: %w", err)
|
||||
}
|
||||
logger.Infof("retrieved proxy class %#+v", proxyClass.Spec)
|
||||
// only look at pod spec for this prototype
|
||||
if ssOverlay := proxyClass.Spec.StatefulSet; ssOverlay != nil && ssOverlay.Pod != nil {
|
||||
pod.Labels = ssOverlay.Pod.Labels
|
||||
pod.Annotations = ssOverlay.Pod.Annotations
|
||||
pod.Spec.NodeName = ssOverlay.Pod.NodeName
|
||||
pod.Spec.NodeSelector = ssOverlay.Pod.NodeSelector
|
||||
logger.Infof("Setting pod node selctor: %+#v", ssOverlay.Pod.NodeSelector)
|
||||
pod.Spec.ImagePullSecrets = ssOverlay.Pod.ImagePullSecrets
|
||||
pod.Spec.Tolerations = ssOverlay.Pod.Tolerations
|
||||
if ssOverlay.Pod.PodSecurityContext != nil {
|
||||
pod.Spec.SecurityContext = ssOverlay.Pod.PodSecurityContext
|
||||
}
|
||||
if contOverlay := proxyClass.Spec.StatefulSet.Pod.TailscaleContainer; contOverlay != nil {
|
||||
if contOverlay.SecurityContext != nil {
|
||||
// alternatively we could merge this with the existing security context
|
||||
container.SecurityContext = contOverlay.SecurityContext
|
||||
}
|
||||
container.Resources = contOverlay.Resources
|
||||
}
|
||||
if initContOverlay := proxyClass.Spec.StatefulSet.Pod.TailscaleInitContainer; initContOverlay != nil {
|
||||
if initContOverlay.SecurityContext != nil {
|
||||
pod.Spec.InitContainers[0].SecurityContext = initContOverlay.SecurityContext
|
||||
}
|
||||
}
|
||||
if len(ssOverlay.Pod.Patches) > 0 {
|
||||
logger.Info("applying overlay patches")
|
||||
// logger.Infof("before modifying pod's init containers are %#+v", pod.Spec.InitContainers)
|
||||
// get all patches together
|
||||
var patches []byte
|
||||
for _, patch := range ssOverlay.Pod.Patches {
|
||||
jsonBytes, err := json.Marshal(patch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling JSON patch: %w", err)
|
||||
}
|
||||
// there is definitely a better way
|
||||
jsonBytes = []byte("[" + string(jsonBytes) + "]")
|
||||
// patch, err := jsonpatch.DecodePatch(jsonBytes)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("error decoding JSON patch: %w", err)
|
||||
// }
|
||||
if len(patches) == 0 {
|
||||
patches = jsonBytes
|
||||
} else {
|
||||
patches, err = jsonpatch.MergeMergePatches(patches, jsonBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error merging patches: %w", err)
|
||||
}
|
||||
}
|
||||
logger.Infof("patch before merging : %+v\n", string(jsonBytes))
|
||||
}
|
||||
|
||||
// this can be done better
|
||||
podBytes, err := json.Marshal(pod)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling Pod spec to JSON: %w", err)
|
||||
}
|
||||
logger.Infof("patches before unmarshal: %+#v\n", string(patches))
|
||||
mergePatch, err := jsonpatch.DecodePatch(patches)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding JSON patches: %w", err)
|
||||
}
|
||||
modifiedPodBytes, err := mergePatch.Apply(podBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error applying patch: %w", err)
|
||||
}
|
||||
// modifiedPodBytes, err := jsonpatch.MergePatch(podBytes, patches)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("error updating Pod spec using merge patch: %w", err)
|
||||
// }
|
||||
// if jsonpatch.Equal(podBytes, modifiedPodBytes) {
|
||||
// logger.Info("no change was applied")
|
||||
// }
|
||||
logger.Infof("modified pod's init containers are %#+v", string(modifiedPodBytes))
|
||||
if err = json.Unmarshal(modifiedPodBytes, pod); err != nil {
|
||||
return nil, fmt.Errorf("error umarshaling pod bytes: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
container := &ss.Spec.Template.Spec.Containers[0]
|
||||
container.Image = a.proxyImage
|
||||
ss.ObjectMeta = metav1.ObjectMeta{
|
||||
Name: headlessSvc.Name,
|
||||
@@ -512,9 +410,9 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S
|
||||
"app": sts.ParentResourceUID,
|
||||
},
|
||||
}
|
||||
mak.Set(&pod.Labels, "app", sts.ParentResourceUID)
|
||||
mak.Set(&ss.Spec.Template.Labels, "app", sts.ParentResourceUID)
|
||||
for key, val := range sts.ChildResourceLabels {
|
||||
pod.Labels[key] = val // sync StatefulSet labels to Pod to make it easier for users to select the Pod
|
||||
ss.Spec.Template.Labels[key] = val // sync StatefulSet labels to Pod to make it easier for users to select the Pod
|
||||
}
|
||||
|
||||
// Generic containerboot configuration options.
|
||||
@@ -533,12 +431,12 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S
|
||||
// it is passed via an environment variable. So we need to restart the
|
||||
// container when the value changes. We do this by adding an annotation to
|
||||
// the pod template that contains the last value we set.
|
||||
mak.Set(&pod.Annotations, podAnnotationLastSetHostname, sts.Hostname)
|
||||
mak.Set(&ss.Spec.Template.Annotations, podAnnotationLastSetHostname, sts.Hostname)
|
||||
}
|
||||
// Configure containeboot to run tailscaled with a configfile read from the state Secret.
|
||||
if shouldDoTailscaledDeclarativeConfig(sts) {
|
||||
mak.Set(&ss.Spec.Template.Annotations, podAnnotationLastSetConfigFileHash, tsConfigHash)
|
||||
pod.Spec.Volumes = append(ss.Spec.Template.Spec.Volumes, corev1.Volume{
|
||||
ss.Spec.Template.Spec.Volumes = append(ss.Spec.Template.Spec.Volumes, corev1.Volume{
|
||||
Name: "tailscaledconfig",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
@@ -567,7 +465,7 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S
|
||||
Value: a.tsFirewallMode,
|
||||
})
|
||||
}
|
||||
pod.Spec.PriorityClassName = a.proxyPriorityClassName
|
||||
ss.Spec.Template.Spec.PriorityClassName = a.proxyPriorityClassName
|
||||
|
||||
// Ingress/egress proxy configuration options.
|
||||
if sts.ClusterTargetIP != "" {
|
||||
@@ -598,7 +496,7 @@ func (a *tailscaleSTSReconciler) reconcileSTS(ctx context.Context, logger *zap.S
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/tailscaled",
|
||||
})
|
||||
pod.Spec.Volumes = append(ss.Spec.Template.Spec.Volumes, corev1.Volume{
|
||||
ss.Spec.Template.Spec.Volumes = append(ss.Spec.Template.Spec.Volumes, corev1.Volume{
|
||||
Name: "serve-config",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
|
||||
@@ -183,7 +183,6 @@ func (a *ServiceReconciler) maybeProvision(ctx context.Context, logger *zap.Suga
|
||||
Hostname: hostname,
|
||||
Tags: tags,
|
||||
ChildResourceLabels: crl,
|
||||
ProxyClass: svc.GetAnnotations()[AnnotationProxyClass], // nil?
|
||||
}
|
||||
|
||||
a.mu.Lock()
|
||||
|
||||
@@ -2,9 +2,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
|
||||
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/golang/protobuf/proto from github.com/matttproud/golang_protobuf_extensions/pbutil
|
||||
github.com/google/uuid from tailscale.com/tsweb
|
||||
github.com/matttproud/golang_protobuf_extensions/pbutil from github.com/prometheus/common/expfmt
|
||||
💣 github.com/prometheus/client_golang/prometheus from tailscale.com/tsweb/promvarz
|
||||
github.com/prometheus/client_golang/prometheus/internal from github.com/prometheus/client_golang/prometheus
|
||||
github.com/prometheus/client_model/go from github.com/prometheus/client_golang/prometheus+
|
||||
@@ -16,8 +14,9 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
LD github.com/prometheus/procfs/internal/util from github.com/prometheus/procfs
|
||||
💣 go4.org/mem from tailscale.com/metrics+
|
||||
go4.org/netipx from tailscale.com/net/tsaddr
|
||||
google.golang.org/protobuf/encoding/prototext from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/encoding/protowire from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/encoding/protodelim from github.com/prometheus/common/expfmt
|
||||
google.golang.org/protobuf/encoding/prototext from github.com/prometheus/common/expfmt+
|
||||
google.golang.org/protobuf/encoding/protowire from google.golang.org/protobuf/encoding/protodelim+
|
||||
google.golang.org/protobuf/internal/descfmt from google.golang.org/protobuf/internal/filedesc
|
||||
google.golang.org/protobuf/internal/descopts from google.golang.org/protobuf/internal/filedesc+
|
||||
google.golang.org/protobuf/internal/detrand from google.golang.org/protobuf/internal/descfmt+
|
||||
@@ -36,13 +35,11 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
google.golang.org/protobuf/internal/set from google.golang.org/protobuf/encoding/prototext
|
||||
💣 google.golang.org/protobuf/internal/strs from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/internal/version from google.golang.org/protobuf/runtime/protoimpl
|
||||
google.golang.org/protobuf/proto from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/reflect/protodesc from github.com/golang/protobuf/proto
|
||||
💣 google.golang.org/protobuf/reflect/protoreflect from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/reflect/protoregistry from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/runtime/protoiface 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/proto from github.com/prometheus/client_golang/prometheus+
|
||||
💣 google.golang.org/protobuf/reflect/protoreflect from github.com/prometheus/client_model/go+
|
||||
google.golang.org/protobuf/reflect/protoregistry from google.golang.org/protobuf/encoding/prototext+
|
||||
google.golang.org/protobuf/runtime/protoiface from google.golang.org/protobuf/internal/impl+
|
||||
google.golang.org/protobuf/runtime/protoimpl from github.com/prometheus/client_model/go+
|
||||
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+
|
||||
tailscale.com from tailscale.com/version
|
||||
tailscale.com/envknob from tailscale.com/tsweb+
|
||||
@@ -102,7 +99,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
bytes from bufio+
|
||||
cmp from slices
|
||||
compress/flate from compress/gzip
|
||||
compress/gzip from github.com/golang/protobuf/proto+
|
||||
compress/gzip from google.golang.org/protobuf/internal/impl+
|
||||
container/list from crypto/tls+
|
||||
context from crypto/tls+
|
||||
crypto from crypto/ecdh+
|
||||
@@ -147,7 +144,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
html from net/http/pprof+
|
||||
io from bufio+
|
||||
io/fs from crypto/x509+
|
||||
io/ioutil from github.com/golang/protobuf/proto+
|
||||
io/ioutil from golang.org/x/sys/cpu+
|
||||
log from expvar+
|
||||
log/internal from log
|
||||
maps from tailscale.com/tailcfg+
|
||||
|
||||
@@ -272,7 +272,7 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
|
||||
// if foreground mode, create a WatchIPNBus session
|
||||
// and use the nested config for all following operations
|
||||
// TODO(marwan-at-work): nested-config validations should happen here or previous to this point.
|
||||
watcher, err = e.lc.WatchIPNBus(ctx, ipn.NotifyInitialState)
|
||||
watcher, err = e.lc.WatchIPNBus(ctx, ipn.NotifyInitialState|ipn.NotifyNoPrivateKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
|
||||
github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
💣 github.com/mattn/go-colorable from tailscale.com/cmd/tailscale/cli
|
||||
💣 github.com/mattn/go-isatty from github.com/mattn/go-colorable+
|
||||
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
|
||||
@@ -38,7 +37,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
github.com/peterbourgon/ff/v3 from github.com/peterbourgon/ff/v3/ffcli
|
||||
github.com/peterbourgon/ff/v3/ffcli from tailscale.com/cmd/tailscale/cli
|
||||
github.com/peterbourgon/ff/v3/internal from github.com/peterbourgon/ff/v3
|
||||
github.com/pkg/errors from github.com/gorilla/csrf
|
||||
github.com/skip2/go-qrcode from tailscale.com/cmd/tailscale/cli
|
||||
github.com/skip2/go-qrcode/bitset from github.com/skip2/go-qrcode+
|
||||
github.com/skip2/go-qrcode/reedsolomon from github.com/skip2/go-qrcode
|
||||
@@ -63,12 +61,13 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
💣 go4.org/mem from tailscale.com/derp+
|
||||
go4.org/netipx from tailscale.com/wgengine/filter+
|
||||
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
|
||||
gopkg.in/yaml.v2 from sigs.k8s.io/yaml
|
||||
k8s.io/client-go/util/homedir from tailscale.com/cmd/tailscale/cli
|
||||
nhooyr.io/websocket from tailscale.com/derp/derphttp+
|
||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/util from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
|
||||
sigs.k8s.io/yaml from tailscale.com/cmd/tailscale/cli
|
||||
sigs.k8s.io/yaml/goyaml.v2 from sigs.k8s.io/yaml
|
||||
software.sslmate.com/src/go-pkcs12 from tailscale.com/cmd/tailscale/cli
|
||||
software.sslmate.com/src/go-pkcs12/internal/rc2 from software.sslmate.com/src/go-pkcs12
|
||||
tailscale.com from tailscale.com/version
|
||||
|
||||
@@ -6,11 +6,11 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||
LD github.com/anmitsu/go-shlex from tailscale.com/tempfork/gliderlabs/ssh
|
||||
L github.com/aws/aws-sdk-go-v2 from github.com/aws/aws-sdk-go-v2/internal/ini
|
||||
L github.com/aws/aws-sdk-go-v2/aws from github.com/aws/aws-sdk-go-v2/aws/middleware+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/arn from tailscale.com/ipn/store/awsstore
|
||||
L github.com/aws/aws-sdk-go-v2/aws/defaults from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/middleware from github.com/aws/aws-sdk-go-v2/aws/retry+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/middleware/private/metrics from github.com/aws/aws-sdk-go-v2/aws/retry+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/protocol/query from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/aws-sdk-go-v2/aws/protocol/restjson from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/aws/protocol/xml from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
@@ -30,7 +30,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/aws-sdk-go-v2/feature/ec2/imds from github.com/aws/aws-sdk-go-v2/config+
|
||||
L github.com/aws/aws-sdk-go-v2/feature/ec2/imds/internal/config from github.com/aws/aws-sdk-go-v2/feature/ec2/imds
|
||||
L github.com/aws/aws-sdk-go-v2/internal/auth from github.com/aws/aws-sdk-go-v2/aws/signer/v4+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/auth/smithy from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/configsources from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 from github.com/aws/aws-sdk-go-v2/service/ssm/internal/endpoints+
|
||||
L github.com/aws/aws-sdk-go-v2/internal/ini from github.com/aws/aws-sdk-go-v2/config
|
||||
@@ -41,6 +43,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/aws-sdk-go-v2/internal/strings from github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4
|
||||
L github.com/aws/aws-sdk-go-v2/internal/sync/singleflight from github.com/aws/aws-sdk-go-v2/aws
|
||||
L github.com/aws/aws-sdk-go-v2/internal/timeconv from github.com/aws/aws-sdk-go-v2/aws/retry
|
||||
L github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/aws-sdk-go-v2/service/internal/presigned-url from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/aws-sdk-go-v2/service/ssm from tailscale.com/ipn/store/awsstore
|
||||
L github.com/aws/aws-sdk-go-v2/service/ssm/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/ssm
|
||||
@@ -55,6 +58,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/aws-sdk-go-v2/service/sts/internal/endpoints from github.com/aws/aws-sdk-go-v2/service/sts
|
||||
L github.com/aws/aws-sdk-go-v2/service/sts/types from github.com/aws/aws-sdk-go-v2/credentials/stscreds+
|
||||
L github.com/aws/smithy-go from github.com/aws/aws-sdk-go-v2/aws/protocol/restjson+
|
||||
L github.com/aws/smithy-go/auth from github.com/aws/aws-sdk-go-v2/internal/auth+
|
||||
L github.com/aws/smithy-go/auth/bearer from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/smithy-go/context from github.com/aws/smithy-go/auth/bearer
|
||||
L github.com/aws/smithy-go/document from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
@@ -67,6 +71,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/smithy-go/io from github.com/aws/aws-sdk-go-v2/feature/ec2/imds+
|
||||
L github.com/aws/smithy-go/logging from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/smithy-go/middleware from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/smithy-go/private/requestcompression from github.com/aws/aws-sdk-go-v2/config
|
||||
L github.com/aws/smithy-go/ptr from github.com/aws/aws-sdk-go-v2/aws+
|
||||
L github.com/aws/smithy-go/rand from github.com/aws/aws-sdk-go-v2/aws/middleware+
|
||||
L github.com/aws/smithy-go/time from github.com/aws/aws-sdk-go-v2/service/ssm+
|
||||
@@ -108,7 +113,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
github.com/klauspost/compress from github.com/klauspost/compress/zstd
|
||||
github.com/klauspost/compress/flate from nhooyr.io/websocket
|
||||
github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0
|
||||
github.com/klauspost/compress/huff0 from github.com/klauspost/compress/zstd
|
||||
github.com/klauspost/compress/internal/cpuinfo from github.com/klauspost/compress/zstd+
|
||||
@@ -130,7 +134,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/pierrec/lz4/v4/internal/lz4errors from github.com/pierrec/lz4/v4+
|
||||
L github.com/pierrec/lz4/v4/internal/lz4stream from github.com/pierrec/lz4/v4
|
||||
L github.com/pierrec/lz4/v4/internal/xxh32 from github.com/pierrec/lz4/v4/internal/lz4stream
|
||||
github.com/pkg/errors from github.com/gorilla/csrf
|
||||
LD github.com/pkg/sftp from tailscale.com/ssh/tailssh
|
||||
LD github.com/pkg/sftp/internal/encoding/ssh/filexfer from github.com/pkg/sftp
|
||||
L 💣 github.com/safchain/ethtool from tailscale.com/net/netkernelconf
|
||||
@@ -190,13 +193,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
💣 gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack
|
||||
gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/header+
|
||||
gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack
|
||||
gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+
|
||||
💣 gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+
|
||||
gvisor.dev/gvisor/pkg/tcpip/hash/jenkins from gvisor.dev/gvisor/pkg/tcpip/stack+
|
||||
gvisor.dev/gvisor/pkg/tcpip/header from gvisor.dev/gvisor/pkg/tcpip/header/parse+
|
||||
gvisor.dev/gvisor/pkg/tcpip/header/parse from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
gvisor.dev/gvisor/pkg/tcpip/internal/tcp from gvisor.dev/gvisor/pkg/tcpip/stack+
|
||||
gvisor.dev/gvisor/pkg/tcpip/link/channel from tailscale.com/wgengine/netstack
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/hash from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/hash from gvisor.dev/gvisor/pkg/tcpip/network/ipv4
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/internal/fragmentation from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/internal/ip from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/internal/multicast from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
@@ -219,6 +222,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
W 💣 inet.af/wf from tailscale.com/wf
|
||||
nhooyr.io/websocket from tailscale.com/derp/derphttp+
|
||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/util from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
|
||||
tailscale.com from tailscale.com/version
|
||||
tailscale.com/appc from tailscale.com/ipn/ipnlocal
|
||||
@@ -348,6 +352,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
|
||||
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics+
|
||||
tailscale.com/util/dnsname from tailscale.com/hostinfo+
|
||||
tailscale.com/util/execqueue from tailscale.com/control/controlclient+
|
||||
tailscale.com/util/goroutines from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/util/groupmember from tailscale.com/ipn/ipnauth+
|
||||
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
||||
@@ -490,7 +495,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
hash from crypto+
|
||||
hash/adler32 from compress/zlib+
|
||||
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
|
||||
html from tailscale.com/ipn/ipnlocal+
|
||||
html/template from github.com/gorilla/csrf
|
||||
|
||||
@@ -13,6 +13,7 @@ package main // import "tailscale.com/cmd/tailscaled"
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"expvar"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -729,7 +730,7 @@ func runDebugServer(mux *http.ServeMux, addr string) {
|
||||
}
|
||||
|
||||
func newNetstack(logf logger.Logf, sys *tsd.System) (*netstack.Impl, error) {
|
||||
return netstack.Create(logf,
|
||||
ret, err := netstack.Create(logf,
|
||||
sys.Tun.Get(),
|
||||
sys.Engine.Get(),
|
||||
sys.MagicSock.Get(),
|
||||
@@ -737,6 +738,14 @@ func newNetstack(logf logger.Logf, sys *tsd.System) (*netstack.Impl, error) {
|
||||
sys.DNSManager.Get(),
|
||||
sys.ProxyMapper(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Only register debug info if we have a debug mux
|
||||
if debugMux != nil {
|
||||
expvar.Publish("netstack", ret.ExpVar())
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// mustStartProxyListeners creates listeners for local SOCKS and HTTP
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/persist"
|
||||
"tailscale.com/types/structs"
|
||||
"tailscale.com/util/execqueue"
|
||||
)
|
||||
|
||||
type LoginGoal struct {
|
||||
@@ -118,7 +119,7 @@ type Auto struct {
|
||||
closed bool
|
||||
updateCh chan struct{} // readable when we should inform the server of a change
|
||||
observer Observer // called to update Client status; always non-nil
|
||||
observerQueue execQueue
|
||||
observerQueue execqueue.ExecQueue
|
||||
|
||||
unregisterHealthWatch func()
|
||||
|
||||
@@ -675,7 +676,7 @@ func (c *Auto) Shutdown() {
|
||||
direct := c.direct
|
||||
if !closed {
|
||||
c.closed = true
|
||||
c.observerQueue.shutdown()
|
||||
c.observerQueue.Shutdown()
|
||||
c.cancelAuthCtxLocked()
|
||||
c.cancelMapCtxLocked()
|
||||
for _, w := range c.unpauseWaiters {
|
||||
@@ -696,7 +697,7 @@ func (c *Auto) Shutdown() {
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
c.observerQueue.wait(ctx)
|
||||
c.observerQueue.Wait(ctx)
|
||||
c.logf("Client.Shutdown done.")
|
||||
}
|
||||
}
|
||||
@@ -737,95 +738,3 @@ func (c *Auto) DoNoiseRequest(req *http.Request) (*http.Response, error) {
|
||||
func (c *Auto) GetSingleUseNoiseRoundTripper(ctx context.Context) (http.RoundTripper, *tailcfg.EarlyNoise, error) {
|
||||
return c.direct.GetSingleUseNoiseRoundTripper(ctx)
|
||||
}
|
||||
|
||||
type execQueue struct {
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
inFlight bool // whether a goroutine is running q.run
|
||||
doneWaiter chan struct{} // non-nil if waiter is waiting, then closed
|
||||
queue []func()
|
||||
}
|
||||
|
||||
func (q *execQueue) Add(f func()) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
if q.closed {
|
||||
return
|
||||
}
|
||||
if q.inFlight {
|
||||
q.queue = append(q.queue, f)
|
||||
} else {
|
||||
q.inFlight = true
|
||||
go q.run(f)
|
||||
}
|
||||
}
|
||||
|
||||
// RunSync waits for the queue to be drained and then synchronously runs f.
|
||||
// It returns an error if the queue is closed before f is run or ctx expires.
|
||||
func (q *execQueue) RunSync(ctx context.Context, f func()) error {
|
||||
for {
|
||||
if err := q.wait(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
q.mu.Lock()
|
||||
if q.inFlight {
|
||||
q.mu.Unlock()
|
||||
continue
|
||||
}
|
||||
defer q.mu.Unlock()
|
||||
if q.closed {
|
||||
return errors.New("closed")
|
||||
}
|
||||
f()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (q *execQueue) run(f func()) {
|
||||
f()
|
||||
|
||||
q.mu.Lock()
|
||||
for len(q.queue) > 0 && !q.closed {
|
||||
f := q.queue[0]
|
||||
q.queue[0] = nil
|
||||
q.queue = q.queue[1:]
|
||||
q.mu.Unlock()
|
||||
f()
|
||||
q.mu.Lock()
|
||||
}
|
||||
q.inFlight = false
|
||||
q.queue = nil
|
||||
if q.doneWaiter != nil {
|
||||
close(q.doneWaiter)
|
||||
q.doneWaiter = nil
|
||||
}
|
||||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
func (q *execQueue) shutdown() {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
q.closed = true
|
||||
}
|
||||
|
||||
// wait waits for the queue to be empty.
|
||||
func (q *execQueue) wait(ctx context.Context) error {
|
||||
q.mu.Lock()
|
||||
waitCh := q.doneWaiter
|
||||
if q.inFlight && waitCh == nil {
|
||||
waitCh = make(chan struct{})
|
||||
q.doneWaiter = waitCh
|
||||
}
|
||||
q.mu.Unlock()
|
||||
|
||||
if waitCh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-waitCh:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,10 @@ type Knobs struct {
|
||||
// renewing node keys without breaking connections.
|
||||
// http://go/seamless-key-renewal
|
||||
SeamlessKeyRenewal atomic.Bool
|
||||
|
||||
// ProbeUDPLifetime is whether the node should probe UDP path lifetime on
|
||||
// the tail end of an active direct connection in magicsock.
|
||||
ProbeUDPLifetime atomic.Bool
|
||||
}
|
||||
|
||||
// UpdateFromNodeAttributes updates k (if non-nil) based on the provided self
|
||||
@@ -95,6 +99,7 @@ func (k *Knobs) UpdateFromNodeAttributes(selfNodeAttrs []tailcfg.NodeCapability,
|
||||
forceIPTables = has(tailcfg.NodeAttrLinuxMustUseIPTables)
|
||||
forceNfTables = has(tailcfg.NodeAttrLinuxMustUseNfTables)
|
||||
seamlessKeyRenewal = has(tailcfg.NodeAttrSeamlessKeyRenewal)
|
||||
probeUDPLifetime = has(tailcfg.NodeAttrProbeUDPLifetime)
|
||||
)
|
||||
|
||||
if has(tailcfg.NodeAttrOneCGNATEnable) {
|
||||
@@ -116,6 +121,7 @@ func (k *Knobs) UpdateFromNodeAttributes(selfNodeAttrs []tailcfg.NodeCapability,
|
||||
k.LinuxForceIPTables.Store(forceIPTables)
|
||||
k.LinuxForceNfTables.Store(forceNfTables)
|
||||
k.SeamlessKeyRenewal.Store(seamlessKeyRenewal)
|
||||
k.ProbeUDPLifetime.Store(probeUDPLifetime)
|
||||
}
|
||||
|
||||
// AsDebugJSON returns k as something that can be marshalled with json.Marshal
|
||||
@@ -138,5 +144,6 @@ func (k *Knobs) AsDebugJSON() map[string]any {
|
||||
"LinuxForceIPTables": k.LinuxForceIPTables.Load(),
|
||||
"LinuxForceNfTables": k.LinuxForceNfTables.Load(),
|
||||
"SeamlessKeyRenewal": k.SeamlessKeyRenewal.Load(),
|
||||
"ProbeUDPLifetime": k.ProbeUDPLifetime.Load(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<policyDefinitions revision="1.0" schemaVersion="1.0"
|
||||
xmlns="http://www.microsoft.com/GroupPolicy/PolicyDefinitions">
|
||||
<policyNamespaces>
|
||||
|
||||
@@ -120,4 +120,4 @@
|
||||
in
|
||||
flake-utils.lib.eachDefaultSystem (system: flakeForSystem nixpkgs system);
|
||||
}
|
||||
# nix-direnv cache busting line: sha256-8PtzUS8VL1p7KnqSx6Y55tOl41KYOhJfe52V4qMB3Yw=
|
||||
# nix-direnv cache busting line: sha256-b/iffKOn7nMiWvM0AIGGzZaJ15NTaBlJff+aja3NQio=
|
||||
|
||||
171
go.mod
171
go.mod
@@ -1,63 +1,65 @@
|
||||
module tailscale.com
|
||||
|
||||
go 1.21
|
||||
go 1.21.1
|
||||
|
||||
toolchain go1.21.5
|
||||
|
||||
require (
|
||||
filippo.io/mkcert v1.4.4
|
||||
github.com/akutz/memconn v0.1.0
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa
|
||||
github.com/andybalholm/brotli v1.0.5
|
||||
github.com/andybalholm/brotli v1.1.0
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.42
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.5
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.64
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.0
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.38.0
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/coreos/go-systemd/v22 v22.5.0
|
||||
github.com/creack/pty v1.1.18
|
||||
github.com/creack/pty v1.1.21
|
||||
github.com/dave/courtney v0.4.0
|
||||
github.com/dave/jennifer v1.7.0
|
||||
github.com/dave/patsy v0.0.0-20210517141501-957256f50cba
|
||||
github.com/dblohm7/wingoes v0.0.0-20230929194252-e994401fc077
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e
|
||||
github.com/dsnet/try v0.0.3
|
||||
github.com/evanw/esbuild v0.19.4
|
||||
github.com/frankban/quicktest v1.14.5
|
||||
github.com/evanw/esbuild v0.19.11
|
||||
github.com/frankban/quicktest v1.14.6
|
||||
github.com/fxamacker/cbor/v2 v2.5.0
|
||||
github.com/go-json-experiment/json v0.0.0-20230922184908-dc36ffcf8533
|
||||
github.com/go-logr/zapr v1.2.4
|
||||
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0
|
||||
github.com/go-logr/zapr v1.3.0
|
||||
github.com/go-ole/go-ole v1.3.0
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
|
||||
github.com/golangci/golangci-lint v1.52.2
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/go-containerregistry v0.17.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-containerregistry v0.18.0
|
||||
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/goreleaser/nfpm/v2 v2.33.1
|
||||
github.com/hdevalence/ed25519consensus v0.1.0
|
||||
github.com/hdevalence/ed25519consensus v0.2.0
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
github.com/illarion/gonotify v1.0.1
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
|
||||
github.com/jsimonetti/rtnetlink v1.3.5
|
||||
github.com/jsimonetti/rtnetlink v1.4.0
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.17.4
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mattn/go-isatty v0.0.19
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mdlayher/genetlink v1.3.2
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/mdlayher/sdnotify v1.0.0
|
||||
github.com/miekg/dns v1.1.56
|
||||
github.com/miekg/dns v1.1.58
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
github.com/peterbourgon/ff/v3 v3.4.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.13.6
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
github.com/prometheus/common v0.44.0
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
github.com/prometheus/common v0.46.0
|
||||
github.com/safchain/ethtool v0.3.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e
|
||||
@@ -73,55 +75,56 @@ require (
|
||||
github.com/tc-hib/winres v0.2.1
|
||||
github.com/tcnksm/go-httpstat v0.2.0
|
||||
github.com/toqueteos/webbrowser v1.2.0
|
||||
github.com/u-root/u-root v0.11.0
|
||||
github.com/u-root/u-root v0.12.0
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
github.com/vishvananda/netns v0.0.4
|
||||
go.uber.org/zap v1.26.0
|
||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925
|
||||
golang.org/x/crypto v0.17.1-0.20240102205709-08396bb92b82
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.18.0
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
|
||||
golang.org/x/mod v0.14.0
|
||||
golang.org/x/net v0.18.0
|
||||
golang.org/x/oauth2 v0.12.0
|
||||
golang.org/x/sync v0.5.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.org/x/term v0.15.0
|
||||
golang.org/x/time v0.3.0
|
||||
golang.org/x/tools v0.15.0
|
||||
golang.org/x/net v0.20.0
|
||||
golang.org/x/oauth2 v0.16.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/sys v0.16.0
|
||||
golang.org/x/term v0.16.0
|
||||
golang.org/x/time v0.5.0
|
||||
golang.org/x/tools v0.17.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gvisor.dev/gvisor v0.0.0-20230928000133-4fe30062272c
|
||||
gvisor.dev/gvisor v0.0.0-20240119233241-c9c1d4f9b186
|
||||
honnef.co/go/tools v0.4.6
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
|
||||
inet.af/tcpproxy v0.0.0-20231102063150-2862066fc2a9
|
||||
inet.af/wf v0.0.0-20221017222439-36129f591884
|
||||
k8s.io/api v0.28.2
|
||||
k8s.io/apimachinery v0.28.2
|
||||
k8s.io/apiserver v0.28.2
|
||||
k8s.io/client-go v0.28.2
|
||||
nhooyr.io/websocket v1.8.7
|
||||
k8s.io/api v0.29.1
|
||||
k8s.io/apimachinery v0.29.1
|
||||
k8s.io/apiserver v0.29.1
|
||||
k8s.io/client-go v0.29.1
|
||||
nhooyr.io/websocket v1.8.10
|
||||
sigs.k8s.io/controller-runtime v0.16.2
|
||||
sigs.k8s.io/controller-tools v0.13.0
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.1
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/dave/astrid v0.0.0-20170323122508-8c2895878b14 // indirect
|
||||
github.com/dave/brenda v1.1.0 // indirect
|
||||
github.com/gobuffalo/flect v1.0.2 // indirect
|
||||
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.1 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Abirdcfly/dupword v0.0.11 // indirect
|
||||
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||
github.com/Antonboom/errname v0.1.9 // indirect
|
||||
@@ -134,27 +137,26 @@ require (
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/OpenPeeDeeP/depguard v1.1.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/alingse/asasalint v0.0.11 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.5.1 // indirect
|
||||
github.com/ashanbrown/makezero v1.1.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 // indirect
|
||||
github.com/aws/smithy-go v1.14.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
|
||||
github.com/aws/smithy-go v1.19.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.0 // indirect
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
||||
@@ -167,35 +169,35 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||
github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect
|
||||
github.com/curioswitch/go-reassign v0.2.0 // indirect
|
||||
github.com/daixiang0/gci v0.10.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/denis-tingaikin/go-header v0.4.3 // indirect
|
||||
github.com/docker/cli v24.0.7+incompatible // indirect
|
||||
github.com/docker/cli v25.0.0+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v24.0.7+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/docker/docker v25.0.0+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.2 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/esimonov/ifshort v1.0.4 // indirect
|
||||
github.com/ettle/strcase v0.1.1 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.8.1 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/go-critic/go-critic v0.8.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.4.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.8.1 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/go-git/go-git/v5 v5.11.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.2 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.22.7 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.1.0 // indirect
|
||||
@@ -224,7 +226,7 @@ require (
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect
|
||||
github.com/goreleaser/chglog v0.5.0 // indirect
|
||||
github.com/goreleaser/fileglob v1.3.0 // indirect
|
||||
github.com/gorilla/csrf v1.7.1
|
||||
github.com/gorilla/csrf v1.7.2
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||
github.com/gostaticanalysis/comment v1.4.2 // indirect
|
||||
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
|
||||
@@ -267,7 +269,6 @@ require (
|
||||
github.com/maratori/testpackage v1.1.1 // indirect
|
||||
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
|
||||
github.com/mdlayher/socket v0.5.0 // indirect
|
||||
github.com/mgechev/revive v1.3.1 // indirect
|
||||
@@ -287,14 +288,14 @@ require (
|
||||
github.com/nunnatsa/ginkgolinter v0.11.2 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc6 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/polyfloyd/go-errorlint v1.4.1 // indirect
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.3.19 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
@@ -319,8 +320,8 @@ require (
|
||||
github.com/sonatard/noctx v0.0.2 // indirect
|
||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/cobra v1.8.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.16.0 // indirect
|
||||
@@ -337,7 +338,7 @@ require (
|
||||
github.com/timonwong/loggercheck v0.9.4 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/ultraware/funlen v0.0.3 // indirect
|
||||
github.com/ultraware/whitespace v0.0.5 // indirect
|
||||
@@ -350,27 +351,27 @@ require (
|
||||
gitlab.com/bosi/decorder v0.2.3 // indirect
|
||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/image v0.12.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a // indirect
|
||||
golang.org/x/image v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.28.2 // indirect
|
||||
k8s.io/component-base v0.28.2 // indirect
|
||||
k8s.io/klog/v2 v2.100.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230928205116-a78145627833 // indirect
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||
k8s.io/apiextensions-apiserver v0.29.1 // indirect
|
||||
k8s.io/component-base v0.29.1 // indirect
|
||||
k8s.io/klog/v2 v2.120.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240117194847-208609032b15 // indirect
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect
|
||||
mvdan.cc/gofumpt v0.5.0 // indirect
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
|
||||
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
sha256-8PtzUS8VL1p7KnqSx6Y55tOl41KYOhJfe52V4qMB3Yw=
|
||||
sha256-b/iffKOn7nMiWvM0AIGGzZaJ15NTaBlJff+aja3NQio=
|
||||
|
||||
402
go.sum
402
go.sum
@@ -42,8 +42,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
|
||||
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
|
||||
github.com/Abirdcfly/dupword v0.0.11 h1:z6v8rMETchZXUIuHxYNmlUAuKuB21PeaSymTed16wgU=
|
||||
@@ -78,14 +78,12 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/OpenPeeDeeP/depguard v1.1.1 h1:TSUznLjvp/4IUP+OQ0t/4jF4QUyxIcVX8YnghZdunyA=
|
||||
github.com/OpenPeeDeeP/depguard v1.1.1/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT9jnRVsohBKpc=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs=
|
||||
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
|
||||
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
||||
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -99,8 +97,8 @@ github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pO
|
||||
github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=
|
||||
github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
|
||||
github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
|
||||
@@ -111,58 +109,58 @@ github.com/ashanbrown/forbidigo v1.5.1/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1
|
||||
github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s=
|
||||
github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.22/go.mod h1:mN7Li1wxaPxSSy4Xkr6stFuinJGf3VZW3ZSNvO0q6sI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.42 h1:28jHROB27xZwU0CB88giDSjz7M1Sba3olb5JBGwina8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.42/go.mod h1:4AZM3nMMxwlG+eZlxvBKqwVbkDLlnN2a4UGTL6HjaZI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.21/go.mod h1:90Dk1lJoMyspa/EDUrldTxsPns0wn6+KpRKpdAWc0uA=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.40 h1:s8yOkDh+5b1jUDhMBtngF6zKWLDs84chUk2Vk0c38Og=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.40/go.mod h1:VtEHVAAqDWASwdOqj/1huyT6uHbs5s8FUHfDQdky/Rs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.64 h1:9QJQs36z61YB8nxGwRDfWXEDYbU6H7jdI6zFiAX1vag=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.64/go.mod h1:4Q7R9MFpXRdjO3YnAfUTdnuENs32WzBkASt6VxSYDYQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 h1:g+qlObJH4Kn4n21g69DjspU0hKTjWtq7naZ9OLCv0ew=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 h1:AzwRi5OKKwo4QNqPf7TjeO+tK8AyOK3GVSwmRPo7/Cs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25/go.mod h1:SUbB4wcbSEyCvqBxv/O/IBf93RbEze7U7OnoTlpPB+g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 h1:vGWm5vTpMr39tEZfQeDiDAMgk+5qsnvRny3FjLpnH5w=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28/go.mod h1:spfrICMD6wCAhjhzHuy6DOZZ+LAIY10UxhUmLzpJTTs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 h1:NbWkRxEEIRSCqxhsHQuMiTH7yo+JZW1gp8v3elSVMTQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2/go.mod h1:4tfW5l4IAB32VWCDEBxCRtR9T4BWy4I4kr1spr8NgZM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.0 h1:L5h2fymEdVJYvn6hYO8Jx48YmC6xVmjmgHJV3oGKgmc=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.33.0/go.mod h1:J9kLNzEiHSeGMyN7238EjJmBpCniVzFda75Gxl/NqB8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.38.0 h1:JON9MBvwUlM8HXylfB2caZuH3VXz9RxO4SMp2+TNc3Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.38.0/go.mod h1:JjBzoceyKkpQY3v1GPIdg6kHqUFHRJ7SDlwtwoH0Qh8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.9/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 h1:YkNzx1RLS0F5qdf9v1Q8Cuv9NXCL2TkosOxhzlUPV64=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.9/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 h1:8lKOidPkmSmfUtiTgtdXWgaKItCZ/g75/jEk6Ql6GsA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.10/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 h1:s4bioTgjSFRwOoyEFzAVCmFmoowBgjTR8gkrF/sQ4wk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
|
||||
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ=
|
||||
github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
|
||||
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -200,11 +198,12 @@ github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXa
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
|
||||
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
|
||||
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
|
||||
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
@@ -216,12 +215,14 @@ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pq
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo=
|
||||
github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/daixiang0/gci v0.10.1 h1:eheNA3ljF6SxnPD/vE4lCBusVHmV3Rs3dkKvFrJ7MR0=
|
||||
github.com/daixiang0/gci v0.10.1/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI=
|
||||
github.com/dave/astrid v0.0.0-20170323122508-8c2895878b14 h1:YI1gOOdmMk3xodBao7fehcvoZsEeOyy/cfhlpCSPgM4=
|
||||
@@ -237,26 +238,26 @@ github.com/dave/patsy v0.0.0-20210517141501-957256f50cba/go.mod h1:qfR88CgEGLoiq
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dblohm7/wingoes v0.0.0-20230929194252-e994401fc077 h1:WphxHslVftszsr0oZOHPaOjpmN/BsgNYF+gW/hxZXXc=
|
||||
github.com/dblohm7/wingoes v0.0.0-20230929194252-e994401fc077/go.mod h1:6NCrWM5jRefaG7iN0iMShPalLsljHWBh9v1zxM2f8Xs=
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
|
||||
github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU=
|
||||
github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c=
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
||||
github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg=
|
||||
github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v25.0.0+incompatible h1:zaimaQdnX7fYWFqzN88exE9LDEvRslexpFowZBX6GoQ=
|
||||
github.com/docker/cli v25.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
|
||||
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
|
||||
github.com/docker/docker v25.0.0+incompatible h1:g9b6wZTblhMgzOT2tspESstfw6ySZ9kdm94BLDKaZac=
|
||||
github.com/docker/docker v25.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
|
||||
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI=
|
||||
github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40=
|
||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
|
||||
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/emicklei/go-restful/v3 v3.11.2 h1:1onLa9DcsMYO9P+CXaL0dStDqQ2EHHXLiz+BtnqkLAU=
|
||||
github.com/emicklei/go-restful/v3 v3.11.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -271,28 +272,24 @@ 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/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 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc=
|
||||
github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
github.com/evanw/esbuild v0.19.4 h1:Etk+6ZCjtNxZZLEgMKSqpO0/oM0k1WYKJabaPMJ39iQ=
|
||||
github.com/evanw/esbuild v0.19.4/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
|
||||
github.com/evanphx/json-patch/v5 v5.8.1 h1:iPEdwg0XayoS+E7Mth9JxwUtOgyVxnDTXHtKhZPlZxA=
|
||||
github.com/evanphx/json-patch/v5 v5.8.1/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
github.com/evanw/esbuild v0.19.11 h1:mbPO1VJ/df//jjUd+p/nRLYCpizXxXb2w/zZMShxa2k=
|
||||
github.com/evanw/esbuild v0.19.11/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y=
|
||||
github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
@@ -301,45 +298,35 @@ github.com/go-critic/go-critic v0.8.0 h1:4zOcpvDoKvBOl+R1W81IBznr78f8YaE4zKXkfDV
|
||||
github.com/go-critic/go-critic v0.8.0/go.mod h1:5TjdkPI9cu/yKbYS96BTsslihjKd6zg6vd8O9RZXj2s=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
|
||||
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
|
||||
github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A=
|
||||
github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo=
|
||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
|
||||
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-json-experiment/json v0.0.0-20230922184908-dc36ffcf8533 h1:1SRqDZauC9fz6vMIDLCUOULPNfOnZ0rmvZo8quraoy4=
|
||||
github.com/go-json-experiment/json v0.0.0-20230922184908-dc36ffcf8533/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA=
|
||||
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg=
|
||||
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
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-logr/logr v1.2.0/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/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
|
||||
github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
||||
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
|
||||
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
||||
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
|
||||
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
|
||||
github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU=
|
||||
github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4=
|
||||
github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8=
|
||||
github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0=
|
||||
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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
@@ -367,12 +354,6 @@ github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA
|
||||
github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||
@@ -450,10 +431,11 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.17.0 h1:5p+zYs/R4VGHkhyvgWurWrpJ2hW4Vv9fQI+GzdcwXLk=
|
||||
github.com/google/go-containerregistry v0.17.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.18.0 h1:ShE7erKNPqRh5ue6Z9DUOlk04WsnFWPO6YGr3OxnfoQ=
|
||||
github.com/google/go-containerregistry v0.18.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -481,8 +463,8 @@ github.com/google/rpmpack v0.5.0 h1:L16KZ3QvkFGpYhmp23iQip+mx1X39foEsqszjMNBm8A=
|
||||
github.com/google/rpmpack v0.5.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
@@ -496,13 +478,10 @@ github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+
|
||||
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
|
||||
github.com/goreleaser/nfpm/v2 v2.33.1 h1:EkdAzZyVhAI9JC1vjmjjbmnNzyH1J6Cu4JCsA7YcQuc=
|
||||
github.com/goreleaser/nfpm/v2 v2.33.1/go.mod h1:8wwWWvJWmn84xo/Sqiv0aMvEGTHlHZTXTEuVSgQpkIM=
|
||||
github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
|
||||
github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
|
||||
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
|
||||
github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
|
||||
@@ -527,13 +506,15 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU=
|
||||
github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f h1:ov45/OzrJG8EKbGjn7jJZQJTN7Z1t73sFYNIRd64YlI=
|
||||
github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f/go.mod h1:JoDrYMZpDPYo6uH9/f6Peqms3zNNWT2XiGgioMOIGuI=
|
||||
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
@@ -545,8 +526,8 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -566,10 +547,9 @@ github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/ra
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
|
||||
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/jsimonetti/rtnetlink v1.3.5 h1:hVlNQNRlLDGZz31gBPicsG7Q53rnlsz1l1Ix/9XlpVA=
|
||||
github.com/jsimonetti/rtnetlink v1.3.5/go.mod h1:0LFedyiTkebnd43tE4YAkWGIq9jQphow4CcwxaT2Y00=
|
||||
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
|
||||
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
@@ -595,7 +575,6 @@ github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8=
|
||||
github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
@@ -608,7 +587,6 @@ github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -625,8 +603,6 @@ github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUc
|
||||
github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0=
|
||||
github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo=
|
||||
github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU=
|
||||
github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY=
|
||||
github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM=
|
||||
@@ -641,21 +617,17 @@ github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1r
|
||||
github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc=
|
||||
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE=
|
||||
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo=
|
||||
github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
|
||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||
@@ -668,8 +640,8 @@ github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI
|
||||
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||
github.com/mgechev/revive v1.3.1 h1:OlQkcH40IB2cGuprTPcjB0iIUddgVZgGmDX3IAMR8D4=
|
||||
github.com/mgechev/revive v1.3.1/go.mod h1:YlD6TTWl2B8A103R9KWJSPVI9DrEf+oqr15q21Ld+5I=
|
||||
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
|
||||
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/mitchellh/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/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
@@ -701,7 +673,6 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6Fx
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nishanths/exhaustive v0.10.0 h1:BMznKAcVa9WOoLq/kTGp4NJOJSMwEpcpjFNAVRfPlSo=
|
||||
github.com/nishanths/exhaustive v0.10.0/go.mod h1:IbwrGdVMizvDcIxPYGVdQn5BqWJaOwpCvg4RGb8r/TA=
|
||||
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
|
||||
@@ -714,14 +685,14 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
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/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k=
|
||||
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
@@ -733,8 +704,8 @@ github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNc
|
||||
github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
|
||||
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
|
||||
@@ -756,20 +727,20 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y=
|
||||
github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
@@ -844,10 +815,10 @@ github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag07
|
||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
@@ -924,16 +895,12 @@ github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
|
||||
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
|
||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
|
||||
github.com/u-root/gobusybox/src v0.0.0-20221229083637-46b2883a7f90 h1:zTk5683I9K62wtZ6eUa6vu6IWwVHXPnoKK5n2unAwv0=
|
||||
github.com/u-root/gobusybox/src v0.0.0-20221229083637-46b2883a7f90/go.mod h1:lYt+LVfZBBwDZ3+PHk4k/c/TnKOkjJXiJO73E32Mmpc=
|
||||
github.com/u-root/u-root v0.11.0 h1:6gCZLOeRyevw7gbTwMj3fKxnr9+yHFlgF3N7udUVNO8=
|
||||
github.com/u-root/u-root v0.11.0/go.mod h1:DBkDtiZyONk9hzVEdB/PWI9B4TxDkElWlVTHseglrZY=
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
|
||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa h1:unMPGGK/CRzfg923allsikmvk2l7beBeFPUNC4RVX/8=
|
||||
github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa/go.mod h1:Zj4Tt22fJVn/nz/y6Ergm1SahR9dio1Zm/D2/S0TmXM=
|
||||
github.com/u-root/u-root v0.12.0 h1:K0AuBFriwr0w/PGS3HawiAw89e3+MU7ks80GpghAsNs=
|
||||
github.com/u-root/u-root v0.12.0/go.mod h1:FYjTOh4IkIZHhjsd17lb8nYW6udgXdJhG1c0r6u0arI=
|
||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw=
|
||||
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA=
|
||||
@@ -976,20 +943,16 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
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/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8=
|
||||
go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -1004,8 +967,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.17.1-0.20240102205709-08396bb92b82 h1:Im4GabMwJDxh7eJBIF8XGVAyhmlqdBQmZV49AzWdKEk=
|
||||
golang.org/x/crypto v0.17.1-0.20240102205709-08396bb92b82/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1016,16 +979,16 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9 h1:j3D9DvWRpUfIyFfDPws7LoIZ2MAI1OJHdQXtTnYtN+k=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230905200255-921286631fa9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a h1:8qmSSA8Gz/1kTrCe0nqR0R3Gb/NDhykzWw2q2mWZydM=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
|
||||
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -1100,8 +1063,8 @@ golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1112,8 +1075,8 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
|
||||
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
|
||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1127,8 +1090,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1147,7 +1110,6 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1189,7 +1151,6 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1197,8 +1158,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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -1207,8 +1168,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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1223,14 +1184,13 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -1304,8 +1264,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.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.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
||||
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1408,8 +1368,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -1441,8 +1401,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
gvisor.dev/gvisor v0.0.0-20230928000133-4fe30062272c h1:bYb98Ra11fJ8F2xFbZx0zg2VQ28lYqC1JxfaaF53xqY=
|
||||
gvisor.dev/gvisor v0.0.0-20230928000133-4fe30062272c/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||
gvisor.dev/gvisor v0.0.0-20240119233241-c9c1d4f9b186 h1:VWRSJX9ghfqsRSZGMAILL6QpYRKWnHcYPi24SCubQRs=
|
||||
gvisor.dev/gvisor v0.0.0-20240119233241-c9c1d4f9b186/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -1460,24 +1420,24 @@ inet.af/tcpproxy v0.0.0-20231102063150-2862066fc2a9 h1:zomTWJvjwLbKRgGameQtpK6DN
|
||||
inet.af/tcpproxy v0.0.0-20231102063150-2862066fc2a9/go.mod h1:Tojt5kmHpDIR2jMojxzZK2w2ZR7OILODmUo2gaSwjrk=
|
||||
inet.af/wf v0.0.0-20221017222439-36129f591884 h1:zg9snq3Cpy50lWuVqDYM7AIRVTtU50y5WXETMFohW/Q=
|
||||
inet.af/wf v0.0.0-20221017222439-36129f591884/go.mod h1:bSAQ38BYbY68uwpasXOTZo22dKGy9SNvI6PZFeKomZE=
|
||||
k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw=
|
||||
k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg=
|
||||
k8s.io/apiextensions-apiserver v0.28.2 h1:J6/QRWIKV2/HwBhHRVITMLYoypCoPY1ftigDM0Kn+QU=
|
||||
k8s.io/apiextensions-apiserver v0.28.2/go.mod h1:5tnkxLGa9nefefYzWuAlWZ7RZYuN/765Au8cWLA6SRg=
|
||||
k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ=
|
||||
k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU=
|
||||
k8s.io/apiserver v0.28.2 h1:rBeYkLvF94Nku9XfXyUIirsVzCzJBs6jMn3NWeHieyI=
|
||||
k8s.io/apiserver v0.28.2/go.mod h1:f7D5e8wH8MWcKD7azq6Csw9UN+CjdtXIVQUyUhrtb+E=
|
||||
k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY=
|
||||
k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY=
|
||||
k8s.io/component-base v0.28.2 h1:Yc1yU+6AQSlpJZyvehm/NkJBII72rzlEsd6MkBQ+G0E=
|
||||
k8s.io/component-base v0.28.2/go.mod h1:4IuQPQviQCg3du4si8GpMrhAIegxpsgPngPRR/zWpzc=
|
||||
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
|
||||
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20230928205116-a78145627833 h1:iFFEmmB7szQhJP42AvRD2+gzdVP7EuIKY1rJgxf0JZY=
|
||||
k8s.io/kube-openapi v0.0.0-20230928205116-a78145627833/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw=
|
||||
k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ=
|
||||
k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw=
|
||||
k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU=
|
||||
k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc=
|
||||
k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU=
|
||||
k8s.io/apiserver v0.29.1 h1:e2wwHUfEmMsa8+cuft8MT56+16EONIEK8A/gpBSco+g=
|
||||
k8s.io/apiserver v0.29.1/go.mod h1:V0EpkTRrJymyVT3M49we8uh2RvXf7fWC5XLB0P3SwRw=
|
||||
k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A=
|
||||
k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks=
|
||||
k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw=
|
||||
k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc=
|
||||
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
|
||||
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20240117194847-208609032b15 h1:m6dl1pkxz3HuE2mP9MUYPCCGyy6IIFlv/vTlLBDxIwA=
|
||||
k8s.io/kube-openapi v0.0.0-20240117194847-208609032b15/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw=
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ=
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E=
|
||||
mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I=
|
||||
@@ -1486,8 +1446,8 @@ mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphD
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 h1:VuJo4Mt0EVPychre4fNlDWDuE5AjXtPJpRUWqZDQhaI=
|
||||
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8/go.mod h1:Oh/d7dEtzsNHGOq1Cdv8aMm3KdKhVvPbRQcM8WFpBR8=
|
||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
|
||||
nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
@@ -1497,9 +1457,9 @@ sigs.k8s.io/controller-tools v0.13.0 h1:NfrvuZ4bxyolhDBt/rCZhDnx3M2hzlhgo5n3Iv2R
|
||||
sigs.k8s.io/controller-tools v0.13.0/go.mod h1:5vw3En2NazbejQGCeWKRrE7q4P+CW8/klfVqP8QZkgA=
|
||||
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/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.1 h1:tbT1jjaeFOF230tzOIRJ6U5S1jNqpsSyNjzDd58H3J8=
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.1/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
|
||||
@@ -1 +1 @@
|
||||
7592922bcb2af4e0b49bc67d7923fbdabe168dab
|
||||
ea90ced9ddc95c09aed7d9c59631aa978022c3ba
|
||||
|
||||
@@ -3446,15 +3446,21 @@ func (b *LocalBackend) reconfigAppConnectorLocked(nm *netmap.NetworkMap, prefs i
|
||||
})
|
||||
}
|
||||
|
||||
var domains []string
|
||||
var (
|
||||
domains []string
|
||||
routes []netip.Prefix
|
||||
)
|
||||
for _, attr := range attrs {
|
||||
if slices.Contains(attr.Connectors, "*") || selfHasTag(attr.Connectors) {
|
||||
domains = append(domains, attr.Domains...)
|
||||
routes = append(routes, attr.Routes...)
|
||||
}
|
||||
}
|
||||
slices.Sort(domains)
|
||||
slices.SortFunc(routes, func(i, j netip.Prefix) int { return i.Addr().Compare(j.Addr()) })
|
||||
domains = slices.Compact(domains)
|
||||
b.appConnector.UpdateDomains(domains)
|
||||
routes = slices.Compact(routes)
|
||||
b.appConnector.UpdateDomainsAndRoutes(domains, routes)
|
||||
}
|
||||
|
||||
// authReconfig pushes a new configuration into wgengine, if engine
|
||||
@@ -4550,6 +4556,7 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
|
||||
}
|
||||
|
||||
b.MagicConn().SetSilentDisco(b.ControlKnobs().SilentDisco.Load())
|
||||
b.MagicConn().SetProbeUDPLifetime(b.ControlKnobs().ProbeUDPLifetime.Load())
|
||||
|
||||
b.setDebugLogsByCapabilityLocked(nm)
|
||||
|
||||
@@ -5784,21 +5791,73 @@ var ErrDisallowedAutoRoute = errors.New("route is not allowed")
|
||||
// AdvertiseRoute implements the appc.RouteAdvertiser interface. It sets a new
|
||||
// route advertisement if one is not already present in the existing routes.
|
||||
// If the route is disallowed, ErrDisallowedAutoRoute is returned.
|
||||
func (b *LocalBackend) AdvertiseRoute(ipp netip.Prefix) error {
|
||||
if !allowedAutoRoute(ipp) {
|
||||
return ErrDisallowedAutoRoute
|
||||
func (b *LocalBackend) AdvertiseRoute(ipps ...netip.Prefix) error {
|
||||
finalRoutes := b.Prefs().AdvertiseRoutes().AsSlice()
|
||||
newRoutes := false
|
||||
|
||||
for _, ipp := range ipps {
|
||||
if !allowedAutoRoute(ipp) {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(finalRoutes, ipp) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the new prefix is already contained by existing routes, skip it.
|
||||
if coveredRouteRange(finalRoutes, ipp) {
|
||||
continue
|
||||
}
|
||||
|
||||
finalRoutes = append(finalRoutes, ipp)
|
||||
newRoutes = true
|
||||
}
|
||||
currentRoutes := b.Prefs().AdvertiseRoutes()
|
||||
if currentRoutes.ContainsFunc(func(r netip.Prefix) bool {
|
||||
// TODO(raggi): add support for subset checks and avoid subset route creations.
|
||||
return ipp.IsSingleIP() && r.Contains(ipp.Addr()) || r == ipp
|
||||
}) {
|
||||
|
||||
if !newRoutes {
|
||||
return nil
|
||||
}
|
||||
routes := append(currentRoutes.AsSlice(), ipp)
|
||||
|
||||
_, err := b.EditPrefs(&ipn.MaskedPrefs{
|
||||
Prefs: ipn.Prefs{
|
||||
AdvertiseRoutes: routes,
|
||||
AdvertiseRoutes: finalRoutes,
|
||||
},
|
||||
AdvertiseRoutesSet: true,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// coveredRouteRange checks if a route is already included in a slice of
|
||||
// prefixes.
|
||||
func coveredRouteRange(finalRoutes []netip.Prefix, ipp netip.Prefix) bool {
|
||||
for _, r := range finalRoutes {
|
||||
if ipp.IsSingleIP() {
|
||||
if r.Contains(ipp.Addr()) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if r.Contains(ipp.Addr()) && r.Contains(netipx.PrefixLastIP(ipp)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UnadvertiseRoute implements the appc.RouteAdvertiser interface. It removes
|
||||
// a route advertisement if one is present in the existing routes.
|
||||
func (b *LocalBackend) UnadvertiseRoute(toRemove ...netip.Prefix) error {
|
||||
currentRoutes := b.Prefs().AdvertiseRoutes().AsSlice()
|
||||
finalRoutes := currentRoutes[:0]
|
||||
|
||||
for _, ipp := range currentRoutes {
|
||||
if slices.Contains(toRemove, ipp) {
|
||||
continue
|
||||
}
|
||||
finalRoutes = append(finalRoutes, ipp)
|
||||
}
|
||||
|
||||
_, err := b.EditPrefs(&ipn.MaskedPrefs{
|
||||
Prefs: ipn.Prefs{
|
||||
AdvertiseRoutes: finalRoutes,
|
||||
},
|
||||
AdvertiseRoutesSet: true,
|
||||
})
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"go4.org/netipx"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/appc"
|
||||
"tailscale.com/appc/appctest"
|
||||
"tailscale.com/control/controlclient"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/store/mem"
|
||||
@@ -1169,6 +1170,13 @@ func TestRouteAdvertiser(t *testing.T) {
|
||||
if routes.Len() != 1 || routes.At(0) != testPrefix {
|
||||
t.Fatalf("got routes %v, want %v", routes, []netip.Prefix{testPrefix})
|
||||
}
|
||||
|
||||
must.Do(ra.UnadvertiseRoute(testPrefix))
|
||||
|
||||
routes = b.Prefs().AdvertiseRoutes()
|
||||
if routes.Len() != 0 {
|
||||
t.Fatalf("got routes %v, want none", routes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterAdvertiserIgnoresContainedRoutes(t *testing.T) {
|
||||
@@ -1197,14 +1205,52 @@ func TestObserveDNSResponse(t *testing.T) {
|
||||
// ensure no error when no app connector is configured
|
||||
b.ObserveDNSResponse(dnsResponse("example.com.", "192.0.0.8"))
|
||||
|
||||
rc := &routeCollector{}
|
||||
rc := &appctest.RouteCollector{}
|
||||
b.appConnector = appc.NewAppConnector(t.Logf, rc)
|
||||
b.appConnector.UpdateDomains([]string{"example.com"})
|
||||
b.appConnector.Wait(context.Background())
|
||||
|
||||
b.ObserveDNSResponse(dnsResponse("example.com.", "192.0.0.8"))
|
||||
b.appConnector.Wait(context.Background())
|
||||
wantRoutes := []netip.Prefix{netip.MustParsePrefix("192.0.0.8/32")}
|
||||
if !slices.Equal(rc.routes, wantRoutes) {
|
||||
t.Fatalf("got routes %v, want %v", rc.routes, wantRoutes)
|
||||
if !slices.Equal(rc.Routes(), wantRoutes) {
|
||||
t.Fatalf("got routes %v, want %v", rc.Routes(), wantRoutes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoveredRouteRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
existingRoute netip.Prefix
|
||||
newRoute netip.Prefix
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
existingRoute: netip.MustParsePrefix("192.0.0.1/32"),
|
||||
newRoute: netip.MustParsePrefix("192.0.0.1/32"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
existingRoute: netip.MustParsePrefix("192.0.0.1/32"),
|
||||
newRoute: netip.MustParsePrefix("192.0.0.2/32"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
existingRoute: netip.MustParsePrefix("192.0.0.0/24"),
|
||||
newRoute: netip.MustParsePrefix("192.0.0.1/32"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
existingRoute: netip.MustParsePrefix("192.0.0.0/16"),
|
||||
newRoute: netip.MustParsePrefix("192.0.0.0/24"),
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := coveredRouteRange([]netip.Prefix{tt.existingRoute}, tt.newRoute)
|
||||
if got != tt.want {
|
||||
t.Errorf("coveredRouteRange(%v, %v) = %v, want %v", tt.existingRoute, tt.newRoute, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1243,6 +1289,7 @@ func TestReconfigureAppConnector(t *testing.T) {
|
||||
}).View()
|
||||
|
||||
b.reconfigAppConnectorLocked(b.netMap, b.pm.prefs)
|
||||
b.appConnector.Wait(context.Background())
|
||||
|
||||
want := []string{"example.com"}
|
||||
if !slices.Equal(b.appConnector.Domains().AsSlice(), want) {
|
||||
@@ -1342,16 +1389,6 @@ func dnsResponse(domain, address string) []byte {
|
||||
return must.Get(b.Finish())
|
||||
}
|
||||
|
||||
// routeCollector is a test helper that collects the list of routes advertised
|
||||
type routeCollector struct {
|
||||
routes []netip.Prefix
|
||||
}
|
||||
|
||||
func (rc *routeCollector) AdvertiseRoute(pfx netip.Prefix) error {
|
||||
rc.routes = append(rc.routes, pfx)
|
||||
return nil
|
||||
}
|
||||
|
||||
type errorSyspolicyHandler struct {
|
||||
t *testing.T
|
||||
err error
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"go4.org/netipx"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/appc"
|
||||
"tailscale.com/appc/appctest"
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/store/mem"
|
||||
@@ -685,10 +686,11 @@ func TestPeerAPIReplyToDNSQueries(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPeerAPIReplyToDNSQueriesAreObserved(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var h peerAPIHandler
|
||||
h.remoteAddr = netip.MustParseAddrPort("100.150.151.152:12345")
|
||||
|
||||
rc := &routeCollector{}
|
||||
rc := &appctest.RouteCollector{}
|
||||
eng, _ := wgengine.NewFakeUserspaceEngine(logger.Discard, 0)
|
||||
pm := must.Get(newProfileManager(new(mem.Store), t.Logf))
|
||||
h.ps = &peerAPIServer{
|
||||
@@ -700,6 +702,7 @@ func TestPeerAPIReplyToDNSQueriesAreObserved(t *testing.T) {
|
||||
},
|
||||
}
|
||||
h.ps.b.appConnector.UpdateDomains([]string{"example.com"})
|
||||
h.ps.b.appConnector.Wait(ctx)
|
||||
|
||||
h.ps.resolver = &fakeResolver{}
|
||||
f := filter.NewAllowAllForTest(logger.Discard)
|
||||
@@ -717,10 +720,11 @@ func TestPeerAPIReplyToDNSQueriesAreObserved(t *testing.T) {
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("unexpected status code: %v", w.Code)
|
||||
}
|
||||
h.ps.b.appConnector.Wait(ctx)
|
||||
|
||||
wantRoutes := []netip.Prefix{netip.MustParsePrefix("192.0.0.8/32")}
|
||||
if !slices.Equal(rc.routes, wantRoutes) {
|
||||
t.Errorf("got %v; want %v", rc.routes, wantRoutes)
|
||||
if !slices.Equal(rc.Routes(), wantRoutes) {
|
||||
t.Errorf("got %v; want %v", rc.Routes(), wantRoutes)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -605,7 +605,20 @@ func (rp *reverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
p := &httputil.ReverseProxy{Rewrite: func(r *httputil.ProxyRequest) {
|
||||
oldOutPath := r.Out.URL.Path
|
||||
r.SetURL(rp.url)
|
||||
|
||||
// If mount point matches the request path exactly, the outbound
|
||||
// request URL was set to empty string in serveWebHandler which
|
||||
// would have resulted in the outbound path set to <proxy path>
|
||||
// + '/' in SetURL. In that case, if the proxy path was set, we
|
||||
// want to send the request to the <proxy path> (without the
|
||||
// '/') .
|
||||
if oldOutPath == "" && rp.url.Path != "" {
|
||||
r.Out.URL.Path = rp.url.Path
|
||||
r.Out.URL.RawPath = rp.url.RawPath
|
||||
}
|
||||
|
||||
r.Out.Host = r.In.Host
|
||||
addProxyForwardedHeaders(r)
|
||||
rp.lb.addTailscaleIdentityHeaders(r)
|
||||
|
||||
@@ -348,7 +348,109 @@ func TestServeConfigETag(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeHTTPProxy(t *testing.T) {
|
||||
func TestServeHTTPProxyPath(t *testing.T) {
|
||||
b := newTestBackend(t)
|
||||
// Start test serve endpoint.
|
||||
testServ := httptest.NewServer(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
// Set the request URL path to a response header, so the
|
||||
// requested URL path can be checked in tests.
|
||||
t.Logf("adding path %s", r.URL.Path)
|
||||
w.Header().Add("Path", r.URL.Path)
|
||||
},
|
||||
))
|
||||
defer testServ.Close()
|
||||
tests := []struct {
|
||||
name string
|
||||
mountPoint string
|
||||
proxyPath string
|
||||
requestPath string
|
||||
wantRequestPath string
|
||||
}{
|
||||
{
|
||||
name: "/foo -> /foo, with mount point and path /foo",
|
||||
mountPoint: "/foo",
|
||||
proxyPath: "/foo",
|
||||
requestPath: "/foo",
|
||||
wantRequestPath: "/foo",
|
||||
},
|
||||
{
|
||||
name: "/foo/ -> /foo/, with mount point and path /foo",
|
||||
mountPoint: "/foo",
|
||||
proxyPath: "/foo",
|
||||
requestPath: "/foo/",
|
||||
wantRequestPath: "/foo/",
|
||||
},
|
||||
{
|
||||
name: "/foo -> /foo/, with mount point and path /foo/",
|
||||
mountPoint: "/foo/",
|
||||
proxyPath: "/foo/",
|
||||
requestPath: "/foo",
|
||||
wantRequestPath: "/foo/",
|
||||
},
|
||||
{
|
||||
name: "/-> /, with mount point and path /",
|
||||
mountPoint: "/",
|
||||
proxyPath: "/",
|
||||
requestPath: "/",
|
||||
wantRequestPath: "/",
|
||||
},
|
||||
{
|
||||
name: "/foo -> /foo, with mount point and path /",
|
||||
mountPoint: "/",
|
||||
proxyPath: "/",
|
||||
requestPath: "/foo",
|
||||
wantRequestPath: "/foo",
|
||||
},
|
||||
{
|
||||
name: "/foo/bar -> /foo/bar, with mount point and path /foo",
|
||||
mountPoint: "/foo",
|
||||
proxyPath: "/foo",
|
||||
requestPath: "/foo/bar",
|
||||
wantRequestPath: "/foo/bar",
|
||||
},
|
||||
{
|
||||
name: "/foo/bar/baz -> /foo/bar/baz, with mount point and path /foo",
|
||||
mountPoint: "/foo",
|
||||
proxyPath: "/foo",
|
||||
requestPath: "/foo/bar/baz",
|
||||
wantRequestPath: "/foo/bar/baz",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
conf := &ipn.ServeConfig{
|
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||
"example.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
||||
tt.mountPoint: {Proxy: testServ.URL + tt.proxyPath},
|
||||
}},
|
||||
},
|
||||
}
|
||||
if err := b.SetServeConfig(conf, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := &http.Request{
|
||||
URL: &url.URL{Path: tt.requestPath},
|
||||
TLS: &tls.ConnectionState{ServerName: "example.ts.net"},
|
||||
}
|
||||
req = req.WithContext(serveHTTPContextKey.WithValue(req.Context(),
|
||||
&serveHTTPContext{
|
||||
DestPort: 443,
|
||||
SrcAddr: netip.MustParseAddrPort("1.2.3.4:1234"), // random src
|
||||
}))
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
b.serveWebHandler(w, req)
|
||||
|
||||
// Verify what path was requested
|
||||
p := w.Result().Header.Get("Path")
|
||||
if p != tt.wantRequestPath {
|
||||
t.Errorf("wanted request path %s got %s", tt.wantRequestPath, p)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestServeHTTPProxyHeaders(t *testing.T) {
|
||||
b := newTestBackend(t)
|
||||
|
||||
// Start test serve endpoint.
|
||||
|
||||
@@ -49,7 +49,7 @@ func init() {
|
||||
|
||||
// Adds the list of known types to api.Scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion, &Connector{}, &ConnectorList{}, &ProxyClass{}, &ProxyClassList{})
|
||||
scheme.AddKnownTypes(SchemeGroupVersion, &Connector{}, &ConnectorList{})
|
||||
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !plan9
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var ProxyClassKind = "ProxyClass"
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Cluster,shortName=pc
|
||||
|
||||
type ProxyClass struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ProxyClassSpec `json:"spec"`
|
||||
|
||||
// This would need status if we do any validation in operator.
|
||||
// +optional
|
||||
// Status ProxyClassStatus `json:"status"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
type ProxyClassList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
|
||||
Items []ProxyClass `json:"items"`
|
||||
}
|
||||
|
||||
type ProxyClassSpec struct {
|
||||
// +optional
|
||||
Service `json:"service,omitempty"`
|
||||
// +optional
|
||||
StatefulSet *StatefulSet `json:"statefulSet,omitempty"`
|
||||
}
|
||||
|
||||
// Configuration for the headless Service, not actually used in this prototype,
|
||||
// but is here to better illustrate the API structure
|
||||
type Service struct {
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
type StatefulSet struct {
|
||||
// +optional
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
// +optional
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
// +optional
|
||||
Pod *Pod `json:"pod,omitempty"`
|
||||
}
|
||||
|
||||
type Pod struct {
|
||||
// Or should we just sync statefulset.labels, statefulset.annotations?
|
||||
// +optional
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
// +optional
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
TailscaleContainer *Container `json:"tailscaleContainer,omitempty"`
|
||||
TailscaleInitContainer *Container `json:"tailscaleInitContainer,omitempty"`
|
||||
PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"`
|
||||
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
|
||||
NodeName string `json:"nodeName,omitempty"`
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
|
||||
Patches []Patch `json:"patches,omitempty"`
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"`
|
||||
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
|
||||
}
|
||||
|
||||
// RFC 6902 JSON patch
|
||||
type Patch struct {
|
||||
Path string `json:"path"`
|
||||
// +optional
|
||||
Value string `json:"value,omitempty"`
|
||||
Op string `json:"op"`
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
@@ -137,189 +136,6 @@ func (in *ConnectorStatus) DeepCopy() *ConnectorStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Container) DeepCopyInto(out *Container) {
|
||||
*out = *in
|
||||
if in.SecurityContext != nil {
|
||||
in, out := &in.SecurityContext, &out.SecurityContext
|
||||
*out = new(v1.SecurityContext)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.Resources.DeepCopyInto(&out.Resources)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Container.
|
||||
func (in *Container) DeepCopy() *Container {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Container)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Patch) DeepCopyInto(out *Patch) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Patch.
|
||||
func (in *Patch) DeepCopy() *Patch {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Patch)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Pod) DeepCopyInto(out *Pod) {
|
||||
*out = *in
|
||||
if in.Labels != nil {
|
||||
in, out := &in.Labels, &out.Labels
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Annotations != nil {
|
||||
in, out := &in.Annotations, &out.Annotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.TailscaleContainer != nil {
|
||||
in, out := &in.TailscaleContainer, &out.TailscaleContainer
|
||||
*out = new(Container)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.TailscaleInitContainer != nil {
|
||||
in, out := &in.TailscaleInitContainer, &out.TailscaleInitContainer
|
||||
*out = new(Container)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.PodSecurityContext != nil {
|
||||
in, out := &in.PodSecurityContext, &out.PodSecurityContext
|
||||
*out = new(v1.PodSecurityContext)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ImagePullSecrets != nil {
|
||||
in, out := &in.ImagePullSecrets, &out.ImagePullSecrets
|
||||
*out = make([]v1.LocalObjectReference, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.NodeSelector != nil {
|
||||
in, out := &in.NodeSelector, &out.NodeSelector
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]v1.Toleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Patches != nil {
|
||||
in, out := &in.Patches, &out.Patches
|
||||
*out = make([]Patch, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Pod.
|
||||
func (in *Pod) DeepCopy() *Pod {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Pod)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProxyClass) DeepCopyInto(out *ProxyClass) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyClass.
|
||||
func (in *ProxyClass) DeepCopy() *ProxyClass {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProxyClass)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ProxyClass) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProxyClassList) DeepCopyInto(out *ProxyClassList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]ProxyClass, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyClassList.
|
||||
func (in *ProxyClassList) DeepCopy() *ProxyClassList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProxyClassList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ProxyClassList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProxyClassSpec) DeepCopyInto(out *ProxyClassSpec) {
|
||||
*out = *in
|
||||
in.Service.DeepCopyInto(&out.Service)
|
||||
if in.StatefulSet != nil {
|
||||
in, out := &in.StatefulSet, &out.StatefulSet
|
||||
*out = new(StatefulSet)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyClassSpec.
|
||||
func (in *ProxyClassSpec) DeepCopy() *ProxyClassSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProxyClassSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in Routes) DeepCopyInto(out *Routes) {
|
||||
{
|
||||
@@ -339,62 +155,6 @@ func (in Routes) DeepCopy() Routes {
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Service) DeepCopyInto(out *Service) {
|
||||
*out = *in
|
||||
if in.Labels != nil {
|
||||
in, out := &in.Labels, &out.Labels
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Service.
|
||||
func (in *Service) DeepCopy() *Service {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Service)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StatefulSet) DeepCopyInto(out *StatefulSet) {
|
||||
*out = *in
|
||||
if in.Labels != nil {
|
||||
in, out := &in.Labels, &out.Labels
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Annotations != nil {
|
||||
in, out := &in.Annotations, &out.Annotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Pod != nil {
|
||||
in, out := &in.Pod, &out.Pod
|
||||
*out = new(Pod)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatefulSet.
|
||||
func (in *StatefulSet) DeepCopy() *StatefulSet {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StatefulSet)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SubnetRouter) DeepCopyInto(out *SubnetRouter) {
|
||||
*out = *in
|
||||
|
||||
303
net/portmapper/legacy_upnp.go
Normal file
303
net/portmapper/legacy_upnp.go
Normal file
@@ -0,0 +1,303 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !js
|
||||
|
||||
// (no raw sockets in JS/WASM)
|
||||
|
||||
package portmapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tailscale/goupnp"
|
||||
"github.com/tailscale/goupnp/soap"
|
||||
)
|
||||
|
||||
const (
|
||||
urn_LegacyWANPPPConnection_1 = "urn:dslforum-org:service:WANPPPConnection:1"
|
||||
urn_LegacyWANIPConnection_1 = "urn:dslforum-org:service:WANIPConnection:1"
|
||||
)
|
||||
|
||||
// legacyWANPPPConnection1 is the same as internetgateway2.WANPPPConnection1,
|
||||
// except using the old URN that starts with "urn:dslforum-org".
|
||||
//
|
||||
// The definition for this can be found in older documentation about UPnP; for
|
||||
// the purposes of this implementation, we're referring to "DSL Forum TR-064:
|
||||
// LAN-Side DSL CPE Configuration", which, while deprecated, can be found at:
|
||||
//
|
||||
// https://www.broadband-forum.org/wp-content/uploads/2018/11/TR-064_Corrigendum-1.pdf
|
||||
// https://www.broadband-forum.org/pdfs/tr-064-1-0-1.pdf
|
||||
type legacyWANPPPConnection1 struct {
|
||||
goupnp.ServiceClient
|
||||
}
|
||||
|
||||
// AddPortMapping implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) AddPortMapping(
|
||||
ctx context.Context,
|
||||
NewRemoteHost string,
|
||||
NewExternalPort uint16,
|
||||
NewProtocol string,
|
||||
NewInternalPort uint16,
|
||||
NewInternalClient string,
|
||||
NewEnabled bool,
|
||||
NewPortMappingDescription string,
|
||||
NewLeaseDuration uint32,
|
||||
) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
NewExternalPort string
|
||||
NewProtocol string
|
||||
NewInternalPort string
|
||||
NewInternalClient string
|
||||
NewEnabled string
|
||||
NewPortMappingDescription string
|
||||
NewLeaseDuration string
|
||||
}{}
|
||||
|
||||
if request.NewRemoteHost, err = soap.MarshalString(NewRemoteHost); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewExternalPort, err = soap.MarshalUi2(NewExternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewProtocol, err = soap.MarshalString(NewProtocol); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewInternalPort, err = soap.MarshalUi2(NewInternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewInternalClient, err = soap.MarshalString(NewInternalClient); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewEnabled, err = soap.MarshalBoolean(NewEnabled); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewPortMappingDescription, err = soap.MarshalString(NewPortMappingDescription); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewLeaseDuration, err = soap.MarshalUi4(NewLeaseDuration); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Response structure.
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "AddPortMapping", request, response)
|
||||
}
|
||||
|
||||
// DeletePortMapping implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) DeletePortMapping(ctx context.Context, NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
NewExternalPort string
|
||||
NewProtocol string
|
||||
}{}
|
||||
if request.NewRemoteHost, err = soap.MarshalString(NewRemoteHost); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewExternalPort, err = soap.MarshalUi2(NewExternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewProtocol, err = soap.MarshalString(NewProtocol); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Response structure.
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "DeletePortMapping", request, response)
|
||||
}
|
||||
|
||||
// GetExternalIPAddress implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) GetExternalIPAddress(ctx context.Context) (NewExternalIPAddress string, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
// Response structure.
|
||||
response := &struct {
|
||||
NewExternalIPAddress string
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "GetExternalIPAddress", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if NewExternalIPAddress, err = soap.UnmarshalString(response.NewExternalIPAddress); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetStatusInfo implements upnpClient
|
||||
func (client *legacyWANPPPConnection1) GetStatusInfo(ctx context.Context) (NewConnectionStatus string, NewLastConnectionError string, NewUptime uint32, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
// Response structure.
|
||||
response := &struct {
|
||||
NewConnectionStatus string
|
||||
NewLastConnectionError string
|
||||
NewUpTime string // NOTE: the "T" is capitalized here, per the spec, though it's lowercase in the newer UPnP spec
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANPPPConnection_1, "GetStatusInfo", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if NewConnectionStatus, err = soap.UnmarshalString(response.NewConnectionStatus); err != nil {
|
||||
return
|
||||
}
|
||||
if NewLastConnectionError, err = soap.UnmarshalString(response.NewLastConnectionError); err != nil {
|
||||
return
|
||||
}
|
||||
if NewUptime, err = soap.UnmarshalUi4(response.NewUpTime); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// legacyWANIPConnection1 is the same as internetgateway2.WANIPConnection1,
|
||||
// except using the old URN that starts with "urn:dslforum-org".
|
||||
//
|
||||
// See legacyWANPPPConnection1 for details on where this is defined.
|
||||
type legacyWANIPConnection1 struct {
|
||||
goupnp.ServiceClient
|
||||
}
|
||||
|
||||
// AddPortMapping implements upnpClient
|
||||
func (client *legacyWANIPConnection1) AddPortMapping(
|
||||
ctx context.Context,
|
||||
NewRemoteHost string,
|
||||
NewExternalPort uint16,
|
||||
NewProtocol string,
|
||||
NewInternalPort uint16,
|
||||
NewInternalClient string,
|
||||
NewEnabled bool,
|
||||
NewPortMappingDescription string,
|
||||
NewLeaseDuration uint32,
|
||||
) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
NewExternalPort string
|
||||
NewProtocol string
|
||||
NewInternalPort string
|
||||
NewInternalClient string
|
||||
NewEnabled string
|
||||
NewPortMappingDescription string
|
||||
NewLeaseDuration string
|
||||
}{}
|
||||
|
||||
if request.NewRemoteHost, err = soap.MarshalString(NewRemoteHost); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewExternalPort, err = soap.MarshalUi2(NewExternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewProtocol, err = soap.MarshalString(NewProtocol); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewInternalPort, err = soap.MarshalUi2(NewInternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewInternalClient, err = soap.MarshalString(NewInternalClient); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewEnabled, err = soap.MarshalBoolean(NewEnabled); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewPortMappingDescription, err = soap.MarshalString(NewPortMappingDescription); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewLeaseDuration, err = soap.MarshalUi4(NewLeaseDuration); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Response structure.
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "AddPortMapping", request, response)
|
||||
}
|
||||
|
||||
// DeletePortMapping implements upnpClient
|
||||
func (client *legacyWANIPConnection1) DeletePortMapping(ctx context.Context, NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (err error) {
|
||||
// Request structure.
|
||||
request := &struct {
|
||||
NewRemoteHost string
|
||||
NewExternalPort string
|
||||
NewProtocol string
|
||||
}{}
|
||||
if request.NewRemoteHost, err = soap.MarshalString(NewRemoteHost); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewExternalPort, err = soap.MarshalUi2(NewExternalPort); err != nil {
|
||||
return
|
||||
}
|
||||
if request.NewProtocol, err = soap.MarshalString(NewProtocol); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Response structure.
|
||||
response := any(nil)
|
||||
|
||||
// Perform the SOAP call.
|
||||
return client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "DeletePortMapping", request, response)
|
||||
}
|
||||
|
||||
// GetExternalIPAddress implements upnpClient
|
||||
func (client *legacyWANIPConnection1) GetExternalIPAddress(ctx context.Context) (NewExternalIPAddress string, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
// Response structure.
|
||||
response := &struct {
|
||||
NewExternalIPAddress string
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "GetExternalIPAddress", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if NewExternalIPAddress, err = soap.UnmarshalString(response.NewExternalIPAddress); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetStatusInfo implements upnpClient
|
||||
func (client *legacyWANIPConnection1) GetStatusInfo(ctx context.Context) (NewConnectionStatus string, NewLastConnectionError string, NewUptime uint32, err error) {
|
||||
// Request structure.
|
||||
request := any(nil)
|
||||
|
||||
// Response structure.
|
||||
response := &struct {
|
||||
NewConnectionStatus string
|
||||
NewLastConnectionError string
|
||||
NewUpTime string // NOTE: the "T" is capitalized here, per the spec, though it's lowercase in the newer UPnP spec
|
||||
}{}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction(ctx, urn_LegacyWANIPConnection_1, "GetStatusInfo", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if NewConnectionStatus, err = soap.UnmarshalString(response.NewConnectionStatus); err != nil {
|
||||
return
|
||||
}
|
||||
if NewLastConnectionError, err = soap.UnmarshalString(response.NewLastConnectionError); err != nil {
|
||||
return
|
||||
}
|
||||
if NewUptime, err = soap.UnmarshalUi4(response.NewUpTime); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1199,6 +1199,10 @@ var (
|
||||
// received a UPnP response from a port other than the UPnP port.
|
||||
metricUPnPResponseAlternatePort = clientmetric.NewCounter("portmap_upnp_response_alternate_port")
|
||||
|
||||
// metricUPnPSelectLegacy counts the number of times that a legacy
|
||||
// service was found in a UPnP response.
|
||||
metricUPnPSelectLegacy = clientmetric.NewCounter("portmap_upnp_select_legacy")
|
||||
|
||||
// metricUPnPSelectSingle counts the number of times that only a single
|
||||
// UPnP device was available in selectBestService.
|
||||
metricUPnPSelectSingle = clientmetric.NewCounter("portmap_upnp_select_single")
|
||||
|
||||
@@ -252,7 +252,8 @@ func getUPnPRootDevice(ctx context.Context, logf logger.Logf, debug DebugKnobs,
|
||||
}
|
||||
|
||||
// selectBestService picks the "best" service from the given UPnP root device
|
||||
// to use to create a port mapping.
|
||||
// to use to create a port mapping. It may return (nil, nil) if no supported
|
||||
// service was found in the provided *goupnp.RootDevice.
|
||||
//
|
||||
// loc is the parsed location that was used to fetch the given RootDevice.
|
||||
//
|
||||
@@ -288,6 +289,19 @@ func selectBestService(ctx context.Context, logf logger.Logf, root *goupnp.RootD
|
||||
clients = append(clients, v)
|
||||
}
|
||||
|
||||
// These are legacy services that were deprecated in 2015, but are
|
||||
// still in use by older devices; try them just in case.
|
||||
legacyClients, _ := goupnp.NewServiceClientsFromRootDevice(ctx, root, loc, urn_LegacyWANPPPConnection_1)
|
||||
metricUPnPSelectLegacy.Add(int64(len(legacyClients)))
|
||||
for _, client := range legacyClients {
|
||||
clients = append(clients, &legacyWANPPPConnection1{client})
|
||||
}
|
||||
legacyClients, _ = goupnp.NewServiceClientsFromRootDevice(ctx, root, loc, urn_LegacyWANIPConnection_1)
|
||||
metricUPnPSelectLegacy.Add(int64(len(legacyClients)))
|
||||
for _, client := range legacyClients {
|
||||
clients = append(clients, &legacyWANIPConnection1{client})
|
||||
}
|
||||
|
||||
// If we have no clients, then return right now; if we only have one,
|
||||
// just select and return it.
|
||||
if len(clients) == 0 {
|
||||
@@ -559,6 +573,20 @@ func (c *Client) tryUPnPPortmapWithDevice(
|
||||
return netip.AddrPort{}, nil, err
|
||||
}
|
||||
|
||||
// If we have no client, we cannot continue; this can happen if we get
|
||||
// a valid UPnP response that does not contain any of the service types
|
||||
// that we know how to use.
|
||||
if client == nil {
|
||||
// For debugging, print all available services that we aren't
|
||||
// using because they're not supported; use c.vlogf so we don't
|
||||
// spam the logs unless verbose debugging is turned on.
|
||||
rootDev.Device.VisitServices(func(s *goupnp.Service) {
|
||||
c.vlogf("unsupported UPnP service: Type=%q ID=%q ControlURL=%q", s.ServiceType, s.ServiceId, s.ControlURL.Str)
|
||||
})
|
||||
|
||||
return netip.AddrPort{}, nil, fmt.Errorf("no supported UPnP clients")
|
||||
}
|
||||
|
||||
// Start by trying to make a temporary lease with a duration.
|
||||
var newPort uint16
|
||||
newPort, err = addAnyPortMapping(
|
||||
|
||||
@@ -165,6 +165,252 @@ const (
|
||||
</device>
|
||||
<disabledForTestURLBase>http://10.0.0.1:2828</disabledForTestURLBase>
|
||||
</root>
|
||||
`
|
||||
|
||||
// Huawei, https://github.com/tailscale/tailscale/issues/10911
|
||||
huaweiRootDescXML = `<?xml version="1.0"?>
|
||||
<root xmlns="urn:schemas-upnp-org:device-1-0">
|
||||
<specVersion>
|
||||
<major>1</major>
|
||||
<minor>0</minor>
|
||||
</specVersion>
|
||||
<device>
|
||||
<deviceType>urn:dslforum-org:device:InternetGatewayDevice:1</deviceType>
|
||||
<friendlyName>HG531 V1</friendlyName>
|
||||
<manufacturer>Huawei Technologies Co., Ltd.</manufacturer>
|
||||
<manufacturerURL>http://www.huawei.com</manufacturerURL>
|
||||
<modelDescription>Huawei Home Gateway</modelDescription>
|
||||
<modelName>HG531 V1</modelName>
|
||||
<modelNumber>Huawei Model</modelNumber>
|
||||
<modelURL>http://www.huawei.com</modelURL>
|
||||
<serialNumber>G6J8W15326003974</serialNumber>
|
||||
<UDN>uuid:00e0fc37-2626-2828-2600-587f668bdd9a</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:www-huawei-com:service:DeviceConfig:1</serviceType>
|
||||
<serviceId>urn:www-huawei-com:serviceId:DeviceConfig1</serviceId>
|
||||
<SCPDURL>/desc/DevCfg.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/DeviceConfig_1</controlURL>
|
||||
<eventSubURL>/evt/DeviceConfig_1</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:LANConfigSecurity:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:LANConfigSecurity1</serviceId>
|
||||
<SCPDURL>/desc/LANSec.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/LANConfigSecurity_1</controlURL>
|
||||
<eventSubURL>/evt/LANConfigSecurity_1</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:Layer3Forwarding:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:Layer3Forwarding1</serviceId>
|
||||
<SCPDURL>/desc/L3Fwd.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/Layer3Forwarding_1</controlURL>
|
||||
<eventSubURL>/evt/Layer3Forwarding_1</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
<deviceList>
|
||||
<device>
|
||||
<deviceType>urn:dslforum-org:device:WANDevice:1</deviceType>
|
||||
<friendlyName>WANDevice</friendlyName>
|
||||
<manufacturer>Huawei Technologies Co., Ltd.</manufacturer>
|
||||
<manufacturerURL>http://www.huawei.com</manufacturerURL>
|
||||
<modelDescription>Huawei Home Gateway</modelDescription>
|
||||
<modelName>HG531 V1</modelName>
|
||||
<modelNumber>Huawei Model</modelNumber>
|
||||
<modelURL>http://www.huawei.com</modelURL>
|
||||
<serialNumber>G6J8W15326003974</serialNumber>
|
||||
<UDN>uuid:00e0fc37-2626-2828-2601-587f668bdd9a</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WANDSLInterfaceConfig:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WANDSLInterfaceConfig1</serviceId>
|
||||
<SCPDURL>/desc/WanDslIfCfg.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WANDSLInterfaceConfig_1</controlURL>
|
||||
<eventSubURL>/evt/WANDSLInterfaceConfig_1</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WANCommonInterfaceConfig:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WANCommonInterfaceConfig1</serviceId>
|
||||
<SCPDURL>/desc/WanCommonIfc1.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WANCommonInterfaceConfig_1</controlURL>
|
||||
<eventSubURL>/evt/WANCommonInterfaceConfig_1</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
<deviceList>
|
||||
<device>
|
||||
<deviceType>urn:dslforum-org:device:WANConnectionDevice:1</deviceType>
|
||||
<friendlyName>WANConnectionDevice</friendlyName>
|
||||
<manufacturer>Huawei Technologies Co., Ltd.</manufacturer>
|
||||
<manufacturerURL>http://www.huawei.com</manufacturerURL>
|
||||
<modelDescription>Huawei Home Gateway</modelDescription>
|
||||
<modelName>HG531 V1</modelName>
|
||||
<modelNumber>Huawei Model</modelNumber>
|
||||
<modelURL>http://www.huawei.com</modelURL>
|
||||
<serialNumber>G6J8W15326003974</serialNumber>
|
||||
<UDN>uuid:00e0fc37-2626-2828-2603-587f668bdd9a</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WANPPPConnection:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WANPPPConnection1</serviceId>
|
||||
<SCPDURL>/desc/WanPppConn.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WANPPPConnection_1</controlURL>
|
||||
<eventSubURL>/evt/WANPPPConnection_1</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WANEthernetConnectionManagement:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WANEthernetConnectionManagement1</serviceId>
|
||||
<SCPDURL>/desc/WanEthConnMgt.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WANEthernetConnectionManagement_1</controlURL>
|
||||
<eventSubURL>/evt/WANEthernetConnectionManagement_1</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WANDSLLinkConfig:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WANDSLLinkConfig1</serviceId>
|
||||
<SCPDURL>/desc/WanDslLink.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WANDSLLinkConfig_1</controlURL>
|
||||
<eventSubURL>/evt/WANDSLLinkConfig_1</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
</device>
|
||||
</deviceList>
|
||||
</device>
|
||||
<device>
|
||||
<deviceType>urn:dslforum-org:device:LANDevice:1</deviceType>
|
||||
<friendlyName>LANDevice</friendlyName>
|
||||
<manufacturer>Huawei Technologies Co., Ltd.</manufacturer>
|
||||
<manufacturerURL>http://www.huawei.com</manufacturerURL>
|
||||
<modelDescription>Huawei Home Gateway</modelDescription>
|
||||
<modelName>HG531 V1</modelName>
|
||||
<modelNumber>Huawei Model</modelNumber>
|
||||
<modelURL>http://www.huawei.com</modelURL>
|
||||
<serialNumber>G6J8W15326003974</serialNumber>
|
||||
<UDN>uuid:00e0fc37-2626-2828-2602-587f668bdd9a</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WLANConfiguration:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WLANConfiguration4</serviceId>
|
||||
<SCPDURL>/desc/WLANCfg.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WLANConfiguration_4</controlURL>
|
||||
<eventSubURL>/evt/WLANConfiguration_4</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WLANConfiguration:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WLANConfiguration3</serviceId>
|
||||
<SCPDURL>/desc/WLANCfg.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WLANConfiguration_3</controlURL>
|
||||
<eventSubURL>/evt/WLANConfiguration_3</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WLANConfiguration:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WLANConfiguration2</serviceId>
|
||||
<SCPDURL>/desc/WLANCfg.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WLANConfiguration_2</controlURL>
|
||||
<eventSubURL>/evt/WLANConfiguration_2</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:WLANConfiguration:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:WLANConfiguration1</serviceId>
|
||||
<SCPDURL>/desc/WLANCfg.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/WLANConfiguration_1</controlURL>
|
||||
<eventSubURL>/evt/WLANConfiguration_1</eventSubURL>
|
||||
</service>
|
||||
<service>
|
||||
<serviceType>urn:dslforum-org:service:LANHostConfigManagement:1</serviceType>
|
||||
<serviceId>urn:dslforum-org:serviceId:LANHostConfigManagement1</serviceId>
|
||||
<SCPDURL>/desc/LanHostCfgMgmt.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/LANHostConfigManagement_1</controlURL>
|
||||
<eventSubURL>/evt/LANHostConfigManagement_1</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
</device>
|
||||
</deviceList>
|
||||
<presentationURL>http://127.0.0.1</presentationURL>
|
||||
</device>
|
||||
</root>
|
||||
`
|
||||
|
||||
noSupportedServicesRootDesc = `<?xml version="1.0"?>
|
||||
<root xmlns="urn:schemas-upnp-org:device-1-0">
|
||||
<specVersion>
|
||||
<major>1</major>
|
||||
<minor>0</minor>
|
||||
</specVersion>
|
||||
<device>
|
||||
<deviceType>urn:dslforum-org:device:InternetGatewayDevice:1</deviceType>
|
||||
<friendlyName>Fake Router</friendlyName>
|
||||
<manufacturer>Tailscale, Inc</manufacturer>
|
||||
<manufacturerURL>http://www.tailscale.com</manufacturerURL>
|
||||
<modelDescription>Fake Router</modelDescription>
|
||||
<modelName>Test Model</modelName>
|
||||
<modelNumber>v1</modelNumber>
|
||||
<modelURL>http://www.tailscale.com</modelURL>
|
||||
<serialNumber>123456789</serialNumber>
|
||||
<UDN>uuid:11111111-2222-3333-4444-555555555555</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:schemas-microsoft-com:service:OSInfo:1</serviceType>
|
||||
<serviceId>urn:microsoft-com:serviceId:OSInfo1</serviceId>
|
||||
<SCPDURL>/osinfo.xml</SCPDURL>
|
||||
<controlURL>/upnp/control/aaaaaaaaaa/osinfo</controlURL>
|
||||
<eventSubURL>/upnp/event/aaaaaaaaaa/osinfo</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
<deviceList>
|
||||
<device>
|
||||
<deviceType>urn:schemas-upnp-org:device:WANDevice:1</deviceType>
|
||||
<friendlyName>WANDevice</friendlyName>
|
||||
<manufacturer>Tailscale, Inc</manufacturer>
|
||||
<manufacturerURL>http://www.tailscale.com</manufacturerURL>
|
||||
<modelDescription>Tailscale Test Router</modelDescription>
|
||||
<modelName>Test Model</modelName>
|
||||
<modelNumber>v1</modelNumber>
|
||||
<modelURL>http://www.tailscale.com</modelURL>
|
||||
<serialNumber>123456789</serialNumber>
|
||||
<UDN>uuid:11111111-2222-3333-4444-555555555555</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType>
|
||||
<serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId>
|
||||
<controlURL>/ctl/bbbbbbbb</controlURL>
|
||||
<eventSubURL>/evt/bbbbbbbb</eventSubURL>
|
||||
<SCPDURL>/WANCfg.xml</SCPDURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
<deviceList>
|
||||
<device>
|
||||
<deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:1</deviceType>
|
||||
<friendlyName>WANConnectionDevice</friendlyName>
|
||||
<manufacturer>Tailscale, Inc</manufacturer>
|
||||
<manufacturerURL>http://www.tailscale.com</manufacturerURL>
|
||||
<modelDescription>Tailscale Test Router</modelDescription>
|
||||
<modelName>Test Model</modelName>
|
||||
<modelNumber>v1</modelNumber>
|
||||
<modelURL>http://www.tailscale.com</modelURL>
|
||||
<serialNumber>123456789</serialNumber>
|
||||
<UDN>uuid:11111111-2222-3333-4444-555555555555</UDN>
|
||||
<UPC>000000000001</UPC>
|
||||
<serviceList>
|
||||
<service>
|
||||
<serviceType>urn:tailscale:service:SomethingElse:1</serviceType>
|
||||
<serviceId>urn:upnp-org:serviceId:TailscaleSomethingElse</serviceId>
|
||||
<SCPDURL>/desc/SomethingElse.xml</SCPDURL>
|
||||
<controlURL>/ctrlt/SomethingElse_1</controlURL>
|
||||
<eventSubURL>/evt/SomethingElse_1</eventSubURL>
|
||||
</service>
|
||||
</serviceList>
|
||||
</device>
|
||||
</deviceList>
|
||||
</device>
|
||||
</deviceList>
|
||||
<presentationURL>http://127.0.0.1</presentationURL>
|
||||
</device>
|
||||
</root>
|
||||
`
|
||||
)
|
||||
|
||||
@@ -233,6 +479,19 @@ func TestGetUPnPClient(t *testing.T) {
|
||||
"*internetgateway2.WANIPConnection1",
|
||||
"saw UPnP type WANIPConnection1 at http://127.0.0.1:NNN/rootDesc.xml; MikroTik Router (MikroTik), method=none\n",
|
||||
},
|
||||
{
|
||||
"huawei",
|
||||
huaweiRootDescXML,
|
||||
"*portmapper.legacyWANPPPConnection1",
|
||||
"saw UPnP type *portmapper.legacyWANPPPConnection1 at http://127.0.0.1:NNN/rootDesc.xml; HG531 V1 (Huawei Technologies Co., Ltd.), method=single\n",
|
||||
},
|
||||
{
|
||||
"not_supported",
|
||||
noSupportedServicesRootDesc,
|
||||
"<nil>",
|
||||
"",
|
||||
},
|
||||
|
||||
// TODO(bradfitz): find a PPP one in the wild
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -375,6 +634,99 @@ func TestGetUPnPPortMapping(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetUPnPPortMapping_NoValidServices tests that getUPnPPortMapping doesn't
|
||||
// crash when a valid UPnP response with no supported services is discovered
|
||||
// and parsed.
|
||||
//
|
||||
// See https://github.com/tailscale/tailscale/issues/10911
|
||||
func TestGetUPnPPortMapping_NoValidServices(t *testing.T) {
|
||||
igd, err := NewTestIGD(t.Logf, TestIGDOptions{UPnP: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer igd.Close()
|
||||
|
||||
igd.SetUPnPHandler(&upnpServer{
|
||||
t: t,
|
||||
Desc: noSupportedServicesRootDesc,
|
||||
})
|
||||
|
||||
c := newTestClient(t, igd)
|
||||
defer c.Close()
|
||||
c.debug.VerboseLogs = true
|
||||
|
||||
ctx := context.Background()
|
||||
res, err := c.Probe(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Probe: %v", err)
|
||||
}
|
||||
if !res.UPnP {
|
||||
t.Errorf("didn't detect UPnP")
|
||||
}
|
||||
|
||||
gw, myIP, ok := c.gatewayAndSelfIP()
|
||||
if !ok {
|
||||
t.Fatalf("could not get gateway and self IP")
|
||||
}
|
||||
|
||||
// This shouldn't panic
|
||||
_, ok = c.getUPnPPortMapping(ctx, gw, netip.AddrPortFrom(myIP, 12345), 0)
|
||||
if ok {
|
||||
t.Fatal("did not expect to get UPnP port mapping")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests the legacy behaviour with the pre-UPnP standard portmapping service.
|
||||
func TestGetUPnPPortMapping_Legacy(t *testing.T) {
|
||||
igd, err := NewTestIGD(t.Logf, TestIGDOptions{UPnP: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer igd.Close()
|
||||
|
||||
// This is a very basic fake UPnP server handler.
|
||||
handlers := map[string]any{
|
||||
"AddPortMapping": testLegacyAddPortMappingResponse,
|
||||
"GetExternalIPAddress": testLegacyGetExternalIPAddressResponse,
|
||||
"GetStatusInfo": testLegacyGetStatusInfoResponse,
|
||||
"DeletePortMapping": "", // Do nothing for test
|
||||
}
|
||||
|
||||
igd.SetUPnPHandler(&upnpServer{
|
||||
t: t,
|
||||
Desc: huaweiRootDescXML,
|
||||
Control: map[string]map[string]any{
|
||||
"/ctrlt/WANPPPConnection_1": handlers,
|
||||
},
|
||||
})
|
||||
|
||||
c := newTestClient(t, igd)
|
||||
defer c.Close()
|
||||
c.debug.VerboseLogs = true
|
||||
|
||||
ctx := context.Background()
|
||||
res, err := c.Probe(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Probe: %v", err)
|
||||
}
|
||||
if !res.UPnP {
|
||||
t.Errorf("didn't detect UPnP")
|
||||
}
|
||||
|
||||
gw, myIP, ok := c.gatewayAndSelfIP()
|
||||
if !ok {
|
||||
t.Fatalf("could not get gateway and self IP")
|
||||
}
|
||||
|
||||
ext, ok := c.getUPnPPortMapping(ctx, gw, netip.AddrPortFrom(myIP, 12345), 0)
|
||||
if !ok {
|
||||
t.Fatal("could not get UPnP port mapping")
|
||||
}
|
||||
if got, want := ext.Addr(), netip.MustParseAddr("123.123.123.123"); got != want {
|
||||
t.Errorf("bad external address; got %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUPnPPortMappingNoResponses(t *testing.T) {
|
||||
igd, err := NewTestIGD(t.Logf, TestIGDOptions{UPnP: true})
|
||||
if err != nil {
|
||||
@@ -676,3 +1028,33 @@ const testGetStatusInfoResponse = `<?xml version="1.0"?>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`
|
||||
|
||||
const testLegacyAddPortMappingResponse = `<?xml version="1.0"?>
|
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<s:Body>
|
||||
<u:AddPortMappingResponse xmlns:u="urn:dslforum-org:service:WANPPPConnection:1"/>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`
|
||||
|
||||
const testLegacyGetExternalIPAddressResponse = `<?xml version="1.0"?>
|
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<s:Body>
|
||||
<u:GetExternalIPAddressResponse xmlns:u="urn:dslforum-org:service:WANPPPConnection:1">
|
||||
<NewExternalIPAddress>123.123.123.123</NewExternalIPAddress>
|
||||
</u:GetExternalIPAddressResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`
|
||||
|
||||
const testLegacyGetStatusInfoResponse = `<?xml version="1.0"?>
|
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<s:Body>
|
||||
<u:GetStatusInfoResponse xmlns:u="urn:dslforum-org:service:WANPPPConnection:1">
|
||||
<NewConnectionStatus>Connected</NewConnectionStatus>
|
||||
<NewLastConnectionError>ERROR_NONE</NewLastConnectionError>
|
||||
<NewUpTime>9999</NewUpTime>
|
||||
</u:GetStatusInfoResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`
|
||||
|
||||
@@ -16,4 +16,4 @@
|
||||
) {
|
||||
src = ./.;
|
||||
}).shellNix
|
||||
# nix-direnv cache busting line: sha256-8PtzUS8VL1p7KnqSx6Y55tOl41KYOhJfe52V4qMB3Yw=
|
||||
# nix-direnv cache busting line: sha256-b/iffKOn7nMiWvM0AIGGzZaJ15NTaBlJff+aja3NQio=
|
||||
|
||||
@@ -126,7 +126,8 @@ type CapabilityVersion int
|
||||
// - 83: 2023-12-18: Client understands DefaultAutoUpdate
|
||||
// - 84: 2024-01-04: Client understands SeamlessKeyRenewal
|
||||
// - 85: 2024-01-05: Client understands MaxKeyDuration
|
||||
const CurrentCapabilityVersion CapabilityVersion = 85
|
||||
// - 86: 2024-01-23: Client understands NodeAttrProbeUDPLifetime
|
||||
const CurrentCapabilityVersion CapabilityVersion = 86
|
||||
|
||||
type StableID string
|
||||
|
||||
@@ -2203,6 +2204,10 @@ const (
|
||||
// NodeAttrSeamlessKeyRenewal makes clients enable beta functionality
|
||||
// of renewing node keys without breaking connections.
|
||||
NodeAttrSeamlessKeyRenewal NodeCapability = "seamless-key-renewal"
|
||||
|
||||
// NodeAttrProbeUDPLifetime makes the client probe UDP path lifetime at the
|
||||
// tail end of an active direct connection in magicsock.
|
||||
NodeAttrProbeUDPLifetime NodeCapability = "probe-udp-lifetime"
|
||||
)
|
||||
|
||||
// SetDNSRequest is a request to add a DNS record.
|
||||
|
||||
@@ -191,6 +191,7 @@ func autoflagsForTest(argv []string, env *Environment, goroot, nativeGOOS, nativ
|
||||
env.Set("CC", cc)
|
||||
env.Set("TS_LINK_FAIL_REFLECT", boolStr(failReflect))
|
||||
env.Set("GOROOT", goroot)
|
||||
env.Set("GOTOOLCHAIN", "local")
|
||||
|
||||
if subcommand == "env" {
|
||||
return argv, env, nil
|
||||
|
||||
@@ -49,6 +49,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=linux (was <nil>)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "build",
|
||||
@@ -74,6 +75,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=linux (was <nil>)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "install",
|
||||
@@ -102,6 +104,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=linux (was <nil>)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "build",
|
||||
@@ -130,6 +133,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=freebsd (was freebsd)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "build",
|
||||
@@ -155,6 +159,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=linux (was <nil>)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "test",
|
||||
@@ -184,6 +189,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=windows (was windows)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "build",
|
||||
@@ -209,6 +215,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=darwin (was <nil>)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "build",
|
||||
@@ -237,6 +244,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=darwin (was <nil>)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "build",
|
||||
@@ -265,6 +273,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=ios (was ios)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=1 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "build",
|
||||
@@ -297,6 +306,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=darwin (was darwin)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "build",
|
||||
@@ -329,6 +339,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=ios (was ios)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=1 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "build",
|
||||
@@ -354,6 +365,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=linux (was <nil>)
|
||||
GOROOT=/special/toolchain/path (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"go", "build",
|
||||
@@ -379,6 +391,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=linux (was <nil>)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "list",
|
||||
@@ -407,6 +420,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=linux (was <nil>)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "build",
|
||||
@@ -433,6 +447,7 @@ GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=linux (was <nil>)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was <nil>)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"go", "run",
|
||||
@@ -443,6 +458,35 @@ TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
"--tags=foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "linux_amd64_to_linux_amd64_custom_toolchain",
|
||||
env: map[string]string{
|
||||
"GOTOOLCHAIN": "go1.30rc5",
|
||||
},
|
||||
argv: []string{"gocross", "build", "./cmd/tailcontrol"},
|
||||
goroot: "/goroot",
|
||||
nativeGOOS: "linux",
|
||||
nativeGOARCH: "amd64",
|
||||
|
||||
envDiff: `CC=cc (was <nil>)
|
||||
CGO_CFLAGS=-O3 -std=gnu11 (was <nil>)
|
||||
CGO_ENABLED=1 (was <nil>)
|
||||
CGO_LDFLAGS= (was <nil>)
|
||||
GOARCH=amd64 (was <nil>)
|
||||
GOARM=5 (was <nil>)
|
||||
GOMIPS=softfloat (was <nil>)
|
||||
GOOS=linux (was <nil>)
|
||||
GOROOT=/goroot (was <nil>)
|
||||
GOTOOLCHAIN=local (was go1.30rc5)
|
||||
TS_LINK_FAIL_REFLECT=0 (was <nil>)`,
|
||||
wantArgv: []string{
|
||||
"gocross", "build",
|
||||
"-trimpath",
|
||||
"-tags=tailscale_go,osusergo,netgo",
|
||||
"-ldflags", "-X tailscale.com/version.longStamp=1.2.3-long -X tailscale.com/version.shortStamp=1.2.3 -X tailscale.com/version.gitCommitStamp=abcd -X tailscale.com/version.extraGitCommitStamp=defg '-extldflags=-static'",
|
||||
"./cmd/tailcontrol",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -447,6 +448,74 @@ func VarzHandler(w http.ResponseWriter, r *http.Request) {
|
||||
varz.Handler(w, r)
|
||||
}
|
||||
|
||||
// CleanRedirectURL ensures that urlStr is a valid redirect URL to the
|
||||
// current server, or one of allowedHosts. Returns the cleaned URL or
|
||||
// a validation error.
|
||||
func CleanRedirectURL(urlStr string, allowedHosts []string) (*url.URL, error) {
|
||||
// In some places, we unfortunately query-escape the redirect URL
|
||||
// too many times, and end up needing to redirect to a URL that's
|
||||
// still escaped by one level. Try to unescape the input.
|
||||
unescaped, err := url.QueryUnescape(urlStr)
|
||||
if err == nil && unescaped != urlStr {
|
||||
urlStr = unescaped
|
||||
}
|
||||
|
||||
// Go's URL parser and browser URL parsers disagree on the meaning
|
||||
// of malformed HTTP URLs. Given the input https:/evil.com, Go
|
||||
// parses it as hostname="", path="/evil.com". Browsers parse it
|
||||
// as hostname="evil.com", path="". This means that, using
|
||||
// malformed URLs, an attacker could trick us into approving of a
|
||||
// "local" redirect that in fact sends people elsewhere.
|
||||
//
|
||||
// This very blunt check enforces that we'll only process
|
||||
// redirects that are definitely well-formed URLs.
|
||||
//
|
||||
// Note that the check for just / also allows URLs of the form
|
||||
// "//foo.com/bar", which are scheme-relative redirects. These
|
||||
// must be handled with care below when determining whether a
|
||||
// redirect is relative to the current host. Notably,
|
||||
// url.URL.IsAbs reports // URLs as relative, whereas we want to
|
||||
// treat them as absolute redirects and verify the target host.
|
||||
if !hasSafeRedirectPrefix(urlStr) {
|
||||
return nil, fmt.Errorf("invalid redirect URL %q", urlStr)
|
||||
}
|
||||
|
||||
url, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid redirect URL %q: %w", urlStr, err)
|
||||
}
|
||||
// Redirects to self are always allowed. A self redirect must
|
||||
// start with url.Path, all prior URL sections must be empty.
|
||||
isSelfRedirect := url.Scheme == "" && url.Opaque == "" && url.User == nil && url.Host == ""
|
||||
if isSelfRedirect {
|
||||
return url, nil
|
||||
}
|
||||
for _, allowed := range allowedHosts {
|
||||
if strings.EqualFold(allowed, url.Hostname()) {
|
||||
return url, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("disallowed target host %q in redirect URL %q", url.Hostname(), urlStr)
|
||||
}
|
||||
|
||||
// hasSafeRedirectPrefix reports whether url starts with a slash, or
|
||||
// one of the case-insensitive strings "http://" or "https://".
|
||||
func hasSafeRedirectPrefix(url string) bool {
|
||||
if len(url) >= 1 && url[0] == '/' {
|
||||
return true
|
||||
}
|
||||
const http = "http://"
|
||||
if len(url) >= len(http) && strings.EqualFold(url[:len(http)], http) {
|
||||
return true
|
||||
}
|
||||
const https = "https://"
|
||||
if len(url) >= len(https) && strings.EqualFold(url[:len(https)], https) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AddBrowserHeaders sets various HTTP security headers for browser-facing endpoints.
|
||||
//
|
||||
// The specific headers:
|
||||
|
||||
@@ -617,3 +617,54 @@ func TestPort80Handler(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanRedirectURL(t *testing.T) {
|
||||
tailscaleHost := []string{"tailscale.com"}
|
||||
tailscaleAndOtherHost := []string{"microsoft.com", "tailscale.com"}
|
||||
localHost := []string{"127.0.0.1", "localhost"}
|
||||
myServer := []string{"myserver"}
|
||||
cases := []struct {
|
||||
url string
|
||||
hosts []string
|
||||
want string
|
||||
}{
|
||||
{"http://tailscale.com/foo", tailscaleHost, "http://tailscale.com/foo"},
|
||||
{"http://tailscale.com/foo", tailscaleAndOtherHost, "http://tailscale.com/foo"},
|
||||
{"http://microsoft.com/foo", tailscaleAndOtherHost, "http://microsoft.com/foo"},
|
||||
{"https://tailscale.com/foo", tailscaleHost, "https://tailscale.com/foo"},
|
||||
{"/foo", tailscaleHost, "/foo"},
|
||||
{"//tailscale.com/foo", tailscaleHost, "//tailscale.com/foo"},
|
||||
|
||||
{"/a/foobar", tailscaleHost, "/a/foobar"},
|
||||
{"http://127.0.0.1/a/foobar", localHost, "http://127.0.0.1/a/foobar"},
|
||||
{"http://127.0.0.1:123/a/foobar", localHost, "http://127.0.0.1:123/a/foobar"},
|
||||
{"http://127.0.0.1:31544/a/foobar", localHost, "http://127.0.0.1:31544/a/foobar"},
|
||||
{"http://localhost/a/foobar", localHost, "http://localhost/a/foobar"},
|
||||
{"http://localhost:123/a/foobar", localHost, "http://localhost:123/a/foobar"},
|
||||
{"http://localhost:31544/a/foobar", localHost, "http://localhost:31544/a/foobar"},
|
||||
{"http://myserver/a/foobar", myServer, "http://myserver/a/foobar"},
|
||||
{"http://myserver:123/a/foobar", myServer, "http://myserver:123/a/foobar"},
|
||||
{"http://myserver:31544/a/foobar", myServer, "http://myserver:31544/a/foobar"},
|
||||
{"http://evil.com/foo", tailscaleHost, ""},
|
||||
{"//evil.com", tailscaleHost, ""},
|
||||
{"HttP://tailscale.com", tailscaleHost, "http://tailscale.com"},
|
||||
{"http://TaIlScAlE.CoM/spongebob", tailscaleHost, "http://TaIlScAlE.CoM/spongebob"},
|
||||
{"ftp://tailscale.com", tailscaleHost, ""},
|
||||
{"https:/evil.com", tailscaleHost, ""}, // regression test for tailscale/corp#892
|
||||
{"%2Fa%2F44869c061701", tailscaleHost, "/a/44869c061701"}, // regression test for tailscale/corp#13288
|
||||
{"https%3A%2Ftailscale.com", tailscaleHost, ""}, // escaped colon-single-slash malformed URL
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
gotURL, err := CleanRedirectURL(tc.url, tc.hosts)
|
||||
if err != nil {
|
||||
if tc.want != "" {
|
||||
t.Errorf("CleanRedirectURL(%q, %v) got error: %v", tc.url, tc.hosts, err)
|
||||
}
|
||||
} else {
|
||||
if got := gotURL.String(); got != tc.want {
|
||||
t.Errorf("CleanRedirectURL(%q, %v) = %q, want %q", tc.url, tc.hosts, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ type AppConnectorAttr struct {
|
||||
// Domains enumerates the domains serviced by the specified app connectors.
|
||||
// Domains can be of the form: example.com, or *.example.com.
|
||||
Domains []string `json:"domains,omitempty"`
|
||||
// Routes enumerates the predetermined routes to be advertised by the specified app connectors.
|
||||
Routes []netip.Prefix `json:"routes,omitempty"`
|
||||
// Connectors enumerates the app connectors which service these domains.
|
||||
// These can either be "*" to match any advertising connector, or a
|
||||
// tag of the form tag:<tag-name>.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package key
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
|
||||
@@ -127,6 +128,14 @@ func (k DiscoPublic) String() string {
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// Compare returns an integer comparing DiscoPublic k and l lexicographically.
|
||||
// The result will be 0 if k == l, -1 if k < l, and +1 if k > l. This is useful
|
||||
// for situations requiring only one node in a pair to perform some operation,
|
||||
// e.g. probing UDP path lifetime.
|
||||
func (k DiscoPublic) Compare(l DiscoPublic) int {
|
||||
return bytes.Compare(k.k[:], l.k[:])
|
||||
}
|
||||
|
||||
// AppendText implements encoding.TextAppender.
|
||||
func (k DiscoPublic) AppendText(b []byte) ([]byte, error) {
|
||||
return appendHexKey(b, discoPublicHexPrefix, k.k[:]), nil
|
||||
|
||||
104
util/execqueue/execqueue.go
Normal file
104
util/execqueue/execqueue.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package execqueue implements an ordered asynchronous queue for executing functions.
|
||||
package execqueue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ExecQueue struct {
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
inFlight bool // whether a goroutine is running q.run
|
||||
doneWaiter chan struct{} // non-nil if waiter is waiting, then closed
|
||||
queue []func()
|
||||
}
|
||||
|
||||
func (q *ExecQueue) Add(f func()) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
if q.closed {
|
||||
return
|
||||
}
|
||||
if q.inFlight {
|
||||
q.queue = append(q.queue, f)
|
||||
} else {
|
||||
q.inFlight = true
|
||||
go q.run(f)
|
||||
}
|
||||
}
|
||||
|
||||
// RunSync waits for the queue to be drained and then synchronously runs f.
|
||||
// It returns an error if the queue is closed before f is run or ctx expires.
|
||||
func (q *ExecQueue) RunSync(ctx context.Context, f func()) error {
|
||||
for {
|
||||
if err := q.Wait(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
q.mu.Lock()
|
||||
if q.inFlight {
|
||||
q.mu.Unlock()
|
||||
continue
|
||||
}
|
||||
defer q.mu.Unlock()
|
||||
if q.closed {
|
||||
return errors.New("closed")
|
||||
}
|
||||
f()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (q *ExecQueue) run(f func()) {
|
||||
f()
|
||||
|
||||
q.mu.Lock()
|
||||
for len(q.queue) > 0 && !q.closed {
|
||||
f := q.queue[0]
|
||||
q.queue[0] = nil
|
||||
q.queue = q.queue[1:]
|
||||
q.mu.Unlock()
|
||||
f()
|
||||
q.mu.Lock()
|
||||
}
|
||||
q.inFlight = false
|
||||
q.queue = nil
|
||||
if q.doneWaiter != nil {
|
||||
close(q.doneWaiter)
|
||||
q.doneWaiter = nil
|
||||
}
|
||||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
// Shutdown asynchronously signals the queue to stop.
|
||||
func (q *ExecQueue) Shutdown() {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
q.closed = true
|
||||
}
|
||||
|
||||
// Wait waits for the queue to be empty.
|
||||
func (q *ExecQueue) Wait(ctx context.Context) error {
|
||||
q.mu.Lock()
|
||||
waitCh := q.doneWaiter
|
||||
if q.inFlight && waitCh == nil {
|
||||
waitCh = make(chan struct{})
|
||||
q.doneWaiter = waitCh
|
||||
}
|
||||
q.mu.Unlock()
|
||||
|
||||
if waitCh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-waitCh:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
22
util/execqueue/execqueue_test.go
Normal file
22
util/execqueue/execqueue_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package execqueue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExecQueue(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var n atomic.Int32
|
||||
q := &ExecQueue{}
|
||||
defer q.Shutdown()
|
||||
q.Add(func() { n.Add(1) })
|
||||
q.Wait(ctx)
|
||||
if got := n.Load(); got != 1 {
|
||||
t.Errorf("n=%d; want 1", got)
|
||||
}
|
||||
}
|
||||
89
util/expvarx/expvarx.go
Normal file
89
util/expvarx/expvarx.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package expvarx provides some extensions to the [expvar] package.
|
||||
package expvarx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"expvar"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/types/lazy"
|
||||
)
|
||||
|
||||
// SafeFunc is a wrapper around [expvar.Func] that guards against unbounded call
|
||||
// time and ensures that only a single call is in progress at any given time.
|
||||
type SafeFunc struct {
|
||||
f expvar.Func
|
||||
limit time.Duration
|
||||
onSlow func(time.Duration, any)
|
||||
|
||||
mu sync.Mutex
|
||||
inflight *lazy.SyncValue[any]
|
||||
}
|
||||
|
||||
// NewSafeFunc returns a new SafeFunc that wraps f.
|
||||
// If f takes longer than limit to execute then Value calls return nil.
|
||||
// If onSlow is non-nil, it is called when f takes longer than limit to execute.
|
||||
// onSlow is called with the duration of the slow call and the final computed
|
||||
// value.
|
||||
func NewSafeFunc(f expvar.Func, limit time.Duration, onSlow func(time.Duration, any)) *SafeFunc {
|
||||
return &SafeFunc{f: f, limit: limit, onSlow: onSlow}
|
||||
}
|
||||
|
||||
// Value acts similarly to [expvar.Func.Value], but if the underlying function
|
||||
// takes longer than the configured limit, all callers will receive nil until
|
||||
// the underlying operation completes. On completion of the underlying
|
||||
// operation, the onSlow callback is called if set.
|
||||
func (s *SafeFunc) Value() any {
|
||||
s.mu.Lock()
|
||||
|
||||
if s.inflight == nil {
|
||||
s.inflight = new(lazy.SyncValue[any])
|
||||
}
|
||||
var inflight = s.inflight
|
||||
s.mu.Unlock()
|
||||
|
||||
// inflight ensures that only a single work routine is spawned at any given
|
||||
// time, but if the routine takes too long inflight is populated with a nil
|
||||
// result. The long running computed value is lost forever.
|
||||
return inflight.Get(func() any {
|
||||
start := time.Now()
|
||||
result := make(chan any, 1)
|
||||
|
||||
// work is spawned in routine so that the caller can timeout.
|
||||
go func() {
|
||||
// Allow new work to be started after this work completes
|
||||
defer func() {
|
||||
s.mu.Lock()
|
||||
s.inflight = nil
|
||||
s.mu.Unlock()
|
||||
|
||||
}()
|
||||
|
||||
v := s.f.Value()
|
||||
result <- v
|
||||
}()
|
||||
|
||||
select {
|
||||
case v := <-result:
|
||||
return v
|
||||
case <-time.After(s.limit):
|
||||
if s.onSlow != nil {
|
||||
go func() {
|
||||
s.onSlow(time.Since(start), <-result)
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// String implements stringer in the same pattern as [expvar.Func], calling
|
||||
// Value and serializing the result as JSON, ignoring errors.
|
||||
func (s *SafeFunc) String() string {
|
||||
v, _ := json.Marshal(s.Value())
|
||||
return string(v)
|
||||
}
|
||||
137
util/expvarx/expvarx_test.go
Normal file
137
util/expvarx/expvarx_test.go
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package expvarx
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleNewSafeFunc() {
|
||||
// An artificial blocker to emulate a slow operation.
|
||||
blocker := make(chan struct{})
|
||||
|
||||
// limit is the amount of time a call can take before Value returns nil. No
|
||||
// new calls to the unsafe func will be started until the slow call
|
||||
// completes, at which point onSlow will be called.
|
||||
limit := time.Millisecond
|
||||
|
||||
// onSlow is called with the final call duration and the final value in the
|
||||
// event a slow call.
|
||||
onSlow := func(d time.Duration, v any) {
|
||||
_ = d // d contains the time the call took
|
||||
_ = v // v contains the final value computed by the slow call
|
||||
fmt.Println("slow call!")
|
||||
}
|
||||
|
||||
// An unsafe expvar.Func that blocks on the blocker channel.
|
||||
unsafeFunc := expvar.Func(func() any {
|
||||
for range blocker {
|
||||
}
|
||||
return "hello world"
|
||||
})
|
||||
|
||||
// f implements the same interface as expvar.Func, but returns nil values
|
||||
// when the unsafe func is too slow.
|
||||
f := NewSafeFunc(unsafeFunc, limit, onSlow)
|
||||
|
||||
fmt.Println(f.Value())
|
||||
fmt.Println(f.Value())
|
||||
close(blocker)
|
||||
time.Sleep(time.Millisecond)
|
||||
fmt.Println(f.Value())
|
||||
// Output: <nil>
|
||||
// <nil>
|
||||
// slow call!
|
||||
// hello world
|
||||
}
|
||||
|
||||
func TestSafeFuncHappyPath(t *testing.T) {
|
||||
var count int
|
||||
f := NewSafeFunc(expvar.Func(func() any {
|
||||
count++
|
||||
return count
|
||||
}), time.Millisecond, nil)
|
||||
|
||||
if got, want := f.Value(), 1; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
if got, want := f.Value(), 2; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafeFuncSlow(t *testing.T) {
|
||||
var count int
|
||||
blocker := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
f := NewSafeFunc(expvar.Func(func() any {
|
||||
defer wg.Done()
|
||||
count++
|
||||
<-blocker
|
||||
return count
|
||||
}), time.Millisecond, nil)
|
||||
|
||||
if got := f.Value(); got != nil {
|
||||
t.Errorf("got %v; want nil", got)
|
||||
}
|
||||
if got := f.Value(); got != nil {
|
||||
t.Errorf("got %v; want nil", got)
|
||||
}
|
||||
|
||||
close(blocker)
|
||||
wg.Wait()
|
||||
|
||||
if count != 1 {
|
||||
t.Errorf("got count=%d; want 1", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafeFuncSlowOnSlow(t *testing.T) {
|
||||
var count int
|
||||
blocker := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
var slowDuration atomic.Pointer[time.Duration]
|
||||
var slowCallCount atomic.Int32
|
||||
var slowValue atomic.Value
|
||||
f := NewSafeFunc(expvar.Func(func() any {
|
||||
defer wg.Done()
|
||||
count++
|
||||
<-blocker
|
||||
return count
|
||||
}), time.Millisecond, func(d time.Duration, v any) {
|
||||
defer wg.Done()
|
||||
slowDuration.Store(&d)
|
||||
slowCallCount.Add(1)
|
||||
slowValue.Store(v)
|
||||
})
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
if got := f.Value(); got != nil {
|
||||
t.Fatalf("got value=%v; want nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
close(blocker)
|
||||
wg.Wait()
|
||||
|
||||
if count != 1 {
|
||||
t.Errorf("got count=%d; want 1", count)
|
||||
}
|
||||
if got, want := *slowDuration.Load(), 1*time.Millisecond; got < want {
|
||||
t.Errorf("got slowDuration=%v; want at least %d", got, want)
|
||||
}
|
||||
if got, want := slowCallCount.Load(), int32(1); got != want {
|
||||
t.Errorf("got slowCallCount=%d; want %d", got, want)
|
||||
}
|
||||
if got, want := slowValue.Load().(int), 1; got != want {
|
||||
t.Errorf("got slowValue=%d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,22 @@ func Delete[K ~string, V any](m map[K]V, k K) {
|
||||
delete(m, K(appendToLower(a[:0], string(k))))
|
||||
}
|
||||
|
||||
// AppendSliceElem is equivalent to:
|
||||
//
|
||||
// append(m[strings.ToLower(k)], v)
|
||||
func AppendSliceElem[K ~string, S []E, E any](m map[K]S, k K, vs ...E) {
|
||||
// if the key is already lowercased
|
||||
if isLowerASCII(string(k)) {
|
||||
m[k] = append(m[k], vs...)
|
||||
return
|
||||
}
|
||||
|
||||
// if key needs to become lowercase, uses appendToLower
|
||||
var a [stackArraySize]byte
|
||||
s := appendToLower(a[:0], string(k))
|
||||
m[K(s)] = append(m[K(s)], vs...)
|
||||
}
|
||||
|
||||
func isLowerASCII(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if c := s[i]; c >= utf8.RuneSelf || ('A' <= c && c <= 'Z') {
|
||||
|
||||
@@ -45,6 +45,23 @@ func Test(t *testing.T) {
|
||||
c.Assert(m, qt.DeepEquals, map[string]int{"hello": 2, "baz": 5})
|
||||
Delete(m, "BAZ")
|
||||
c.Assert(m, qt.DeepEquals, map[string]int{"hello": 2})
|
||||
// test cases for AppendSliceElem with int slices
|
||||
appendTestInt := make(map[string][]int)
|
||||
Set(appendTestInt, "firsT", []int{7})
|
||||
c.Assert(appendTestInt, qt.DeepEquals, map[string][]int{"first": {7}})
|
||||
AppendSliceElem(appendTestInt, "firsT", 77)
|
||||
c.Assert(appendTestInt, qt.DeepEquals, map[string][]int{"first": {7, 77}})
|
||||
Set(appendTestInt, "SeCOnd", []int{56})
|
||||
c.Assert(appendTestInt, qt.DeepEquals, map[string][]int{"first": {7, 77}, "second": {56}})
|
||||
AppendSliceElem(appendTestInt, "seCOnd", 563, 23)
|
||||
c.Assert(appendTestInt, qt.DeepEquals, map[string][]int{"first": {7, 77}, "second": {56, 563, 23}})
|
||||
// test cases for AppendSliceElem with string slices
|
||||
appendTestString := make(map[string][]string)
|
||||
Set(appendTestString, "firsTSTRING", []string{"hi"})
|
||||
c.Assert(appendTestString, qt.DeepEquals, map[string][]string{"firststring": {"hi"}})
|
||||
AppendSliceElem(appendTestString, "firsTSTRING", "hello", "bye")
|
||||
c.Assert(appendTestString, qt.DeepEquals, map[string][]string{"firststring": {"hi", "hello", "bye"}})
|
||||
|
||||
}
|
||||
|
||||
var lowerTests = []struct{ in, want string }{
|
||||
|
||||
@@ -77,3 +77,17 @@ func EqualSameNil[S ~[]E, E comparable](s1, s2 S) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Filter calls fn with each element of the provided src slice, and appends the
|
||||
// element to dst if fn returns true.
|
||||
//
|
||||
// dst can be nil to allocate a new slice, or set to src[:0] to filter in-place
|
||||
// without allocating.
|
||||
func Filter[S ~[]T, T any](dst, src S, fn func(T) bool) S {
|
||||
for _, x := range src {
|
||||
if fn(x) {
|
||||
dst = append(dst, x)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
@@ -97,3 +97,42 @@ func TestEqualSameNil(t *testing.T) {
|
||||
c.Check(EqualSameNil([]string{}, nil), qt.Equals, false)
|
||||
c.Check(EqualSameNil[[]string](nil, nil), qt.Equals, true)
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
var sl []int
|
||||
for i := 1; i <= 10; i++ {
|
||||
sl = append(sl, i)
|
||||
}
|
||||
|
||||
evens := Filter(nil, sl, func(elem int) bool {
|
||||
return elem%2 == 0
|
||||
})
|
||||
|
||||
want := []int{2, 4, 6, 8, 10}
|
||||
if !reflect.DeepEqual(evens, want) {
|
||||
t.Errorf("evens: got %v, want %v", evens, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterNoAllocations(t *testing.T) {
|
||||
var sl []int
|
||||
for i := 1; i <= 10; i++ {
|
||||
sl = append(sl, i)
|
||||
}
|
||||
|
||||
want := []int{2, 4, 6, 8, 10}
|
||||
allocs := testing.AllocsPerRun(1000, func() {
|
||||
src := slices.Clone(sl)
|
||||
evens := Filter(src[:0], src, func(elem int) bool {
|
||||
return elem%2 == 0
|
||||
})
|
||||
if !slices.Equal(evens, want) {
|
||||
t.Errorf("evens: got %v, want %v", evens, want)
|
||||
}
|
||||
})
|
||||
|
||||
// 1 alloc for 'src', nothing else
|
||||
if allocs != 1 {
|
||||
t.Fatalf("got %.4f allocs, want 1", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestDockerfileVersion(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := regexp.MustCompile(`(?m)^go (\d\.\d+)\r?$`).FindStringSubmatch(string(goMod))
|
||||
m := regexp.MustCompile(`(?m)^go (\d\.\d+)\r?($|\.)`).FindStringSubmatch(string(goMod))
|
||||
if m == nil {
|
||||
t.Fatalf("didn't find go version in go.mod")
|
||||
}
|
||||
|
||||
@@ -123,11 +123,11 @@ func (c *Conn) ServeHTTPDebug(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func printEndpointHTML(w io.Writer, ep *endpoint) {
|
||||
lastRecv := ep.lastRecv.LoadAtomic()
|
||||
lastRecv := ep.lastRecvWG.LoadAtomic()
|
||||
|
||||
ep.mu.Lock()
|
||||
defer ep.mu.Unlock()
|
||||
if ep.lastSend == 0 && lastRecv == 0 {
|
||||
if ep.lastSendExt == 0 && lastRecv == 0 {
|
||||
return // no activity ever
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ func printEndpointHTML(w io.Writer, ep *endpoint) {
|
||||
|
||||
fmt.Fprintf(w, "<p>Best: <b>%+v</b>, %v ago (for %v)</p>\n", ep.bestAddr, fmtMono(ep.bestAddrAt), ep.trustBestAddrUntil.Sub(mnow).Round(time.Millisecond))
|
||||
fmt.Fprintf(w, "<p>heartbeating: %v</p>\n", ep.heartBeatTimer != nil)
|
||||
fmt.Fprintf(w, "<p>lastSend: %v ago</p>\n", fmtMono(ep.lastSend))
|
||||
fmt.Fprintf(w, "<p>lastSend: %v ago</p>\n", fmtMono(ep.lastSendExt))
|
||||
fmt.Fprintf(w, "<p>lastFullPing: %v ago</p>\n", fmtMono(ep.lastFullPing))
|
||||
|
||||
eps := make([]netip.AddrPort, 0, len(ep.endpointState))
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/mak"
|
||||
@@ -668,7 +669,7 @@ func (c *Conn) processDERPReadResult(dm derpReadResult, b []byte) (n int, ep *en
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
ep.noteRecvActivity(ipp)
|
||||
ep.noteRecvActivity(ipp, mono.Now())
|
||||
if stats := c.stats.Load(); stats != nil {
|
||||
stats.UpdateRxPhysical(ep.nodeAddr, ipp, dm.n)
|
||||
}
|
||||
|
||||
@@ -14,11 +14,12 @@ func _() {
|
||||
_ = x[pingDiscovery-0]
|
||||
_ = x[pingHeartbeat-1]
|
||||
_ = x[pingCLI-2]
|
||||
_ = x[pingHeartbeatForUDPLifetime-3]
|
||||
}
|
||||
|
||||
const _discoPingPurpose_name = "DiscoveryHeartbeatCLI"
|
||||
const _discoPingPurpose_name = "DiscoveryHeartbeatCLIHeartbeatForUDPLifetime"
|
||||
|
||||
var _discoPingPurpose_index = [...]uint8{0, 9, 18, 21}
|
||||
var _discoPingPurpose_index = [...]uint8{0, 9, 18, 21, 44}
|
||||
|
||||
func (i discoPingPurpose) String() string {
|
||||
if i < 0 || i >= discoPingPurpose(len(_discoPingPurpose_index)-1) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -55,7 +56,8 @@ func init() {
|
||||
// to the peer.
|
||||
type endpoint struct {
|
||||
// atomically accessed; declared first for alignment reasons
|
||||
lastRecv mono.Time
|
||||
lastRecvWG mono.Time // last time there were incoming packets from this peer destined for wireguard-go (e.g. not disco)
|
||||
lastRecvUDPAny mono.Time // last time there were incoming UDP packets from this peer of any kind
|
||||
numStopAndResetAtomic int64
|
||||
debugUpdates *ringbuffer.RingBuffer[EndpointChange]
|
||||
|
||||
@@ -73,11 +75,12 @@ type endpoint struct {
|
||||
mu sync.Mutex // Lock ordering: Conn.mu, then endpoint.mu
|
||||
|
||||
heartBeatTimer *time.Timer // nil when idle
|
||||
lastSend mono.Time // last time there was outgoing packets sent to this peer (from wireguard-go)
|
||||
lastSendExt mono.Time // last time there were outgoing packets sent to this peer from an external trigger (e.g. wireguard-go or disco pingCLI)
|
||||
lastSendAny mono.Time // last time there were outgoing packets sent this peer from any trigger, internal or external to magicsock
|
||||
lastFullPing mono.Time // last time we pinged all disco or wireguard only endpoints
|
||||
derpAddr netip.AddrPort // fallback/bootstrap path, if non-zero (non-zero for well-behaved clients)
|
||||
|
||||
bestAddr addrQuality // best non-DERP path; zero if none
|
||||
bestAddr addrQuality // best non-DERP path; zero if none; mutate via setBestAddrLocked()
|
||||
bestAddrAt mono.Time // time best address re-confirmed
|
||||
trustBestAddrUntil mono.Time // time when bestAddr expires
|
||||
sentPing map[stun.TxID]sentPing
|
||||
@@ -88,11 +91,240 @@ type endpoint struct {
|
||||
// implementation that's a WIP as of 2022-10-20.
|
||||
// See #540 for background.
|
||||
heartbeatDisabled bool
|
||||
probeUDPLifetime *probeUDPLifetime // UDP path lifetime probing; nil if disabled
|
||||
|
||||
expired bool // whether the node has expired
|
||||
isWireguardOnly bool // whether the endpoint is WireGuard only
|
||||
}
|
||||
|
||||
func (de *endpoint) setBestAddrLocked(v addrQuality) {
|
||||
if v.AddrPort != de.bestAddr.AddrPort {
|
||||
de.probeUDPLifetime.resetCycleEndpointLocked()
|
||||
}
|
||||
de.bestAddr = v
|
||||
}
|
||||
|
||||
const (
|
||||
// udpLifetimeProbeCliffSlack is how much slack to use relative to a
|
||||
// ProbeUDPLifetimeConfig.Cliffs duration in order to account for RTT,
|
||||
// scheduling jitter, buffers, etc. If the cliff is 10s, we attempt to probe
|
||||
// after 10s - 2s (8s) amount of inactivity.
|
||||
udpLifetimeProbeCliffSlack = time.Second * 2
|
||||
// udpLifetimeProbeSchedulingTolerance is how much of a difference can be
|
||||
// tolerated between a UDP lifetime probe scheduling target and when it
|
||||
// actually fired. This must be some fraction of udpLifetimeProbeCliffSlack.
|
||||
udpLifetimeProbeSchedulingTolerance = udpLifetimeProbeCliffSlack / 8
|
||||
)
|
||||
|
||||
// probeUDPLifetime represents the configuration and state tied to probing UDP
|
||||
// path lifetime. A probe "cycle" involves pinging the UDP path at various
|
||||
// timeout cliffs, which are pre-defined durations of interest commonly used by
|
||||
// NATs/firewalls as default stateful session timeout values. Cliffs are probed
|
||||
// in ascending order. A "cycle" completes when all cliffs have received a pong,
|
||||
// or when a ping times out. Cycles may extend across endpoint session lifetimes
|
||||
// if they are disrupted by user traffic.
|
||||
type probeUDPLifetime struct {
|
||||
// All fields are guarded by endpoint.mu. probeUDPLifetime methods are for
|
||||
// convenience.
|
||||
|
||||
// config holds the probing configuration.
|
||||
config ProbeUDPLifetimeConfig
|
||||
|
||||
// timer is nil when idle. A non-nil timer indicates we intend to probe a
|
||||
// timeout cliff in the future.
|
||||
timer *time.Timer
|
||||
|
||||
// bestAddr contains the endpoint.bestAddr.AddrPort at the time a cycle was
|
||||
// scheduled to start. A probing cycle is 1:1 with the current
|
||||
// endpoint.bestAddr.AddrPort in the interest of simplicity. When
|
||||
// endpoint.bestAddr.AddrPort changes, any active probing cycle will reset.
|
||||
bestAddr netip.AddrPort
|
||||
// cycleStartedAt contains the time at which the first cliff
|
||||
// (ProbeUDPLifetimeConfig.Cliffs[0]) was pinged for the current/last cycle.
|
||||
cycleStartedAt time.Time
|
||||
// cycleActive is true if a probing cycle is active, otherwise false.
|
||||
cycleActive bool
|
||||
// currentCliff represents the index into ProbeUDPLifetimeConfig.Cliffs for
|
||||
// the cliff that we are waiting to ping, or waiting on a pong/timeout.
|
||||
currentCliff int
|
||||
// lastTxID is the ID for the last ping that was sent.
|
||||
lastTxID stun.TxID
|
||||
}
|
||||
|
||||
func (p *probeUDPLifetime) currentCliffDurationEndpointLocked() time.Duration {
|
||||
if p == nil {
|
||||
return 0
|
||||
}
|
||||
return p.config.Cliffs[p.currentCliff]
|
||||
}
|
||||
|
||||
// cycleCompleteMaxCliffEndpointLocked records the max cliff (as an index of
|
||||
// ProbeUDPLifetimeConfig.Cliffs) a probing cycle reached, i.e. received a pong
|
||||
// for. A value < 0 indicates no cliff was reached. It is a no-op if the active
|
||||
// configuration does not equal defaultProbeUDPLifetimeConfig.
|
||||
func (p *probeUDPLifetime) cycleCompleteMaxCliffEndpointLocked(cliff int) {
|
||||
if !p.config.Equals(defaultProbeUDPLifetimeConfig) {
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case cliff < 0:
|
||||
metricUDPLifetimeCycleCompleteNoCliffReached.Add(1)
|
||||
case cliff == 0:
|
||||
metricUDPLifetimeCycleCompleteAt10sCliff.Add(1)
|
||||
case cliff == 1:
|
||||
metricUDPLifetimeCycleCompleteAt30sCliff.Add(1)
|
||||
case cliff == 2:
|
||||
metricUDPLifetimeCycleCompleteAt60sCliff.Add(1)
|
||||
}
|
||||
}
|
||||
|
||||
// resetCycleEndpointLocked resets the state contained in p to reflect an
|
||||
// inactive cycle.
|
||||
func (p *probeUDPLifetime) resetCycleEndpointLocked() {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
if p.timer != nil {
|
||||
p.timer.Stop()
|
||||
p.timer = nil
|
||||
}
|
||||
p.cycleActive = false
|
||||
p.currentCliff = 0
|
||||
p.bestAddr = netip.AddrPort{}
|
||||
}
|
||||
|
||||
// ProbeUDPLifetimeConfig represents the configuration for probing UDP path
|
||||
// lifetime.
|
||||
type ProbeUDPLifetimeConfig struct {
|
||||
// The timeout cliffs to probe. Values are in ascending order. Ascending
|
||||
// order is chosen over descending because we have limited opportunities to
|
||||
// probe. With a descending order we are stuck waiting for a new UDP
|
||||
// path/session if the first value times out. When that new path is
|
||||
// established is anyone's guess.
|
||||
Cliffs []time.Duration
|
||||
// CycleCanStartEvery represents the min duration between cycles starting
|
||||
// up.
|
||||
CycleCanStartEvery time.Duration
|
||||
}
|
||||
|
||||
var (
|
||||
// defaultProbeUDPLifetimeConfig is the configuration that must be used
|
||||
// for UDP path lifetime probing until it can be wholly disseminated (not
|
||||
// just on/off) from upstream control components, and associated metrics
|
||||
// (metricUDPLifetime*) have lifetime management.
|
||||
//
|
||||
// TODO(#10928): support dynamic config via tailcfg.PeerCapMap.
|
||||
defaultProbeUDPLifetimeConfig = &ProbeUDPLifetimeConfig{
|
||||
Cliffs: []time.Duration{
|
||||
time.Second * 10,
|
||||
time.Second * 30,
|
||||
time.Second * 60,
|
||||
},
|
||||
CycleCanStartEvery: time.Hour * 24,
|
||||
}
|
||||
)
|
||||
|
||||
// Equals returns true if b equals p, otherwise false. If both sides are nil,
|
||||
// Equals returns true. If only one side is nil, Equals returns false.
|
||||
func (p *ProbeUDPLifetimeConfig) Equals(b *ProbeUDPLifetimeConfig) bool {
|
||||
if p == b {
|
||||
return true
|
||||
}
|
||||
if (p == nil && b != nil) || (b == nil && p != nil) {
|
||||
return false
|
||||
}
|
||||
if !slices.Equal(p.Cliffs, b.Cliffs) {
|
||||
return false
|
||||
}
|
||||
if p.CycleCanStartEvery != b.CycleCanStartEvery {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Valid returns true if p is valid, otherwise false. p must be non-nil.
|
||||
func (p *ProbeUDPLifetimeConfig) Valid() bool {
|
||||
if len(p.Cliffs) < 1 {
|
||||
// We need at least one cliff, otherwise there is nothing to probe.
|
||||
return false
|
||||
}
|
||||
if p.CycleCanStartEvery < 1 {
|
||||
// Probing must be constrained by a positive CycleCanStartEvery.
|
||||
return false
|
||||
}
|
||||
for i, c := range p.Cliffs {
|
||||
if c <= max(udpLifetimeProbeCliffSlack*2, heartbeatInterval) {
|
||||
// A timeout cliff less than or equal to twice
|
||||
// udpLifetimeProbeCliffSlack is invalid due to being effectively
|
||||
// zero when the cliff slack is subtracted from the cliff value at
|
||||
// scheduling time.
|
||||
//
|
||||
// A timeout cliff less or equal to the heartbeatInterval is also
|
||||
// invalid, as we may attempt to schedule on the tail end of the
|
||||
// last heartbeat tied to an active session.
|
||||
//
|
||||
// These values are constants, but max()'d in case they change in
|
||||
// the future.
|
||||
return false
|
||||
}
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
if c <= p.Cliffs[i-1] {
|
||||
// Cliffs must be in ascending order.
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// setProbeUDPLifetimeOn enables or disables probing of UDP path lifetime based
|
||||
// on v. In the case of enablement defaultProbeUDPLifetimeConfig is used as the
|
||||
// desired configuration.
|
||||
func (de *endpoint) setProbeUDPLifetimeOn(v bool) {
|
||||
de.mu.Lock()
|
||||
if v {
|
||||
de.setProbeUDPLifetimeConfigLocked(defaultProbeUDPLifetimeConfig)
|
||||
} else {
|
||||
de.setProbeUDPLifetimeConfigLocked(nil)
|
||||
}
|
||||
de.mu.Unlock()
|
||||
}
|
||||
|
||||
// setProbeUDPLifetimeConfigLocked sets the desired configuration for probing
|
||||
// UDP path lifetime. Ownership of desired is passed to endpoint, it must not be
|
||||
// mutated once this call is made. A nil value disables the feature. If desired
|
||||
// is non-nil but desired.Valid() returns false this is a no-op.
|
||||
func (de *endpoint) setProbeUDPLifetimeConfigLocked(desired *ProbeUDPLifetimeConfig) {
|
||||
if de.isWireguardOnly {
|
||||
return
|
||||
}
|
||||
if desired == nil {
|
||||
if de.probeUDPLifetime == nil {
|
||||
// noop, not currently configured or desired
|
||||
return
|
||||
}
|
||||
de.probeUDPLifetime.resetCycleEndpointLocked()
|
||||
de.probeUDPLifetime = nil
|
||||
return
|
||||
}
|
||||
if !desired.Valid() {
|
||||
return
|
||||
}
|
||||
if de.probeUDPLifetime != nil {
|
||||
if de.probeUDPLifetime.config.Equals(desired) {
|
||||
// noop, current config equals desired
|
||||
return
|
||||
}
|
||||
de.probeUDPLifetime.resetCycleEndpointLocked()
|
||||
} else {
|
||||
de.probeUDPLifetime = &probeUDPLifetime{}
|
||||
}
|
||||
p := de.probeUDPLifetime
|
||||
p.config = *desired
|
||||
p.resetCycleEndpointLocked()
|
||||
}
|
||||
|
||||
// endpointDisco is the current disco key and short string for an endpoint. This
|
||||
// structure is immutable.
|
||||
type endpointDisco struct {
|
||||
@@ -219,7 +451,7 @@ func (de *endpoint) deleteEndpointLocked(why string, ep netip.AddrPort) {
|
||||
What: "deleteEndpointLocked-bestAddr-" + why,
|
||||
From: de.bestAddr,
|
||||
})
|
||||
de.bestAddr = addrQuality{}
|
||||
de.setBestAddrLocked(addrQuality{})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,9 +468,7 @@ func (de *endpoint) initFakeUDPAddr() {
|
||||
|
||||
// noteRecvActivity records receive activity on de, and invokes
|
||||
// Conn.noteRecvActivity no more than once every 10s.
|
||||
func (de *endpoint) noteRecvActivity(ipp netip.AddrPort) {
|
||||
now := mono.Now()
|
||||
|
||||
func (de *endpoint) noteRecvActivity(ipp netip.AddrPort, now mono.Time) {
|
||||
if de.isWireguardOnly {
|
||||
de.mu.Lock()
|
||||
de.bestAddr.AddrPort = ipp
|
||||
@@ -257,9 +487,9 @@ func (de *endpoint) noteRecvActivity(ipp netip.AddrPort) {
|
||||
de.mu.Unlock()
|
||||
}
|
||||
|
||||
elapsed := now.Sub(de.lastRecv.LoadAtomic())
|
||||
elapsed := now.Sub(de.lastRecvWG.LoadAtomic())
|
||||
if elapsed > 10*time.Second {
|
||||
de.lastRecv.StoreAtomic(now)
|
||||
de.lastRecvWG.StoreAtomic(now)
|
||||
|
||||
if de.c.noteRecvActivity == nil {
|
||||
return
|
||||
@@ -410,12 +640,140 @@ func (de *endpoint) addrForPingSizeLocked(now mono.Time, size int) (udpAddr, der
|
||||
return netip.AddrPort{}, de.derpAddr
|
||||
}
|
||||
|
||||
// maybeProbeUDPLifetimeLocked returns an afterInactivityFor duration and true
|
||||
// if de is a candidate for UDP path lifetime probing in the future, otherwise
|
||||
// false.
|
||||
func (de *endpoint) maybeProbeUDPLifetimeLocked() (afterInactivityFor time.Duration, maybe bool) {
|
||||
p := de.probeUDPLifetime
|
||||
if p == nil {
|
||||
return afterInactivityFor, false
|
||||
}
|
||||
if !de.bestAddr.IsValid() {
|
||||
return afterInactivityFor, false
|
||||
}
|
||||
epDisco := de.disco.Load()
|
||||
if epDisco == nil {
|
||||
// peer does not support disco
|
||||
return afterInactivityFor, false
|
||||
}
|
||||
// We compare disco keys, which may have a shorter lifetime than node keys
|
||||
// since disco keys reset on startup. This has the desired side effect of
|
||||
// shuffling probing probability where the local node ends up with a large
|
||||
// key value lexicographically relative to the other nodes it tends to
|
||||
// communicate with. If de's disco key changes, the cycle will reset.
|
||||
if de.c.discoPublic.Compare(epDisco.key) >= 0 {
|
||||
// lower disco pub key node probes higher
|
||||
return afterInactivityFor, false
|
||||
}
|
||||
if !p.cycleActive && time.Since(p.cycleStartedAt) < p.config.CycleCanStartEvery {
|
||||
// This is conservative as it doesn't account for afterInactivityFor use
|
||||
// by the caller, potentially delaying the start of the next cycle. We
|
||||
// assume the cycle could start immediately following
|
||||
// maybeProbeUDPLifetimeLocked(), regardless of the value of
|
||||
// afterInactivityFor relative to latest packets in/out time.
|
||||
return afterInactivityFor, false
|
||||
}
|
||||
afterInactivityFor = p.currentCliffDurationEndpointLocked() - udpLifetimeProbeCliffSlack
|
||||
if afterInactivityFor < 0 {
|
||||
// shouldn't happen
|
||||
return afterInactivityFor, false
|
||||
}
|
||||
return afterInactivityFor, true
|
||||
}
|
||||
|
||||
// heartbeatForLifetimeVia represents the scheduling source of
|
||||
// endpoint.heartbeatForLifetime().
|
||||
type heartbeatForLifetimeVia string
|
||||
|
||||
const (
|
||||
heartbeatForLifetimeViaSessionInactive heartbeatForLifetimeVia = "session-inactive"
|
||||
heartbeatForLifetimeViaPongRx heartbeatForLifetimeVia = "pong-rx"
|
||||
heartbeatForLifetimeViaSelf heartbeatForLifetimeVia = "self"
|
||||
)
|
||||
|
||||
// scheduleHeartbeatForLifetimeLocked schedules de.heartbeatForLifetime to fire
|
||||
// in the future (after). The caller must describe themselves in the via arg.
|
||||
func (de *endpoint) scheduleHeartbeatForLifetimeLocked(after time.Duration, via heartbeatForLifetimeVia) {
|
||||
p := de.probeUDPLifetime
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
de.c.dlogf("[v1] magicsock: disco: scheduling UDP lifetime probe for cliff=%v via=%v to %v (%v)",
|
||||
p.currentCliffDurationEndpointLocked(), via, de.publicKey.ShortString(), de.discoShort())
|
||||
p.bestAddr = de.bestAddr.AddrPort
|
||||
p.timer = time.AfterFunc(after, de.heartbeatForLifetime)
|
||||
if via == heartbeatForLifetimeViaSelf {
|
||||
metricUDPLifetimeCliffsRescheduled.Add(1)
|
||||
} else {
|
||||
metricUDPLifetimeCliffsScheduled.Add(1)
|
||||
}
|
||||
}
|
||||
|
||||
// heartbeatForLifetime sends a disco ping recorded locally with a purpose of
|
||||
// pingHeartbeatForUDPLifetime to de if de.bestAddr has remained stable, and it
|
||||
// has been inactive for a duration that is within the error bounds for current
|
||||
// lifetime probing cliff. Alternatively it may reschedule itself into the
|
||||
// future, which is one of three scheduling sources. The other scheduling
|
||||
// sources are de.heartbeat() and de.probeUDPLifetimeCliffDoneLocked().
|
||||
func (de *endpoint) heartbeatForLifetime() {
|
||||
de.mu.Lock()
|
||||
defer de.mu.Unlock()
|
||||
p := de.probeUDPLifetime
|
||||
if p == nil || p.timer == nil {
|
||||
// We raced with a code path trying to p.timer.Stop() us. Give up early
|
||||
// in the interest of simplicity. If p.timer.Stop() happened in
|
||||
// de.heartbeat() presumably because of recent packets in/out we *could*
|
||||
// still probe here, and it would be meaningful, but the time logic
|
||||
// below would reschedule as-is.
|
||||
return
|
||||
}
|
||||
p.timer = nil
|
||||
if !p.bestAddr.IsValid() || de.bestAddr.AddrPort != p.bestAddr {
|
||||
// best path changed
|
||||
p.resetCycleEndpointLocked()
|
||||
return
|
||||
}
|
||||
afterInactivityFor, ok := de.maybeProbeUDPLifetimeLocked()
|
||||
if !ok {
|
||||
p.resetCycleEndpointLocked()
|
||||
return
|
||||
}
|
||||
inactiveFor := mono.Now().Sub(max(de.lastRecvUDPAny.LoadAtomic(), de.lastSendAny))
|
||||
delta := afterInactivityFor - inactiveFor
|
||||
if delta.Abs() > udpLifetimeProbeSchedulingTolerance {
|
||||
if delta < 0 {
|
||||
// We missed our opportunity. We can resume this cliff at the tail
|
||||
// end of another session.
|
||||
metricUDPLifetimeCliffsMissed.Add(1)
|
||||
return
|
||||
} else {
|
||||
// We need to wait longer before sending a ping. This can happen for
|
||||
// a number of reasons, which are described in more detail in
|
||||
// de.heartbeat().
|
||||
de.scheduleHeartbeatForLifetimeLocked(delta, heartbeatForLifetimeViaSelf)
|
||||
return
|
||||
}
|
||||
}
|
||||
if p.currentCliff == 0 {
|
||||
p.cycleStartedAt = time.Now()
|
||||
p.cycleActive = true
|
||||
}
|
||||
de.c.dlogf("[v1] magicsock: disco: sending disco ping for UDP lifetime probe cliff=%v to %v (%v)",
|
||||
p.currentCliffDurationEndpointLocked(), de.publicKey.ShortString(), de.discoShort())
|
||||
de.startDiscoPingLocked(de.bestAddr.AddrPort, mono.Now(), pingHeartbeatForUDPLifetime, 0, nil)
|
||||
}
|
||||
|
||||
// heartbeat is called every heartbeatInterval to keep the best UDP path alive,
|
||||
// or kick off discovery of other paths.
|
||||
// kick off discovery of other paths, or schedule the probing of UDP path
|
||||
// lifetime on the tail end of an active session.
|
||||
func (de *endpoint) heartbeat() {
|
||||
de.mu.Lock()
|
||||
defer de.mu.Unlock()
|
||||
|
||||
if de.probeUDPLifetime != nil && de.probeUDPLifetime.timer != nil {
|
||||
de.probeUDPLifetime.timer.Stop()
|
||||
de.probeUDPLifetime.timer = nil
|
||||
}
|
||||
de.heartBeatTimer = nil
|
||||
|
||||
if de.heartbeatDisabled {
|
||||
@@ -423,18 +781,42 @@ func (de *endpoint) heartbeat() {
|
||||
return
|
||||
}
|
||||
|
||||
if de.lastSend.IsZero() {
|
||||
if de.lastSendExt.IsZero() {
|
||||
// Shouldn't happen.
|
||||
return
|
||||
}
|
||||
|
||||
if mono.Since(de.lastSend) > sessionActiveTimeout {
|
||||
now := mono.Now()
|
||||
if now.Sub(de.lastSendExt) > sessionActiveTimeout {
|
||||
// Session's idle. Stop heartbeating.
|
||||
de.c.dlogf("[v1] magicsock: disco: ending heartbeats for idle session to %v (%v)", de.publicKey.ShortString(), de.discoShort())
|
||||
if afterInactivityFor, ok := de.maybeProbeUDPLifetimeLocked(); ok {
|
||||
// This is the best place to best effort schedule a probe of UDP
|
||||
// path lifetime in the future as it loosely translates to "UDP path
|
||||
// is inactive".
|
||||
//
|
||||
// Note: wireguard-go schedules a WireGuard keepalive packet (by
|
||||
// default, not tied to persistent keepalive feature) 10 seconds in
|
||||
// the future after receiving an authenticated data packet. It's
|
||||
// typically only sent by one side based on how the WireGuard state
|
||||
// machine controls the timer. So, if we are on the receiving end of
|
||||
// that keepalive, de.lastSendExt won't move, assuming there is no
|
||||
// other user-generated traffic. This is one reason why we perform
|
||||
// a more granular check of the last packets in/out time, below, as
|
||||
// a WireGuard keepalive may have fallen somewhere within the
|
||||
// sessionActiveTimeout window. heartbeatForLifetime will also
|
||||
// perform a similar check, and reschedule as necessary.
|
||||
inactiveFor := now.Sub(max(de.lastSendAny, de.lastRecvUDPAny.LoadAtomic()))
|
||||
after := afterInactivityFor - inactiveFor
|
||||
if after < 0 {
|
||||
// shouldn't happen
|
||||
return
|
||||
}
|
||||
de.scheduleHeartbeatForLifetimeLocked(after, heartbeatForLifetimeViaSessionInactive)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
now := mono.Now()
|
||||
udpAddr, _, _ := de.addrForSendLocked(now)
|
||||
if udpAddr.IsValid() {
|
||||
// We have a preferred path. Ping that every 2 seconds.
|
||||
@@ -478,8 +860,8 @@ func (de *endpoint) wantFullPingLocked(now mono.Time) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (de *endpoint) noteActiveLocked() {
|
||||
de.lastSend = mono.Now()
|
||||
func (de *endpoint) noteTxActivityExtTriggerLocked(now mono.Time) {
|
||||
de.lastSendExt = now
|
||||
if de.heartBeatTimer == nil && !de.heartbeatDisabled {
|
||||
de.heartBeatTimer = time.AfterFunc(heartbeatInterval, de.heartbeat)
|
||||
}
|
||||
@@ -536,7 +918,6 @@ func (de *endpoint) discoPing(res *ipnstate.PingResult, size int, cb func(*ipnst
|
||||
de.startDiscoPingLocked(ep, now, pingCLI, size, resCB)
|
||||
}
|
||||
}
|
||||
de.noteActiveLocked()
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -562,7 +943,8 @@ func (de *endpoint) send(buffs [][]byte) error {
|
||||
} else if !udpAddr.IsValid() || now.After(de.trustBestAddrUntil) {
|
||||
de.sendDiscoPingsLocked(now, true)
|
||||
}
|
||||
de.noteActiveLocked()
|
||||
de.noteTxActivityExtTriggerLocked(now)
|
||||
de.lastSendAny = now
|
||||
de.mu.Unlock()
|
||||
|
||||
if !udpAddr.IsValid() && !derpAddr.IsValid() {
|
||||
@@ -606,6 +988,42 @@ func (de *endpoint) send(buffs [][]byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// probeUDPLifetimeCliffDoneLocked is called when a disco
|
||||
// pingHeartbeatForUDPLifetime is being cleaned up. result contains the reason
|
||||
// for the cleanup, txid contains the ping's txid.
|
||||
// probeUDPLifetimeCliffDoneLocked may schedule another
|
||||
// pingHeartbeatForUDPLifetime in the future if there is another cliff remaining
|
||||
// for the current probing cycle.
|
||||
func (de *endpoint) probeUDPLifetimeCliffDoneLocked(result discoPingResult, txid stun.TxID) {
|
||||
p := de.probeUDPLifetime
|
||||
if p == nil || !p.cycleActive || de.probeUDPLifetime.timer != nil || txid != p.lastTxID {
|
||||
// Probing may have been disabled while heartbeats were in flight. This
|
||||
// can also be a duplicate or late arriving result.
|
||||
return
|
||||
}
|
||||
metricUDPLifetimeCliffsCompleted.Add(1)
|
||||
if result != discoPongReceived || p.currentCliff >= len(p.config.Cliffs)-1 {
|
||||
maxCliffIndex := p.currentCliff
|
||||
if result != discoPongReceived {
|
||||
maxCliffIndex = p.currentCliff - 1
|
||||
}
|
||||
var maxCliffDuration time.Duration
|
||||
if maxCliffIndex >= 0 {
|
||||
maxCliffDuration = p.config.Cliffs[maxCliffIndex]
|
||||
}
|
||||
p.cycleCompleteMaxCliffEndpointLocked(maxCliffIndex)
|
||||
de.c.dlogf("[v1] magicsock: disco: UDP lifetime probe cycle completed max cliff=%v for %v (%v)",
|
||||
maxCliffDuration, de.publicKey.ShortString(), de.discoShort())
|
||||
metricUDPLifetimeCyclesCompleted.Add(1)
|
||||
p.resetCycleEndpointLocked()
|
||||
} else {
|
||||
p.currentCliff++
|
||||
if after, ok := de.maybeProbeUDPLifetimeLocked(); ok {
|
||||
de.scheduleHeartbeatForLifetimeLocked(after, heartbeatForLifetimeViaPongRx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (de *endpoint) discoPingTimeout(txid stun.TxID) {
|
||||
de.mu.Lock()
|
||||
defer de.mu.Unlock()
|
||||
@@ -616,23 +1034,36 @@ func (de *endpoint) discoPingTimeout(txid stun.TxID) {
|
||||
if debugDisco() || !de.bestAddr.IsValid() || mono.Now().After(de.trustBestAddrUntil) {
|
||||
de.c.dlogf("[v1] magicsock: disco: timeout waiting for pong %x from %v (%v, %v)", txid[:6], sp.to, de.publicKey.ShortString(), de.discoShort())
|
||||
}
|
||||
de.removeSentDiscoPingLocked(txid, sp)
|
||||
de.removeSentDiscoPingLocked(txid, sp, discoPingTimedOut)
|
||||
}
|
||||
|
||||
// forgetDiscoPing is called by a timer when a ping either fails to send or
|
||||
// has taken too long to get a pong reply.
|
||||
// forgetDiscoPing is called when a ping fails to send.
|
||||
func (de *endpoint) forgetDiscoPing(txid stun.TxID) {
|
||||
de.mu.Lock()
|
||||
defer de.mu.Unlock()
|
||||
if sp, ok := de.sentPing[txid]; ok {
|
||||
de.removeSentDiscoPingLocked(txid, sp)
|
||||
de.removeSentDiscoPingLocked(txid, sp, discoPingFailed)
|
||||
}
|
||||
}
|
||||
|
||||
func (de *endpoint) removeSentDiscoPingLocked(txid stun.TxID, sp sentPing) {
|
||||
// discoPingResult represents the result of an attempted disco ping send
|
||||
// operation.
|
||||
type discoPingResult int
|
||||
|
||||
const (
|
||||
discoPingResultUnknown discoPingResult = iota
|
||||
discoPingFailed
|
||||
discoPingTimedOut
|
||||
discoPongReceived
|
||||
)
|
||||
|
||||
func (de *endpoint) removeSentDiscoPingLocked(txid stun.TxID, sp sentPing, result discoPingResult) {
|
||||
// Stop the timer for the case where sendPing failed to write to UDP.
|
||||
// In the case of a timer already having fired, this is a no-op:
|
||||
sp.timer.Stop()
|
||||
if sp.purpose == pingHeartbeatForUDPLifetime {
|
||||
de.probeUDPLifetimeCliffDoneLocked(result, txid)
|
||||
}
|
||||
delete(de.sentPing, txid)
|
||||
}
|
||||
|
||||
@@ -685,6 +1116,11 @@ const (
|
||||
// pingCLI means that the user is running "tailscale ping"
|
||||
// from the CLI. These types of pings can go over DERP.
|
||||
pingCLI
|
||||
|
||||
// pingHeartbeatForUDPLifetime means that the purpose of a ping was to
|
||||
// discover whether the UDP path was still active through any and all
|
||||
// stateful middleboxes involved.
|
||||
pingHeartbeatForUDPLifetime
|
||||
)
|
||||
|
||||
// startDiscoPingLocked sends a disco ping to ep in a separate goroutine. resCB,
|
||||
@@ -731,6 +1167,10 @@ func (de *endpoint) startDiscoPingLocked(ep netip.AddrPort, now mono.Time, purpo
|
||||
if purpose == pingHeartbeat {
|
||||
logLevel = discoVerboseLog
|
||||
}
|
||||
if purpose == pingCLI {
|
||||
de.noteTxActivityExtTriggerLocked(now)
|
||||
}
|
||||
de.lastSendAny = now
|
||||
for _, s := range sizes {
|
||||
txid := stun.NewTxID()
|
||||
de.sentPing[txid] = sentPing{
|
||||
@@ -741,6 +1181,9 @@ func (de *endpoint) startDiscoPingLocked(ep netip.AddrPort, now mono.Time, purpo
|
||||
resCB: resCB,
|
||||
size: s,
|
||||
}
|
||||
if purpose == pingHeartbeatForUDPLifetime && de.probeUDPLifetime != nil {
|
||||
de.probeUDPLifetime.lastTxID = txid
|
||||
}
|
||||
go de.sendDiscoPing(ep, epDisco.key, txid, s, logLevel)
|
||||
}
|
||||
|
||||
@@ -864,7 +1307,7 @@ func (de *endpoint) setLastPing(ipp netip.AddrPort, now mono.Time) {
|
||||
|
||||
// updateFromNode updates the endpoint based on a tailcfg.Node from a NetMap
|
||||
// update.
|
||||
func (de *endpoint) updateFromNode(n tailcfg.NodeView, heartbeatDisabled bool) {
|
||||
func (de *endpoint) updateFromNode(n tailcfg.NodeView, heartbeatDisabled bool, probeUDPLifetimeEnabled bool) {
|
||||
if !n.Valid() {
|
||||
panic("nil node when updating endpoint")
|
||||
}
|
||||
@@ -872,6 +1315,11 @@ func (de *endpoint) updateFromNode(n tailcfg.NodeView, heartbeatDisabled bool) {
|
||||
defer de.mu.Unlock()
|
||||
|
||||
de.heartbeatDisabled = heartbeatDisabled
|
||||
if probeUDPLifetimeEnabled {
|
||||
de.setProbeUDPLifetimeConfigLocked(defaultProbeUDPLifetimeConfig)
|
||||
} else {
|
||||
de.setProbeUDPLifetimeConfigLocked(nil)
|
||||
}
|
||||
de.expired = n.Expired()
|
||||
|
||||
epDisco := de.disco.Load()
|
||||
@@ -1009,7 +1457,7 @@ func (de *endpoint) addCandidateEndpoint(ep netip.AddrPort, forRxPingTxID stun.T
|
||||
//
|
||||
// de.mu must be held.
|
||||
func (de *endpoint) clearBestAddrLocked() {
|
||||
de.bestAddr = addrQuality{}
|
||||
de.setBestAddrLocked(addrQuality{})
|
||||
de.bestAddrAt = 0
|
||||
de.trustBestAddrUntil = 0
|
||||
}
|
||||
@@ -1093,7 +1541,7 @@ func (de *endpoint) handlePongConnLocked(m *disco.Pong, di *discoInfo, src netip
|
||||
return false
|
||||
}
|
||||
knownTxID = true // for naked returns below
|
||||
de.removeSentDiscoPingLocked(m.TxID, sp)
|
||||
de.removeSentDiscoPingLocked(m.TxID, sp, discoPongReceived)
|
||||
|
||||
pktLen := int(pingSizeToPktLen(sp.size, sp.to.Addr().Is6()))
|
||||
if sp.size != 0 {
|
||||
@@ -1124,7 +1572,7 @@ func (de *endpoint) handlePongConnLocked(m *disco.Pong, di *discoInfo, src netip
|
||||
})
|
||||
}
|
||||
|
||||
if sp.purpose != pingHeartbeat {
|
||||
if sp.purpose != pingHeartbeat && sp.purpose != pingHeartbeatForUDPLifetime {
|
||||
de.c.dlogf("[v1] magicsock: disco: %v<-%v (%v, %v) got pong tx=%x latency=%v pktlen=%v pong.src=%v%v", de.c.discoShort, de.discoShort(), de.publicKey.ShortString(), src, m.TxID[:6], latency.Round(time.Millisecond), pktLen, m.Src, logger.ArgWriter(func(bw *bufio.Writer) {
|
||||
if sp.to != src {
|
||||
fmt.Fprintf(bw, " ping.to=%v", sp.to)
|
||||
@@ -1152,7 +1600,7 @@ func (de *endpoint) handlePongConnLocked(m *disco.Pong, di *discoInfo, src netip
|
||||
From: de.bestAddr,
|
||||
To: thisPong,
|
||||
})
|
||||
de.bestAddr = thisPong
|
||||
de.setBestAddrLocked(thisPong)
|
||||
}
|
||||
if de.bestAddr.AddrPort == thisPong.AddrPort {
|
||||
de.debugUpdates.Add(EndpointChange{
|
||||
@@ -1327,13 +1775,13 @@ func (de *endpoint) populatePeerStatus(ps *ipnstate.PeerStatus) {
|
||||
|
||||
ps.Relay = de.c.derpRegionCodeOfIDLocked(int(de.derpAddr.Port()))
|
||||
|
||||
if de.lastSend.IsZero() {
|
||||
if de.lastSendExt.IsZero() {
|
||||
return
|
||||
}
|
||||
|
||||
now := mono.Now()
|
||||
ps.LastWrite = de.lastSend.WallTime()
|
||||
ps.Active = now.Sub(de.lastSend) < sessionActiveTimeout
|
||||
ps.LastWrite = de.lastSendExt.WallTime()
|
||||
ps.Active = now.Sub(de.lastSendExt) < sessionActiveTimeout
|
||||
|
||||
if udpAddr, derpAddr, _ := de.addrForSendLocked(now); udpAddr.IsValid() && !derpAddr.IsValid() {
|
||||
ps.CurAddr = udpAddr.String()
|
||||
@@ -1372,7 +1820,7 @@ func (de *endpoint) stopAndReset() {
|
||||
// DERP-only endpoint. It does not stop the endpoint's heartbeat
|
||||
// timer, if one is running.
|
||||
func (de *endpoint) resetLocked() {
|
||||
de.lastSend = 0
|
||||
de.lastSendExt = 0
|
||||
de.lastFullPing = 0
|
||||
de.clearBestAddrLocked()
|
||||
for _, es := range de.endpointState {
|
||||
@@ -1380,9 +1828,10 @@ func (de *endpoint) resetLocked() {
|
||||
}
|
||||
if !de.isWireguardOnly {
|
||||
for txid, sp := range de.sentPing {
|
||||
de.removeSentDiscoPingLocked(txid, sp)
|
||||
de.removeSentDiscoPingLocked(txid, sp, discoPingResultUnknown)
|
||||
}
|
||||
}
|
||||
de.probeUDPLifetime.resetCycleEndpointLocked()
|
||||
}
|
||||
|
||||
func (de *endpoint) numStopAndReset() int64 {
|
||||
|
||||
326
wgengine/magicsock/endpoint_test.go
Normal file
326
wgengine/magicsock/endpoint_test.go
Normal file
@@ -0,0 +1,326 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package magicsock
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dsnet/try"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
func TestProbeUDPLifetimeConfig_Equals(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a *ProbeUDPLifetimeConfig
|
||||
b *ProbeUDPLifetimeConfig
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
"both sides nil",
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"equal pointers",
|
||||
defaultProbeUDPLifetimeConfig,
|
||||
defaultProbeUDPLifetimeConfig,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"a nil",
|
||||
nil,
|
||||
&ProbeUDPLifetimeConfig{
|
||||
Cliffs: []time.Duration{time.Second},
|
||||
CycleCanStartEvery: time.Hour,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"b nil",
|
||||
&ProbeUDPLifetimeConfig{
|
||||
Cliffs: []time.Duration{time.Second},
|
||||
CycleCanStartEvery: time.Hour,
|
||||
},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Cliffs unequal",
|
||||
&ProbeUDPLifetimeConfig{
|
||||
Cliffs: []time.Duration{time.Second},
|
||||
CycleCanStartEvery: time.Hour,
|
||||
},
|
||||
&ProbeUDPLifetimeConfig{
|
||||
Cliffs: []time.Duration{time.Second * 2},
|
||||
CycleCanStartEvery: time.Hour,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"CycleCanStartEvery unequal",
|
||||
&ProbeUDPLifetimeConfig{
|
||||
Cliffs: []time.Duration{time.Second},
|
||||
CycleCanStartEvery: time.Hour,
|
||||
},
|
||||
&ProbeUDPLifetimeConfig{
|
||||
Cliffs: []time.Duration{time.Second},
|
||||
CycleCanStartEvery: time.Hour * 2,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.a.Equals(tt.b); got != tt.want {
|
||||
t.Errorf("Equals() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProbeUDPLifetimeConfig_Valid(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
p *ProbeUDPLifetimeConfig
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
"default config valid",
|
||||
defaultProbeUDPLifetimeConfig,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"no cliffs",
|
||||
&ProbeUDPLifetimeConfig{
|
||||
CycleCanStartEvery: time.Hour,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"zero CycleCanStartEvery",
|
||||
&ProbeUDPLifetimeConfig{
|
||||
Cliffs: []time.Duration{time.Second * 10},
|
||||
CycleCanStartEvery: 0,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"cliff too small",
|
||||
&ProbeUDPLifetimeConfig{
|
||||
Cliffs: []time.Duration{min(udpLifetimeProbeCliffSlack*2, heartbeatInterval)},
|
||||
CycleCanStartEvery: time.Hour,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"duplicate Cliffs values",
|
||||
&ProbeUDPLifetimeConfig{
|
||||
Cliffs: []time.Duration{time.Second * 2, time.Second * 2},
|
||||
CycleCanStartEvery: time.Hour,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Cliffs not ascending",
|
||||
&ProbeUDPLifetimeConfig{
|
||||
Cliffs: []time.Duration{time.Second * 2, time.Second * 1},
|
||||
CycleCanStartEvery: time.Hour,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.p.Valid(); got != tt.want {
|
||||
t.Errorf("Valid() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_endpoint_maybeProbeUDPLifetimeLocked(t *testing.T) {
|
||||
var lower, higher key.DiscoPublic
|
||||
a := key.NewDisco().Public()
|
||||
b := key.NewDisco().Public()
|
||||
if a.String() < b.String() {
|
||||
lower = a
|
||||
higher = b
|
||||
} else {
|
||||
lower = b
|
||||
higher = a
|
||||
}
|
||||
addr := addrQuality{AddrPort: try.E1[netip.AddrPort](netip.ParseAddrPort("1.1.1.1:1"))}
|
||||
newProbeUDPLifetime := func() *probeUDPLifetime {
|
||||
return &probeUDPLifetime{
|
||||
config: *defaultProbeUDPLifetimeConfig,
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
localDisco key.DiscoPublic
|
||||
remoteDisco *key.DiscoPublic
|
||||
probeUDPLifetimeFn func() *probeUDPLifetime
|
||||
bestAddr addrQuality
|
||||
wantAfterInactivityForFn func(*probeUDPLifetime) time.Duration
|
||||
wantMaybe bool
|
||||
}{
|
||||
{
|
||||
"nil probeUDPLifetime",
|
||||
higher,
|
||||
&lower,
|
||||
func() *probeUDPLifetime {
|
||||
return nil
|
||||
},
|
||||
addr,
|
||||
func(lifetime *probeUDPLifetime) time.Duration {
|
||||
return 0
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"local higher disco key",
|
||||
higher,
|
||||
&lower,
|
||||
newProbeUDPLifetime,
|
||||
addr,
|
||||
func(lifetime *probeUDPLifetime) time.Duration {
|
||||
return 0
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"remote no disco key",
|
||||
higher,
|
||||
nil,
|
||||
newProbeUDPLifetime,
|
||||
addr,
|
||||
func(lifetime *probeUDPLifetime) time.Duration {
|
||||
return 0
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid bestAddr",
|
||||
lower,
|
||||
&higher,
|
||||
newProbeUDPLifetime,
|
||||
addrQuality{},
|
||||
func(lifetime *probeUDPLifetime) time.Duration {
|
||||
return 0
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"cycle started too recently",
|
||||
lower,
|
||||
&higher,
|
||||
func() *probeUDPLifetime {
|
||||
l := newProbeUDPLifetime()
|
||||
l.cycleActive = false
|
||||
l.cycleStartedAt = time.Now()
|
||||
return l
|
||||
},
|
||||
addr,
|
||||
func(lifetime *probeUDPLifetime) time.Duration {
|
||||
return 0
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"maybe cliff 0 cycle not active",
|
||||
lower,
|
||||
&higher,
|
||||
func() *probeUDPLifetime {
|
||||
l := newProbeUDPLifetime()
|
||||
l.cycleActive = false
|
||||
l.cycleStartedAt = time.Now().Add(-l.config.CycleCanStartEvery).Add(-time.Second)
|
||||
return l
|
||||
},
|
||||
addr,
|
||||
func(lifetime *probeUDPLifetime) time.Duration {
|
||||
return lifetime.config.Cliffs[0] - udpLifetimeProbeCliffSlack
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"maybe cliff 0",
|
||||
lower,
|
||||
&higher,
|
||||
func() *probeUDPLifetime {
|
||||
l := newProbeUDPLifetime()
|
||||
l.cycleActive = true
|
||||
l.currentCliff = 0
|
||||
return l
|
||||
},
|
||||
addr,
|
||||
func(lifetime *probeUDPLifetime) time.Duration {
|
||||
return lifetime.config.Cliffs[0] - udpLifetimeProbeCliffSlack
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"maybe cliff 1",
|
||||
lower,
|
||||
&higher,
|
||||
func() *probeUDPLifetime {
|
||||
l := newProbeUDPLifetime()
|
||||
l.cycleActive = true
|
||||
l.currentCliff = 1
|
||||
return l
|
||||
},
|
||||
addr,
|
||||
func(lifetime *probeUDPLifetime) time.Duration {
|
||||
return lifetime.config.Cliffs[1] - udpLifetimeProbeCliffSlack
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"maybe cliff 2",
|
||||
lower,
|
||||
&higher,
|
||||
func() *probeUDPLifetime {
|
||||
l := newProbeUDPLifetime()
|
||||
l.cycleActive = true
|
||||
l.currentCliff = 2
|
||||
return l
|
||||
},
|
||||
addr,
|
||||
func(lifetime *probeUDPLifetime) time.Duration {
|
||||
return lifetime.config.Cliffs[2] - udpLifetimeProbeCliffSlack
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
de := &endpoint{
|
||||
c: &Conn{
|
||||
discoPublic: tt.localDisco,
|
||||
},
|
||||
bestAddr: tt.bestAddr,
|
||||
}
|
||||
if tt.remoteDisco != nil {
|
||||
remote := &endpointDisco{
|
||||
key: *tt.remoteDisco,
|
||||
}
|
||||
de.disco.Store(remote)
|
||||
}
|
||||
p := tt.probeUDPLifetimeFn()
|
||||
de.probeUDPLifetime = p
|
||||
gotAfterInactivityFor, gotMaybe := de.maybeProbeUDPLifetimeLocked()
|
||||
wantAfterInactivityFor := tt.wantAfterInactivityForFn(p)
|
||||
if gotAfterInactivityFor != wantAfterInactivityFor {
|
||||
t.Errorf("maybeProbeUDPLifetimeLocked() gotAfterInactivityFor = %v, want %v", gotAfterInactivityFor, wantAfterInactivityFor)
|
||||
}
|
||||
if gotMaybe != tt.wantMaybe {
|
||||
t.Errorf("maybeProbeUDPLifetimeLocked() gotMaybe = %v, want %v", gotMaybe, tt.wantMaybe)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -141,6 +141,8 @@ type Conn struct {
|
||||
|
||||
silentDiscoOn atomic.Bool // whether silent disco is enabled
|
||||
|
||||
probeUDPLifetimeOn atomic.Bool // whether probing of UDP lifetime is enabled
|
||||
|
||||
// noV4Send is whether IPv4 UDP is known to be unable to transmit
|
||||
// at all. This could happen if the socket is in an invalid state
|
||||
// (as can happen on darwin after a network link status change).
|
||||
@@ -749,7 +751,7 @@ func (c *Conn) LastRecvActivityOfNodeKey(nk key.NodePublic) string {
|
||||
if !ok {
|
||||
return "never"
|
||||
}
|
||||
saw := de.lastRecv.LoadAtomic()
|
||||
saw := de.lastRecvWG.LoadAtomic()
|
||||
if saw == 0 {
|
||||
return "never"
|
||||
}
|
||||
@@ -1236,7 +1238,9 @@ func (c *Conn) receiveIP(b []byte, ipp netip.AddrPort, cache *ippEndpointCache)
|
||||
cache.gen = de.numStopAndReset()
|
||||
ep = de
|
||||
}
|
||||
ep.noteRecvActivity(ipp)
|
||||
now := mono.Now()
|
||||
ep.lastRecvUDPAny.StoreAtomic(now)
|
||||
ep.noteRecvActivity(ipp, now)
|
||||
if stats := c.stats.Load(); stats != nil {
|
||||
stats.UpdateRxPhysical(ep.nodeAddr, ipp, len(b))
|
||||
}
|
||||
@@ -1317,7 +1321,7 @@ func (c *Conn) sendDiscoMessage(dst netip.AddrPort, dstKey key.NodePublic, dstDi
|
||||
} else if err == nil {
|
||||
// Can't send. (e.g. no IPv6 locally)
|
||||
} else {
|
||||
if !c.networkDown() {
|
||||
if !c.networkDown() && pmtuShouldLogDiscoTxErr(m, err) {
|
||||
c.logf("magicsock: disco: failed to send %v to %v: %v", disco.MessageSummary(m), dst, err)
|
||||
}
|
||||
}
|
||||
@@ -1383,6 +1387,15 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
|
||||
return
|
||||
}
|
||||
|
||||
isDERP := src.Addr() == tailcfg.DerpMagicIPAddr
|
||||
if !isDERP {
|
||||
// Record receive time for UDP transport packets.
|
||||
pi, ok := c.peerMap.byIPPort[src]
|
||||
if ok {
|
||||
pi.ep.lastRecvUDPAny.StoreAtomic(mono.Now())
|
||||
}
|
||||
}
|
||||
|
||||
// We're now reasonably sure we're expecting communication from
|
||||
// this peer, do the heavy crypto lifting to see what they want.
|
||||
//
|
||||
@@ -1430,7 +1443,6 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
|
||||
return
|
||||
}
|
||||
|
||||
isDERP := src.Addr() == tailcfg.DerpMagicIPAddr
|
||||
if isDERP {
|
||||
metricRecvDiscoDERP.Add(1)
|
||||
} else {
|
||||
@@ -1817,11 +1829,13 @@ func debugRingBufferSize(numPeers int) int {
|
||||
// They might be set by envknob and/or controlknob.
|
||||
// The value is comparable.
|
||||
type debugFlags struct {
|
||||
heartbeatDisabled bool
|
||||
heartbeatDisabled bool
|
||||
probeUDPLifetimeOn bool
|
||||
}
|
||||
|
||||
func (c *Conn) debugFlagsLocked() (f debugFlags) {
|
||||
f.heartbeatDisabled = debugEnableSilentDisco() || c.silentDiscoOn.Load()
|
||||
f.probeUDPLifetimeOn = c.probeUDPLifetimeOn.Load()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1846,6 +1860,19 @@ func (c *Conn) SilentDisco() bool {
|
||||
return flags.heartbeatDisabled
|
||||
}
|
||||
|
||||
// SetProbeUDPLifetime toggles probing of UDP lifetime based on v.
|
||||
func (c *Conn) SetProbeUDPLifetime(v bool) {
|
||||
old := c.probeUDPLifetimeOn.Swap(v)
|
||||
if old == v {
|
||||
return
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.peerMap.forEachEndpoint(func(ep *endpoint) {
|
||||
ep.setProbeUDPLifetimeOn(v)
|
||||
})
|
||||
}
|
||||
|
||||
// SetNetworkMap is called when the control client gets a new network
|
||||
// map from the control server. It must always be non-nil.
|
||||
//
|
||||
@@ -1876,7 +1903,8 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
|
||||
if nodesEqual(priorPeers, curPeers) && c.lastFlags == flags {
|
||||
// The rest of this function is all adjusting state for peers that have
|
||||
// changed. But if the set of peers is equal and the debug flags (for
|
||||
// silent disco) haven't changed, no need to do anything else.
|
||||
// silent disco and probe UDP lifetime) haven't changed, there is no
|
||||
// need to do anything else.
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1927,7 +1955,7 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
|
||||
if epDisco := ep.disco.Load(); epDisco != nil {
|
||||
oldDiscoKey = epDisco.key
|
||||
}
|
||||
ep.updateFromNode(n, flags.heartbeatDisabled)
|
||||
ep.updateFromNode(n, flags.heartbeatDisabled, flags.probeUDPLifetimeOn)
|
||||
c.peerMap.upsertEndpoint(ep, oldDiscoKey) // maybe update discokey mappings in peerMap
|
||||
continue
|
||||
}
|
||||
@@ -1980,7 +2008,7 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
|
||||
c.logEndpointCreated(n)
|
||||
}
|
||||
|
||||
ep.updateFromNode(n, flags.heartbeatDisabled)
|
||||
ep.updateFromNode(n, flags.heartbeatDisabled, flags.probeUDPLifetimeOn)
|
||||
c.peerMap.upsertEndpoint(ep, key.DiscoPublic{})
|
||||
}
|
||||
|
||||
@@ -2947,8 +2975,34 @@ var (
|
||||
// received an peer MTU probe response for a given MTU size.
|
||||
// TODO: add proper support for label maps in clientmetrics
|
||||
metricRecvDiscoPeerMTUProbesByMTU syncs.Map[string, *clientmetric.Metric]
|
||||
|
||||
// metricUDPLifetime* metrics pertain to UDP lifetime probing, see type
|
||||
// probeUDPLifetime. These metrics assume a static/default configuration for
|
||||
// probing (defaultProbeUDPLifetimeConfig) until we disseminate
|
||||
// ProbeUDPLifetimeConfig from control, and have lifetime management (GC old
|
||||
// metrics) of clientmetrics or similar.
|
||||
metricUDPLifetimeCliffsScheduled = newUDPLifetimeCounter("magicsock_udp_lifetime_cliffs_scheduled")
|
||||
metricUDPLifetimeCliffsCompleted = newUDPLifetimeCounter("magicsock_udp_lifetime_cliffs_completed")
|
||||
metricUDPLifetimeCliffsMissed = newUDPLifetimeCounter("magicsock_udp_lifetime_cliffs_missed")
|
||||
metricUDPLifetimeCliffsRescheduled = newUDPLifetimeCounter("magicsock_udp_lifetime_cliffs_rescheduled")
|
||||
metricUDPLifetimeCyclesCompleted = newUDPLifetimeCounter("magicsock_udp_lifetime_cycles_completed")
|
||||
metricUDPLifetimeCycleCompleteNoCliffReached = newUDPLifetimeCounter("magicsock_udp_lifetime_cycle_complete_no_cliff_reached")
|
||||
metricUDPLifetimeCycleCompleteAt10sCliff = newUDPLifetimeCounter("magicsock_udp_lifetime_cycle_complete_at_10s_cliff")
|
||||
metricUDPLifetimeCycleCompleteAt30sCliff = newUDPLifetimeCounter("magicsock_udp_lifetime_cycle_complete_at_30s_cliff")
|
||||
metricUDPLifetimeCycleCompleteAt60sCliff = newUDPLifetimeCounter("magicsock_udp_lifetime_cycle_complete_at_60s_cliff")
|
||||
)
|
||||
|
||||
// newUDPLifetimeCounter returns a new *clientmetric.Metric with the provided
|
||||
// name combined with a suffix representing defaultProbeUDPLifetimeConfig.
|
||||
func newUDPLifetimeCounter(name string) *clientmetric.Metric {
|
||||
var sb strings.Builder
|
||||
for _, cliff := range defaultProbeUDPLifetimeConfig.Cliffs {
|
||||
sb.WriteString(fmt.Sprintf("%ds", cliff/time.Second))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("_%ds", defaultProbeUDPLifetimeConfig.CycleCanStartEvery/time.Second))
|
||||
return clientmetric.NewCounter(fmt.Sprintf("%s_%s", name, sb.String()))
|
||||
}
|
||||
|
||||
func getPeerMTUsProbedMetric(mtu tstun.WireMTU) *clientmetric.Metric {
|
||||
key := fmt.Sprintf("magicsock_recv_disco_peer_mtu_probes_by_mtu_%d", mtu)
|
||||
mm, _ := metricRecvDiscoPeerMTUProbesByMTU.LoadOrInit(key, func() *clientmetric.Metric { return clientmetric.NewCounter(key) })
|
||||
|
||||
@@ -1220,15 +1220,15 @@ func Test32bitAlignment(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if off := unsafe.Offsetof(de.lastRecv); off%8 != 0 {
|
||||
t.Fatalf("endpoint.lastRecv is not 8-byte aligned")
|
||||
if off := unsafe.Offsetof(de.lastRecvWG); off%8 != 0 {
|
||||
t.Fatalf("endpoint.lastRecvWG is not 8-byte aligned")
|
||||
}
|
||||
|
||||
de.noteRecvActivity(netip.AddrPort{}) // verify this doesn't panic on 32-bit
|
||||
de.noteRecvActivity(netip.AddrPort{}, mono.Now()) // verify this doesn't panic on 32-bit
|
||||
if called != 1 {
|
||||
t.Fatal("expected call to noteRecvActivity")
|
||||
}
|
||||
de.noteRecvActivity(netip.AddrPort{})
|
||||
de.noteRecvActivity(netip.AddrPort{}, mono.Now())
|
||||
if called != 1 {
|
||||
t.Error("expected no second call to noteRecvActivity")
|
||||
}
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
|
||||
package magicsock
|
||||
|
||||
import "tailscale.com/net/tstun"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/disco"
|
||||
"tailscale.com/net/tstun"
|
||||
)
|
||||
|
||||
// Peer path MTU routines shared by platforms that implement it.
|
||||
|
||||
@@ -110,3 +116,15 @@ func (c *Conn) UpdatePMTUD() {
|
||||
c.peerMTUEnabled.Store(newStatus)
|
||||
c.resetEndpointStates()
|
||||
}
|
||||
|
||||
var errEMSGSIZE error = unix.EMSGSIZE
|
||||
|
||||
func pmtuShouldLogDiscoTxErr(m disco.Message, err error) bool {
|
||||
// Large disco.Ping packets used to probe path MTU may result in
|
||||
// an EMSGSIZE error fairly regularly which can pollute logs.
|
||||
p, ok := m.(*disco.Ping)
|
||||
if !ok || p.Padding == 0 || !errors.Is(err, errEMSGSIZE) || debugPMTUD() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
package magicsock
|
||||
|
||||
import "tailscale.com/disco"
|
||||
|
||||
func (c *Conn) DontFragSetting() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
@@ -19,3 +21,7 @@ func (c *Conn) PeerMTUEnabled() bool {
|
||||
|
||||
func (c *Conn) UpdatePMTUD() {
|
||||
}
|
||||
|
||||
func pmtuShouldLogDiscoTxErr(m disco.Message, err error) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"expvar"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
@@ -36,6 +38,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/metrics"
|
||||
"tailscale.com/net/dns"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/packet"
|
||||
@@ -1059,7 +1062,7 @@ func (ns *Impl) acceptUDP(r *udp.ForwarderRequest) {
|
||||
return // Only MagicDNS traffic runs on the service IPs for now.
|
||||
}
|
||||
|
||||
c := gonet.NewUDPConn(ns.ipstack, &wq, ep)
|
||||
c := gonet.NewUDPConn(&wq, ep)
|
||||
go ns.handleMagicDNSUDP(srcAddr, c)
|
||||
return
|
||||
}
|
||||
@@ -1071,12 +1074,12 @@ func (ns *Impl) acceptUDP(r *udp.ForwarderRequest) {
|
||||
ep.Close()
|
||||
return
|
||||
}
|
||||
go h(gonet.NewUDPConn(ns.ipstack, &wq, ep))
|
||||
go h(gonet.NewUDPConn(&wq, ep))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c := gonet.NewUDPConn(ns.ipstack, &wq, ep)
|
||||
c := gonet.NewUDPConn(&wq, ep)
|
||||
go ns.forwardUDP(c, srcAddr, dstAddr)
|
||||
}
|
||||
|
||||
@@ -1259,3 +1262,151 @@ func ipPortOfNetstackAddr(a tcpip.Address, port uint16) (ipp netip.AddrPort, ok
|
||||
}
|
||||
return netip.AddrPort{}, false
|
||||
}
|
||||
|
||||
func readStatCounter(sc *tcpip.StatCounter) int64 {
|
||||
vv := sc.Value()
|
||||
if vv > math.MaxInt64 {
|
||||
return int64(math.MaxInt64)
|
||||
}
|
||||
return int64(vv)
|
||||
}
|
||||
|
||||
// ExpVar returns an expvar variable suitable for registering with expvar.Publish.
|
||||
func (ns *Impl) ExpVar() expvar.Var {
|
||||
m := new(metrics.Set)
|
||||
|
||||
// Global metrics
|
||||
stats := ns.ipstack.Stats()
|
||||
m.Set("gauge_dropped_packets", expvar.Func(func() any {
|
||||
return readStatCounter(stats.DroppedPackets)
|
||||
}))
|
||||
|
||||
// IP statistics
|
||||
ipStats := ns.ipstack.Stats().IP
|
||||
ipMetrics := []struct {
|
||||
name string
|
||||
field *tcpip.StatCounter
|
||||
}{
|
||||
{"packets_received", ipStats.PacketsReceived},
|
||||
{"valid_packets_received", ipStats.ValidPacketsReceived},
|
||||
{"disabled_packets_received", ipStats.DisabledPacketsReceived},
|
||||
{"invalid_destination_addresses_received", ipStats.InvalidDestinationAddressesReceived},
|
||||
{"invalid_source_addresses_received", ipStats.InvalidSourceAddressesReceived},
|
||||
{"packets_delivered", ipStats.PacketsDelivered},
|
||||
{"packets_sent", ipStats.PacketsSent},
|
||||
{"outgoing_packet_errors", ipStats.OutgoingPacketErrors},
|
||||
{"malformed_packets_received", ipStats.MalformedPacketsReceived},
|
||||
{"malformed_fragments_received", ipStats.MalformedFragmentsReceived},
|
||||
{"iptables_prerouting_dropped", ipStats.IPTablesPreroutingDropped},
|
||||
{"iptables_input_dropped", ipStats.IPTablesInputDropped},
|
||||
{"iptables_forward_dropped", ipStats.IPTablesForwardDropped},
|
||||
{"iptables_output_dropped", ipStats.IPTablesOutputDropped},
|
||||
{"iptables_postrouting_dropped", ipStats.IPTablesPostroutingDropped},
|
||||
{"option_timestamp_received", ipStats.OptionTimestampReceived},
|
||||
{"option_record_route_received", ipStats.OptionRecordRouteReceived},
|
||||
{"option_router_alert_received", ipStats.OptionRouterAlertReceived},
|
||||
{"option_unknown_received", ipStats.OptionUnknownReceived},
|
||||
}
|
||||
for _, metric := range ipMetrics {
|
||||
metric := metric
|
||||
m.Set("gauge_ip_"+metric.name, expvar.Func(func() any {
|
||||
return readStatCounter(metric.field)
|
||||
}))
|
||||
}
|
||||
|
||||
// IP forwarding statistics
|
||||
fwdStats := ipStats.Forwarding
|
||||
fwdMetrics := []struct {
|
||||
name string
|
||||
field *tcpip.StatCounter
|
||||
}{
|
||||
{"unrouteable", fwdStats.Unrouteable},
|
||||
{"exhausted_ttl", fwdStats.ExhaustedTTL},
|
||||
{"initializing_source", fwdStats.InitializingSource},
|
||||
{"link_local_source", fwdStats.LinkLocalSource},
|
||||
{"link_local_destination", fwdStats.LinkLocalDestination},
|
||||
{"packet_too_big", fwdStats.PacketTooBig},
|
||||
{"host_unreachable", fwdStats.HostUnreachable},
|
||||
{"extension_header_problem", fwdStats.ExtensionHeaderProblem},
|
||||
{"unexpected_multicast_input_interface", fwdStats.UnexpectedMulticastInputInterface},
|
||||
{"unknown_output_endpoint", fwdStats.UnknownOutputEndpoint},
|
||||
{"no_multicast_pending_queue_buffer_space", fwdStats.NoMulticastPendingQueueBufferSpace},
|
||||
{"outgoing_device_no_buffer_space", fwdStats.OutgoingDeviceNoBufferSpace},
|
||||
{"errors", fwdStats.Errors},
|
||||
}
|
||||
for _, metric := range fwdMetrics {
|
||||
metric := metric
|
||||
m.Set("gauge_ip_forward_"+metric.name, expvar.Func(func() any {
|
||||
return readStatCounter(metric.field)
|
||||
}))
|
||||
}
|
||||
|
||||
// TCP metrics
|
||||
tcpStats := ns.ipstack.Stats().TCP
|
||||
tcpMetrics := []struct {
|
||||
name string
|
||||
field *tcpip.StatCounter
|
||||
}{
|
||||
{"active_connection_openings", tcpStats.ActiveConnectionOpenings},
|
||||
{"passive_connection_openings", tcpStats.PassiveConnectionOpenings},
|
||||
{"current_established", tcpStats.CurrentEstablished},
|
||||
{"current_connected", tcpStats.CurrentConnected},
|
||||
{"established_resets", tcpStats.EstablishedResets},
|
||||
{"established_closed", tcpStats.EstablishedClosed},
|
||||
{"established_timeout", tcpStats.EstablishedTimedout},
|
||||
{"listen_overflow_syn_drop", tcpStats.ListenOverflowSynDrop},
|
||||
{"listen_overflow_ack_drop", tcpStats.ListenOverflowAckDrop},
|
||||
{"listen_overflow_syn_cookie_sent", tcpStats.ListenOverflowSynCookieSent},
|
||||
{"listen_overflow_syn_cookie_rcvd", tcpStats.ListenOverflowSynCookieRcvd},
|
||||
{"listen_overflow_invalid_syn_cookie_rcvd", tcpStats.ListenOverflowInvalidSynCookieRcvd},
|
||||
{"failed_connection_attempts", tcpStats.FailedConnectionAttempts},
|
||||
{"valid_segments_received", tcpStats.ValidSegmentsReceived},
|
||||
{"invalid_segments_received", tcpStats.InvalidSegmentsReceived},
|
||||
{"segments_sent", tcpStats.SegmentsSent},
|
||||
{"segment_send_errors", tcpStats.SegmentSendErrors},
|
||||
{"resets_sent", tcpStats.ResetsSent},
|
||||
{"resets_received", tcpStats.ResetsReceived},
|
||||
{"retransmits", tcpStats.Retransmits},
|
||||
{"fast_recovery", tcpStats.FastRecovery},
|
||||
{"sack_recovery", tcpStats.SACKRecovery},
|
||||
{"tlp_recovery", tcpStats.TLPRecovery},
|
||||
{"slow_start_retransmits", tcpStats.SlowStartRetransmits},
|
||||
{"fast_retransmit", tcpStats.FastRetransmit},
|
||||
{"timeouts", tcpStats.Timeouts},
|
||||
{"checksum_errors", tcpStats.ChecksumErrors},
|
||||
{"failed_port_reservations", tcpStats.FailedPortReservations},
|
||||
{"segments_acked_with_dsack", tcpStats.SegmentsAckedWithDSACK},
|
||||
{"spurious_recovery", tcpStats.SpuriousRecovery},
|
||||
{"spurious_rto_recovery", tcpStats.SpuriousRTORecovery},
|
||||
{"gauge_tcp_forward_max_in_flight_drop", tcpStats.ForwardMaxInFlightDrop},
|
||||
}
|
||||
for _, metric := range tcpMetrics {
|
||||
metric := metric
|
||||
m.Set("gauge_tcp_"+metric.name, expvar.Func(func() any {
|
||||
return readStatCounter(metric.field)
|
||||
}))
|
||||
}
|
||||
|
||||
// UDP metrics
|
||||
udpStats := ns.ipstack.Stats().UDP
|
||||
udpMetrics := []struct {
|
||||
name string
|
||||
field *tcpip.StatCounter
|
||||
}{
|
||||
{"packets_received", udpStats.PacketsReceived},
|
||||
{"unknown_port_errors", udpStats.UnknownPortErrors},
|
||||
{"receive_buffer_errors", udpStats.ReceiveBufferErrors},
|
||||
{"malformed_packets_received", udpStats.MalformedPacketsReceived},
|
||||
{"packets_sent", udpStats.PacketsSent},
|
||||
{"packet_send_errors", udpStats.PacketSendErrors},
|
||||
{"checksum_errors", udpStats.ChecksumErrors},
|
||||
}
|
||||
for _, metric := range udpMetrics {
|
||||
metric := metric
|
||||
m.Set("gauge_udp_"+metric.name, expvar.Func(func() any {
|
||||
return readStatCounter(metric.field)
|
||||
}))
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -387,3 +387,4 @@ centauri
|
||||
becrux
|
||||
godzilla
|
||||
sirius
|
||||
vector
|
||||
|
||||
Reference in New Issue
Block a user