Update dependencies
This commit is contained in:
149
vendor/tailscale.com/net/bakedroots/bakedroots.go
generated
vendored
Normal file
149
vendor/tailscale.com/net/bakedroots/bakedroots.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package bakedroots contains WebPKI CA roots we bake into the tailscaled binary,
|
||||
// lest the system's CA roots be missing them (or entirely empty).
|
||||
package bakedroots
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"sync"
|
||||
|
||||
"tailscale.com/util/testenv"
|
||||
)
|
||||
|
||||
// Get returns the baked-in roots.
|
||||
//
|
||||
// As of 2025-01-21, this includes only the LetsEncrypt ISRG Root X1 root.
|
||||
func Get() *x509.CertPool {
|
||||
roots.once.Do(func() {
|
||||
roots.parsePEM(append(
|
||||
[]byte(letsEncryptX1),
|
||||
letsEncryptX2...,
|
||||
))
|
||||
})
|
||||
return roots.p
|
||||
}
|
||||
|
||||
// testingTB is a subset of testing.TB needed
|
||||
// to verify the caller isn't in a parallel test.
|
||||
type testingTB interface {
|
||||
// Setenv panics if it's in a parallel test.
|
||||
Setenv(k, v string)
|
||||
}
|
||||
|
||||
// ResetForTest resets the cached roots for testing,
|
||||
// optionally setting them to caPEM if non-nil.
|
||||
func ResetForTest(tb testingTB, caPEM []byte) {
|
||||
if !testenv.InTest() {
|
||||
panic("not in test")
|
||||
}
|
||||
tb.Setenv("ASSERT_NOT_PARALLEL_TEST", "1") // panics if tb's Parallel was called
|
||||
|
||||
roots = rootsOnce{}
|
||||
if caPEM != nil {
|
||||
roots.once.Do(func() { roots.parsePEM(caPEM) })
|
||||
}
|
||||
}
|
||||
|
||||
var roots rootsOnce
|
||||
|
||||
type rootsOnce struct {
|
||||
once sync.Once
|
||||
p *x509.CertPool
|
||||
}
|
||||
|
||||
func (r *rootsOnce) parsePEM(caPEM []byte) {
|
||||
p := x509.NewCertPool()
|
||||
if !p.AppendCertsFromPEM(caPEM) {
|
||||
panic("bogus PEM")
|
||||
}
|
||||
r.p = p
|
||||
}
|
||||
|
||||
/*
|
||||
letsEncryptX1 is the LetsEncrypt X1 root:
|
||||
|
||||
Certificate:
|
||||
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
82:10:cf:b0:d2:40:e3:59:44:63:e0:bb:63:82:8b:00
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X1
|
||||
Validity
|
||||
Not Before: Jun 4 11:04:38 2015 GMT
|
||||
Not After : Jun 4 11:04:38 2035 GMT
|
||||
Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X1
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
RSA Public-Key: (4096 bit)
|
||||
|
||||
We bake it into the binary as a fallback verification root,
|
||||
in case the system we're running on doesn't have it.
|
||||
(Tailscale runs on some ancient devices.)
|
||||
|
||||
To test that this code is working on Debian/Ubuntu:
|
||||
|
||||
$ sudo mv /usr/share/ca-certificates/mozilla/ISRG_Root_X1.crt{,.old}
|
||||
$ sudo update-ca-certificates
|
||||
|
||||
Then restart tailscaled. To also test dnsfallback's use of it, nuke
|
||||
your /etc/resolv.conf and it should still start & run fine.
|
||||
*/
|
||||
const letsEncryptX1 = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
// letsEncryptX2 is the ISRG Root X2.
|
||||
//
|
||||
// Subject: O = Internet Security Research Group, CN = ISRG Root X2
|
||||
// Key type: ECDSA P-384
|
||||
// Validity: until 2035-09-04 (generated 2020-09-04)
|
||||
const letsEncryptX2 = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
|
||||
CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
|
||||
R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
|
||||
MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
|
||||
ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
|
||||
EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
|
||||
+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
|
||||
ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
|
||||
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
|
||||
zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
|
||||
tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
|
||||
/q4AaOeMSQ+2b1tbFfLn
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
36
vendor/tailscale.com/net/captivedetection/captivedetection.go
generated
vendored
36
vendor/tailscale.com/net/captivedetection/captivedetection.go
generated
vendored
@@ -11,6 +11,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
|
||||
// Detector checks whether the system is behind a captive portal.
|
||||
type Detector struct {
|
||||
clock func() time.Time
|
||||
|
||||
// httpClient is the HTTP client that is used for captive portal detection. It is configured
|
||||
// to not follow redirects, have a short timeout and no keep-alive.
|
||||
@@ -52,6 +54,13 @@ func NewDetector(logf logger.Logf) *Detector {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Detector) Now() time.Time {
|
||||
if d.clock != nil {
|
||||
return d.clock()
|
||||
}
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// Timeout is the timeout for captive portal detection requests. Because the captive portal intercepting our requests
|
||||
// is usually located on the LAN, this is a relatively short timeout.
|
||||
const Timeout = 3 * time.Second
|
||||
@@ -136,26 +145,31 @@ func interfaceNameDoesNotNeedCaptiveDetection(ifName string, goos string) bool {
|
||||
func (d *Detector) detectOnInterface(ctx context.Context, ifIndex int, endpoints []Endpoint) bool {
|
||||
defer d.httpClient.CloseIdleConnections()
|
||||
|
||||
d.logf("[v2] %d available captive portal detection endpoints: %v", len(endpoints), endpoints)
|
||||
use := min(len(endpoints), 5)
|
||||
endpoints = endpoints[:use]
|
||||
d.logf("[v2] %d available captive portal detection endpoints; trying %v", len(endpoints), use)
|
||||
|
||||
// We try to detect the captive portal more quickly by making requests to multiple endpoints concurrently.
|
||||
var wg sync.WaitGroup
|
||||
resultCh := make(chan bool, len(endpoints))
|
||||
|
||||
for i, e := range endpoints {
|
||||
if i >= 5 {
|
||||
// Try a maximum of 5 endpoints, break out (returning false) if we run of attempts.
|
||||
break
|
||||
}
|
||||
// Once any goroutine detects a captive portal, we shut down the others.
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
for _, e := range endpoints {
|
||||
wg.Add(1)
|
||||
go func(endpoint Endpoint) {
|
||||
defer wg.Done()
|
||||
found, err := d.verifyCaptivePortalEndpoint(ctx, endpoint, ifIndex)
|
||||
if err != nil {
|
||||
d.logf("[v1] checkCaptivePortalEndpoint failed with endpoint %v: %v", endpoint, err)
|
||||
if ctx.Err() == nil {
|
||||
d.logf("[v1] checkCaptivePortalEndpoint failed with endpoint %v: %v", endpoint, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if found {
|
||||
cancel() // one match is good enough
|
||||
resultCh <- true
|
||||
}
|
||||
}(e)
|
||||
@@ -182,10 +196,16 @@ func (d *Detector) verifyCaptivePortalEndpoint(ctx context.Context, e Endpoint,
|
||||
ctx, cancel := context.WithTimeout(ctx, Timeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", e.URL.String(), nil)
|
||||
u := *e.URL
|
||||
v := u.Query()
|
||||
v.Add("t", strconv.Itoa(int(d.Now().Unix())))
|
||||
u.RawQuery = v.Encode()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
req.Header.Set("Cache-Control", "no-cache, no-store, must-revalidate, no-transform, max-age=0")
|
||||
|
||||
// Attach the Tailscale challenge header if the endpoint supports it. Not all captive portal detection endpoints
|
||||
// support this, so we only attach it if the endpoint does.
|
||||
|
||||
2
vendor/tailscale.com/net/captivedetection/endpoints.go
generated
vendored
2
vendor/tailscale.com/net/captivedetection/endpoints.go
generated
vendored
@@ -89,7 +89,7 @@ func availableEndpoints(derpMap *tailcfg.DERPMap, preferredDERPRegionID int, log
|
||||
// Use the DERP IPs as captive portal detection endpoints. Using IPs is better than hostnames
|
||||
// because they do not depend on DNS resolution.
|
||||
for _, region := range derpMap.Regions {
|
||||
if region.Avoid {
|
||||
if region.Avoid || region.NoMeasureNoHome {
|
||||
continue
|
||||
}
|
||||
for _, node := range region.Nodes {
|
||||
|
||||
22
vendor/tailscale.com/net/connstats/stats.go
generated
vendored
22
vendor/tailscale.com/net/connstats/stats.go
generated
vendored
@@ -131,23 +131,23 @@ func (s *Statistics) updateVirtual(b []byte, receive bool) {
|
||||
s.virtual[conn] = cnts
|
||||
}
|
||||
|
||||
// UpdateTxPhysical updates the counters for a transmitted wireguard packet
|
||||
// UpdateTxPhysical updates the counters for zero or more transmitted wireguard packets.
|
||||
// The src is always a Tailscale IP address, representing some remote peer.
|
||||
// The dst is a remote IP address and port that corresponds
|
||||
// with some physical peer backing the Tailscale IP address.
|
||||
func (s *Statistics) UpdateTxPhysical(src netip.Addr, dst netip.AddrPort, n int) {
|
||||
s.updatePhysical(src, dst, n, false)
|
||||
func (s *Statistics) UpdateTxPhysical(src netip.Addr, dst netip.AddrPort, packets, bytes int) {
|
||||
s.updatePhysical(src, dst, packets, bytes, false)
|
||||
}
|
||||
|
||||
// UpdateRxPhysical updates the counters for a received wireguard packet.
|
||||
// UpdateRxPhysical updates the counters for zero or more received wireguard packets.
|
||||
// The src is always a Tailscale IP address, representing some remote peer.
|
||||
// The dst is a remote IP address and port that corresponds
|
||||
// with some physical peer backing the Tailscale IP address.
|
||||
func (s *Statistics) UpdateRxPhysical(src netip.Addr, dst netip.AddrPort, n int) {
|
||||
s.updatePhysical(src, dst, n, true)
|
||||
func (s *Statistics) UpdateRxPhysical(src netip.Addr, dst netip.AddrPort, packets, bytes int) {
|
||||
s.updatePhysical(src, dst, packets, bytes, true)
|
||||
}
|
||||
|
||||
func (s *Statistics) updatePhysical(src netip.Addr, dst netip.AddrPort, n int, receive bool) {
|
||||
func (s *Statistics) updatePhysical(src netip.Addr, dst netip.AddrPort, packets, bytes int, receive bool) {
|
||||
conn := netlogtype.Connection{Src: netip.AddrPortFrom(src, 0), Dst: dst}
|
||||
|
||||
s.mu.Lock()
|
||||
@@ -157,11 +157,11 @@ func (s *Statistics) updatePhysical(src netip.Addr, dst netip.AddrPort, n int, r
|
||||
return
|
||||
}
|
||||
if receive {
|
||||
cnts.RxPackets++
|
||||
cnts.RxBytes += uint64(n)
|
||||
cnts.RxPackets += uint64(packets)
|
||||
cnts.RxBytes += uint64(bytes)
|
||||
} else {
|
||||
cnts.TxPackets++
|
||||
cnts.TxBytes += uint64(n)
|
||||
cnts.TxPackets += uint64(packets)
|
||||
cnts.TxBytes += uint64(bytes)
|
||||
}
|
||||
s.physical[conn] = cnts
|
||||
}
|
||||
|
||||
52
vendor/tailscale.com/net/dns/direct_linux.go
generated
vendored
52
vendor/tailscale.com/net/dns/direct_linux.go
generated
vendored
@@ -6,21 +6,25 @@ package dns
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/illarion/gonotify/v2"
|
||||
"github.com/illarion/gonotify/v3"
|
||||
"tailscale.com/health"
|
||||
)
|
||||
|
||||
func (m *directManager) runFileWatcher() {
|
||||
ctx, cancel := context.WithCancel(m.ctx)
|
||||
defer cancel()
|
||||
in, err := gonotify.NewInotify(ctx)
|
||||
if err != nil {
|
||||
// Oh well, we tried. This is all best effort for now, to
|
||||
// surface warnings to users.
|
||||
m.logf("dns: inotify new: %v", err)
|
||||
return
|
||||
if err := watchFile(m.ctx, "/etc/", resolvConf, m.checkForFileTrample); err != nil {
|
||||
// This is all best effort for now, so surface warnings to users.
|
||||
m.logf("dns: inotify: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// watchFile sets up an inotify watch for a given directory and
|
||||
// calls the callback function every time a particular file is changed.
|
||||
// The filename should be located in the provided directory.
|
||||
func watchFile(ctx context.Context, dir, filename string, cb func()) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
const events = gonotify.IN_ATTRIB |
|
||||
gonotify.IN_CLOSE_WRITE |
|
||||
@@ -29,30 +33,20 @@ func (m *directManager) runFileWatcher() {
|
||||
gonotify.IN_MODIFY |
|
||||
gonotify.IN_MOVE
|
||||
|
||||
if err := in.AddWatch("/etc/", events); err != nil {
|
||||
m.logf("dns: inotify addwatch: %v", err)
|
||||
return
|
||||
watcher, err := gonotify.NewDirWatcher(ctx, events, dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("NewDirWatcher: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
events, err := in.Read()
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
m.logf("dns: inotify read: %v", err)
|
||||
return
|
||||
}
|
||||
var match bool
|
||||
for _, ev := range events {
|
||||
if ev.Name == resolvConf {
|
||||
match = true
|
||||
break
|
||||
select {
|
||||
case event := <-watcher.C:
|
||||
if event.Name == filename {
|
||||
cb()
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
m.checkForFileTrample()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
72
vendor/tailscale.com/net/dns/manager.go
generated
vendored
72
vendor/tailscale.com/net/dns/manager.go
generated
vendored
@@ -8,6 +8,7 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
@@ -18,7 +19,6 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/net/dns/resolver"
|
||||
@@ -30,10 +30,14 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/slicesx"
|
||||
)
|
||||
|
||||
var (
|
||||
errFullQueue = errors.New("request queue full")
|
||||
// ErrNoDNSConfig is returned by RecompileDNSConfig when the Manager
|
||||
// has no existing DNS configuration.
|
||||
ErrNoDNSConfig = errors.New("no DNS configuration")
|
||||
)
|
||||
|
||||
// maxActiveQueries returns the maximal number of DNS requests that can
|
||||
@@ -90,21 +94,18 @@ func NewManager(logf logger.Logf, oscfg OSConfigurator, health *health.Tracker,
|
||||
}
|
||||
|
||||
// Rate limit our attempts to correct our DNS configuration.
|
||||
// This is done on incoming queries, we don't want to spam it.
|
||||
limiter := rate.NewLimiter(1.0/5.0, 1)
|
||||
|
||||
// This will recompile the DNS config, which in turn will requery the system
|
||||
// DNS settings. The recovery func should triggered only when we are missing
|
||||
// upstream nameservers and require them to forward a query.
|
||||
m.resolver.SetMissingUpstreamRecovery(func() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.config == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if limiter.Allow() {
|
||||
m.logf("DNS resolution failed due to missing upstream nameservers. Recompiling DNS configuration.")
|
||||
m.setLocked(*m.config)
|
||||
m.logf("resolution failed due to missing upstream nameservers. Recompiling DNS configuration.")
|
||||
if err := m.RecompileDNSConfig(); err != nil {
|
||||
m.logf("config recompilation failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -116,6 +117,26 @@ func NewManager(logf logger.Logf, oscfg OSConfigurator, health *health.Tracker,
|
||||
// Resolver returns the Manager's DNS Resolver.
|
||||
func (m *Manager) Resolver() *resolver.Resolver { return m.resolver }
|
||||
|
||||
// RecompileDNSConfig sets the DNS config to the current value, which has
|
||||
// the side effect of re-querying the OS's interface nameservers. This should be used
|
||||
// on platforms where the interface nameservers can change. Darwin, for example,
|
||||
// where the nameservers aren't always available when we process a major interface
|
||||
// change event, or platforms where the nameservers may change while tunnel is up.
|
||||
//
|
||||
// This should be called if it is determined that [OSConfigurator.GetBaseConfig] may
|
||||
// give a better or different result than when [Manager.Set] was last called. The
|
||||
// logic for making that determination is up to the caller.
|
||||
//
|
||||
// It returns [ErrNoDNSConfig] if the [Manager] has no existing DNS configuration.
|
||||
func (m *Manager) RecompileDNSConfig() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.config == nil {
|
||||
return ErrNoDNSConfig
|
||||
}
|
||||
return m.setLocked(*m.config)
|
||||
}
|
||||
|
||||
func (m *Manager) Set(cfg Config) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
@@ -156,11 +177,11 @@ func (m *Manager) setLocked(cfg Config) error {
|
||||
return err
|
||||
}
|
||||
if err := m.os.SetDNS(ocfg); err != nil {
|
||||
m.health.SetDNSOSHealth(err)
|
||||
m.health.SetUnhealthy(osConfigurationSetWarnable, health.Args{health.ArgError: err.Error()})
|
||||
return err
|
||||
}
|
||||
|
||||
m.health.SetDNSOSHealth(nil)
|
||||
m.health.SetHealthy(osConfigurationSetWarnable)
|
||||
m.config = &cfg
|
||||
|
||||
return nil
|
||||
@@ -203,7 +224,7 @@ func compileHostEntries(cfg Config) (hosts []*HostEntry) {
|
||||
if len(hostsMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
hosts = xmaps.Values(hostsMap)
|
||||
hosts = slicesx.MapValues(hostsMap)
|
||||
slices.SortFunc(hosts, func(a, b *HostEntry) int {
|
||||
if len(a.Hosts) == 0 && len(b.Hosts) == 0 {
|
||||
return 0
|
||||
@@ -217,6 +238,26 @@ func compileHostEntries(cfg Config) (hosts []*HostEntry) {
|
||||
return hosts
|
||||
}
|
||||
|
||||
var osConfigurationReadWarnable = health.Register(&health.Warnable{
|
||||
Code: "dns-read-os-config-failed",
|
||||
Title: "Failed to read system DNS configuration",
|
||||
Text: func(args health.Args) string {
|
||||
return fmt.Sprintf("Tailscale failed to fetch the DNS configuration of your device: %v", args[health.ArgError])
|
||||
},
|
||||
Severity: health.SeverityLow,
|
||||
DependsOn: []*health.Warnable{health.NetworkStatusWarnable},
|
||||
})
|
||||
|
||||
var osConfigurationSetWarnable = health.Register(&health.Warnable{
|
||||
Code: "dns-set-os-config-failed",
|
||||
Title: "Failed to set system DNS configuration",
|
||||
Text: func(args health.Args) string {
|
||||
return fmt.Sprintf("Tailscale failed to set the DNS configuration of your device: %v", args[health.ArgError])
|
||||
},
|
||||
Severity: health.SeverityMedium,
|
||||
DependsOn: []*health.Warnable{health.NetworkStatusWarnable},
|
||||
})
|
||||
|
||||
// compileConfig converts cfg into a quad-100 resolver configuration
|
||||
// and an OS-level configuration.
|
||||
func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig, err error) {
|
||||
@@ -225,8 +266,10 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
|
||||
// the OS.
|
||||
rcfg.Hosts = cfg.Hosts
|
||||
routes := map[dnsname.FQDN][]*dnstype.Resolver{} // assigned conditionally to rcfg.Routes below.
|
||||
var propagateHostsToOS bool
|
||||
for suffix, resolvers := range cfg.Routes {
|
||||
if len(resolvers) == 0 {
|
||||
propagateHostsToOS = true
|
||||
rcfg.LocalDomains = append(rcfg.LocalDomains, suffix)
|
||||
} else {
|
||||
routes[suffix] = resolvers
|
||||
@@ -235,7 +278,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
|
||||
|
||||
// Similarly, the OS always gets search paths.
|
||||
ocfg.SearchDomains = cfg.SearchDomains
|
||||
if m.goos == "windows" {
|
||||
if propagateHostsToOS && m.goos == "windows" {
|
||||
ocfg.Hosts = compileHostEntries(cfg)
|
||||
}
|
||||
|
||||
@@ -320,9 +363,10 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
|
||||
// This is currently (2022-10-13) expected on certain iOS and macOS
|
||||
// builds.
|
||||
} else {
|
||||
m.health.SetDNSOSHealth(err)
|
||||
m.health.SetUnhealthy(osConfigurationReadWarnable, health.Args{health.ArgError: err.Error()})
|
||||
return resolver.Config{}, OSConfig{}, err
|
||||
}
|
||||
m.health.SetHealthy(osConfigurationReadWarnable)
|
||||
}
|
||||
|
||||
if baseCfg == nil {
|
||||
|
||||
2
vendor/tailscale.com/net/dns/manager_default.go
generated
vendored
2
vendor/tailscale.com/net/dns/manager_default.go
generated
vendored
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !linux && !freebsd && !openbsd && !windows && !darwin
|
||||
//go:build !linux && !freebsd && !openbsd && !windows && !darwin && !illumos && !solaris
|
||||
|
||||
package dns
|
||||
|
||||
|
||||
14
vendor/tailscale.com/net/dns/manager_solaris.go
generated
vendored
Normal file
14
vendor/tailscale.com/net/dns/manager_solaris.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs.Knobs, iface string) (OSConfigurator, error) {
|
||||
return newDirectManager(logf, health), nil
|
||||
}
|
||||
74
vendor/tailscale.com/net/dns/manager_windows.go
generated
vendored
74
vendor/tailscale.com/net/dns/manager_windows.go
generated
vendored
@@ -8,10 +8,12 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -140,9 +142,8 @@ func (m *windowsManager) setSplitDNS(resolvers []netip.Addr, domains []dnsname.F
|
||||
return m.nrptDB.WriteSplitDNSConfig(servers, domains)
|
||||
}
|
||||
|
||||
func setTailscaleHosts(prevHostsFile []byte, hosts []*HostEntry) ([]byte, error) {
|
||||
b := bytes.ReplaceAll(prevHostsFile, []byte("\r\n"), []byte("\n"))
|
||||
sc := bufio.NewScanner(bytes.NewReader(b))
|
||||
func setTailscaleHosts(logf logger.Logf, prevHostsFile []byte, hosts []*HostEntry) ([]byte, error) {
|
||||
sc := bufio.NewScanner(bytes.NewReader(prevHostsFile))
|
||||
const (
|
||||
header = "# TailscaleHostsSectionStart"
|
||||
footer = "# TailscaleHostsSectionEnd"
|
||||
@@ -151,6 +152,32 @@ func setTailscaleHosts(prevHostsFile []byte, hosts []*HostEntry) ([]byte, error)
|
||||
"# This section contains MagicDNS entries for Tailscale.",
|
||||
"# Do not edit this section manually.",
|
||||
}
|
||||
|
||||
prevEntries := make(map[netip.Addr][]string)
|
||||
addPrevEntry := func(line string) {
|
||||
if line == "" || line[0] == '#' {
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(line, " ")
|
||||
if len(parts) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
addr, err := netip.ParseAddr(parts[0])
|
||||
if err != nil {
|
||||
logf("Parsing address from hosts: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
prevEntries[addr] = parts[1:]
|
||||
}
|
||||
|
||||
nextEntries := make(map[netip.Addr][]string, len(hosts))
|
||||
for _, he := range hosts {
|
||||
nextEntries[he.Addr] = he.Hosts
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
var inSection bool
|
||||
for sc.Scan() {
|
||||
@@ -164,26 +191,34 @@ func setTailscaleHosts(prevHostsFile []byte, hosts []*HostEntry) ([]byte, error)
|
||||
continue
|
||||
}
|
||||
if inSection {
|
||||
addPrevEntry(line)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintln(&out, line)
|
||||
fmt.Fprintf(&out, "%s\r\n", line)
|
||||
}
|
||||
if err := sc.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(hosts) > 0 {
|
||||
fmt.Fprintln(&out, header)
|
||||
for _, c := range comments {
|
||||
fmt.Fprintln(&out, c)
|
||||
}
|
||||
fmt.Fprintln(&out)
|
||||
for _, he := range hosts {
|
||||
fmt.Fprintf(&out, "%s %s\n", he.Addr, strings.Join(he.Hosts, " "))
|
||||
}
|
||||
fmt.Fprintln(&out)
|
||||
fmt.Fprintln(&out, footer)
|
||||
|
||||
unchanged := maps.EqualFunc(prevEntries, nextEntries, func(a, b []string) bool {
|
||||
return slices.Equal(a, b)
|
||||
})
|
||||
if unchanged {
|
||||
return nil, nil
|
||||
}
|
||||
return bytes.ReplaceAll(out.Bytes(), []byte("\n"), []byte("\r\n")), nil
|
||||
|
||||
if len(hosts) > 0 {
|
||||
fmt.Fprintf(&out, "%s\r\n", header)
|
||||
for _, c := range comments {
|
||||
fmt.Fprintf(&out, "%s\r\n", c)
|
||||
}
|
||||
fmt.Fprintf(&out, "\r\n")
|
||||
for _, he := range hosts {
|
||||
fmt.Fprintf(&out, "%s %s\r\n", he.Addr, strings.Join(he.Hosts, " "))
|
||||
}
|
||||
fmt.Fprintf(&out, "\r\n%s\r\n", footer)
|
||||
}
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
// setHosts sets the hosts file to contain the given host entries.
|
||||
@@ -197,10 +232,15 @@ func (m *windowsManager) setHosts(hosts []*HostEntry) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outB, err := setTailscaleHosts(b, hosts)
|
||||
outB, err := setTailscaleHosts(m.logf, b, hosts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if outB == nil {
|
||||
// No change to hosts file, therefore no write necessary.
|
||||
return nil
|
||||
}
|
||||
|
||||
const fileMode = 0 // ignored on windows.
|
||||
|
||||
// This can fail spuriously with an access denied error, so retry it a
|
||||
|
||||
4
vendor/tailscale.com/net/dns/nm.go
generated
vendored
4
vendor/tailscale.com/net/dns/nm.go
generated
vendored
@@ -7,6 +7,7 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/josharian/native"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
@@ -137,7 +137,7 @@ func (m *nmManager) trySet(ctx context.Context, config OSConfig) error {
|
||||
for _, ip := range config.Nameservers {
|
||||
b := ip.As16()
|
||||
if ip.Is4() {
|
||||
dnsv4 = append(dnsv4, native.Endian.Uint32(b[12:]))
|
||||
dnsv4 = append(dnsv4, binary.NativeEndian.Uint32(b[12:]))
|
||||
} else {
|
||||
dnsv6 = append(dnsv6, b[:])
|
||||
}
|
||||
|
||||
3
vendor/tailscale.com/net/dns/resolvd.go
generated
vendored
3
vendor/tailscale.com/net/dns/resolvd.go
generated
vendored
@@ -57,6 +57,7 @@ func (m *resolvdManager) SetDNS(config OSConfig) error {
|
||||
|
||||
if len(newSearch) > 1 {
|
||||
newResolvConf = append(newResolvConf, []byte(strings.Join(newSearch, " "))...)
|
||||
newResolvConf = append(newResolvConf, '\n')
|
||||
}
|
||||
|
||||
err = m.fs.WriteFile(resolvConf, newResolvConf, 0644)
|
||||
@@ -123,6 +124,6 @@ func (m resolvdManager) readResolvConf() (config OSConfig, err error) {
|
||||
}
|
||||
|
||||
func removeSearchLines(orig []byte) []byte {
|
||||
re := regexp.MustCompile(`(?m)^search\s+.+$`)
|
||||
re := regexp.MustCompile(`(?ms)^search\s+.+$`)
|
||||
return re.ReplaceAll(orig, []byte(""))
|
||||
}
|
||||
|
||||
9
vendor/tailscale.com/net/dns/resolved.go
generated
vendored
9
vendor/tailscale.com/net/dns/resolved.go
generated
vendored
@@ -163,9 +163,9 @@ func (m *resolvedManager) run(ctx context.Context) {
|
||||
}
|
||||
conn.Signal(signals)
|
||||
|
||||
// Reset backoff and SetNSOSHealth after successful on reconnect.
|
||||
// Reset backoff and set osConfigurationSetWarnable to healthy after a successful reconnect.
|
||||
bo.BackOff(ctx, nil)
|
||||
m.health.SetDNSOSHealth(nil)
|
||||
m.health.SetHealthy(osConfigurationSetWarnable)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -243,9 +243,12 @@ func (m *resolvedManager) run(ctx context.Context) {
|
||||
// Set health while holding the lock, because this will
|
||||
// graciously serialize the resync's health outcome with a
|
||||
// concurrent SetDNS call.
|
||||
m.health.SetDNSOSHealth(err)
|
||||
|
||||
if err != nil {
|
||||
m.logf("failed to configure systemd-resolved: %v", err)
|
||||
m.health.SetUnhealthy(osConfigurationSetWarnable, health.Args{health.ArgError: err.Error()})
|
||||
} else {
|
||||
m.health.SetHealthy(osConfigurationSetWarnable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
vendor/tailscale.com/net/dns/resolver/tsdns.go
generated
vendored
2
vendor/tailscale.com/net/dns/resolver/tsdns.go
generated
vendored
@@ -384,7 +384,7 @@ func (r *Resolver) HandlePeerDNSQuery(ctx context.Context, q []byte, from netip.
|
||||
// but for now that's probably good enough. Later we'll
|
||||
// want to blend in everything from scutil --dns.
|
||||
fallthrough
|
||||
case "linux", "freebsd", "openbsd", "illumos", "ios":
|
||||
case "linux", "freebsd", "openbsd", "illumos", "solaris", "ios":
|
||||
nameserver, err := stubResolverForOS()
|
||||
if err != nil {
|
||||
r.logf("stubResolverForOS: %v", err)
|
||||
|
||||
8
vendor/tailscale.com/net/ipset/ipset.go
generated
vendored
8
vendor/tailscale.com/net/ipset/ipset.go
generated
vendored
@@ -82,8 +82,8 @@ func NewContainsIPFunc(addrs views.Slice[netip.Prefix]) func(ip netip.Addr) bool
|
||||
pathForTest("bart")
|
||||
// Built a bart table.
|
||||
t := &bart.Table[struct{}]{}
|
||||
for i := range addrs.Len() {
|
||||
t.Insert(addrs.At(i), struct{}{})
|
||||
for _, p := range addrs.All() {
|
||||
t.Insert(p, struct{}{})
|
||||
}
|
||||
return bartLookup(t)
|
||||
}
|
||||
@@ -99,8 +99,8 @@ func NewContainsIPFunc(addrs views.Slice[netip.Prefix]) func(ip netip.Addr) bool
|
||||
// General case:
|
||||
pathForTest("ip-map")
|
||||
m := set.Set[netip.Addr]{}
|
||||
for i := range addrs.Len() {
|
||||
m.Add(addrs.At(i).Addr())
|
||||
for _, p := range addrs.All() {
|
||||
m.Add(p.Addr())
|
||||
}
|
||||
return ipInMap(m)
|
||||
}
|
||||
|
||||
214
vendor/tailscale.com/net/netcheck/netcheck.go
generated
vendored
214
vendor/tailscale.com/net/netcheck/netcheck.go
generated
vendored
@@ -23,7 +23,6 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/tcnksm/go-httpstat"
|
||||
"tailscale.com/derp/derphttp"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/captivedetection"
|
||||
@@ -85,13 +84,14 @@ const (
|
||||
|
||||
// Report contains the result of a single netcheck.
|
||||
type Report struct {
|
||||
UDP bool // a UDP STUN round trip completed
|
||||
IPv6 bool // an IPv6 STUN round trip completed
|
||||
IPv4 bool // an IPv4 STUN round trip completed
|
||||
IPv6CanSend bool // an IPv6 packet was able to be sent
|
||||
IPv4CanSend bool // an IPv4 packet was able to be sent
|
||||
OSHasIPv6 bool // could bind a socket to ::1
|
||||
ICMPv4 bool // an ICMPv4 round trip completed
|
||||
Now time.Time // the time the report was run
|
||||
UDP bool // a UDP STUN round trip completed
|
||||
IPv6 bool // an IPv6 STUN round trip completed
|
||||
IPv4 bool // an IPv4 STUN round trip completed
|
||||
IPv6CanSend bool // an IPv6 packet was able to be sent
|
||||
IPv4CanSend bool // an IPv4 packet was able to be sent
|
||||
OSHasIPv6 bool // could bind a socket to ::1
|
||||
ICMPv4 bool // an ICMPv4 round trip completed
|
||||
|
||||
// MappingVariesByDestIP is whether STUN results depend which
|
||||
// STUN server you're talking to (on IPv4).
|
||||
@@ -172,25 +172,14 @@ func (r *Report) Clone() *Report {
|
||||
return nil
|
||||
}
|
||||
r2 := *r
|
||||
r2.RegionLatency = cloneDurationMap(r2.RegionLatency)
|
||||
r2.RegionV4Latency = cloneDurationMap(r2.RegionV4Latency)
|
||||
r2.RegionV6Latency = cloneDurationMap(r2.RegionV6Latency)
|
||||
r2.RegionLatency = maps.Clone(r2.RegionLatency)
|
||||
r2.RegionV4Latency = maps.Clone(r2.RegionV4Latency)
|
||||
r2.RegionV6Latency = maps.Clone(r2.RegionV6Latency)
|
||||
r2.GlobalV4Counters = maps.Clone(r2.GlobalV4Counters)
|
||||
r2.GlobalV6Counters = maps.Clone(r2.GlobalV6Counters)
|
||||
return &r2
|
||||
}
|
||||
|
||||
func cloneDurationMap(m map[int]time.Duration) map[int]time.Duration {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
m2 := make(map[int]time.Duration, len(m))
|
||||
for k, v := range m {
|
||||
m2[k] = v
|
||||
}
|
||||
return m2
|
||||
}
|
||||
|
||||
// Client generates Reports describing the result of both passive and active
|
||||
// network configuration probing. It provides two different modes of report, a
|
||||
// full report (see MakeNextReportFull) and a more lightweight incremental
|
||||
@@ -235,6 +224,10 @@ type Client struct {
|
||||
// If false, the default net.Resolver will be used, with no caching.
|
||||
UseDNSCache bool
|
||||
|
||||
// if non-zero, force this DERP region to be preferred in all reports where
|
||||
// the DERP is found to be reachable.
|
||||
ForcePreferredDERP int
|
||||
|
||||
// For tests
|
||||
testEnoughRegions int
|
||||
testCaptivePortalDelay time.Duration
|
||||
@@ -391,10 +384,14 @@ type probePlan map[string][]probe
|
||||
// sortRegions returns the regions of dm first sorted
|
||||
// from fastest to slowest (based on the 'last' report),
|
||||
// end in regions that have no data.
|
||||
func sortRegions(dm *tailcfg.DERPMap, last *Report) (prev []*tailcfg.DERPRegion) {
|
||||
func sortRegions(dm *tailcfg.DERPMap, last *Report, preferredDERP int) (prev []*tailcfg.DERPRegion) {
|
||||
prev = make([]*tailcfg.DERPRegion, 0, len(dm.Regions))
|
||||
for _, reg := range dm.Regions {
|
||||
if reg.Avoid {
|
||||
if reg.NoMeasureNoHome {
|
||||
continue
|
||||
}
|
||||
// include an otherwise avoid region if it is the current preferred region
|
||||
if reg.Avoid && reg.RegionID != preferredDERP {
|
||||
continue
|
||||
}
|
||||
prev = append(prev, reg)
|
||||
@@ -419,9 +416,19 @@ func sortRegions(dm *tailcfg.DERPMap, last *Report) (prev []*tailcfg.DERPRegion)
|
||||
// a full report, all regions are scanned.)
|
||||
const numIncrementalRegions = 3
|
||||
|
||||
// makeProbePlan generates the probe plan for a DERPMap, given the most
|
||||
// recent report and whether IPv6 is configured on an interface.
|
||||
func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (plan probePlan) {
|
||||
// makeProbePlan generates the probe plan for a DERPMap, given the most recent
|
||||
// report and the current home DERP. preferredDERP is passed independently of
|
||||
// last (report) because last is currently nil'd to indicate a desire for a full
|
||||
// netcheck.
|
||||
//
|
||||
// TODO(raggi,jwhited): refactor the callers and this function to be more clear
|
||||
// about full vs. incremental netchecks, and remove the need for the history
|
||||
// hiding. This was avoided in an incremental change due to exactly this kind of
|
||||
// distant coupling.
|
||||
// TODO(raggi): change from "preferred DERP" from a historical report to "home
|
||||
// DERP" as in what DERP is the current home connection, this would further
|
||||
// reduce flap events.
|
||||
func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report, preferredDERP int) (plan probePlan) {
|
||||
if last == nil || len(last.RegionLatency) == 0 {
|
||||
return makeProbePlanInitial(dm, ifState)
|
||||
}
|
||||
@@ -432,9 +439,34 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (pl
|
||||
had4 := len(last.RegionV4Latency) > 0
|
||||
had6 := len(last.RegionV6Latency) > 0
|
||||
hadBoth := have6if && had4 && had6
|
||||
for ri, reg := range sortRegions(dm, last) {
|
||||
if ri == numIncrementalRegions {
|
||||
break
|
||||
// #13969 ensure that the home region is always probed.
|
||||
// If a netcheck has unstable latency, such as a user with large amounts of
|
||||
// bufferbloat or a highly congested connection, there are cases where a full
|
||||
// netcheck may observe a one-off high latency to the current home DERP. Prior
|
||||
// to the forced inclusion of the home DERP, this would result in an
|
||||
// incremental netcheck following such an event to cause a home DERP move, with
|
||||
// restoration back to the home DERP on the next full netcheck ~5 minutes later
|
||||
// - which is highly disruptive when it causes shifts in geo routed subnet
|
||||
// routers. By always including the home DERP in the incremental netcheck, we
|
||||
// ensure that the home DERP is always probed, even if it observed a recenet
|
||||
// poor latency sample. This inclusion enables the latency history checks in
|
||||
// home DERP selection to still take effect.
|
||||
// planContainsHome indicates whether the home DERP has been added to the probePlan,
|
||||
// if there is no prior home, then there's no home to additionally include.
|
||||
planContainsHome := preferredDERP == 0
|
||||
for ri, reg := range sortRegions(dm, last, preferredDERP) {
|
||||
regIsHome := reg.RegionID == preferredDERP
|
||||
if ri >= numIncrementalRegions {
|
||||
// planned at least numIncrementalRegions regions and that includes the
|
||||
// last home region (or there was none), plan complete.
|
||||
if planContainsHome {
|
||||
break
|
||||
}
|
||||
// planned at least numIncrementalRegions regions, but not the home region,
|
||||
// check if this is the home region, if not, skip it.
|
||||
if !regIsHome {
|
||||
continue
|
||||
}
|
||||
}
|
||||
var p4, p6 []probe
|
||||
do4 := have4if
|
||||
@@ -445,7 +477,7 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (pl
|
||||
tries := 1
|
||||
isFastestTwo := ri < 2
|
||||
|
||||
if isFastestTwo {
|
||||
if isFastestTwo || regIsHome {
|
||||
tries = 2
|
||||
} else if hadBoth {
|
||||
// For dual stack machines, make the 3rd & slower nodes alternate
|
||||
@@ -456,14 +488,15 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *netmon.State, last *Report) (pl
|
||||
do4, do6 = false, true
|
||||
}
|
||||
}
|
||||
if !isFastestTwo && !had6 {
|
||||
if !regIsHome && !isFastestTwo && !had6 {
|
||||
do6 = false
|
||||
}
|
||||
|
||||
if reg.RegionID == last.PreferredDERP {
|
||||
if regIsHome {
|
||||
// But if we already had a DERP home, try extra hard to
|
||||
// make sure it's there so we don't flip flop around.
|
||||
tries = 4
|
||||
planContainsHome = true
|
||||
}
|
||||
|
||||
for try := 0; try < tries; try++ {
|
||||
@@ -503,7 +536,7 @@ func makeProbePlanInitial(dm *tailcfg.DERPMap, ifState *netmon.State) (plan prob
|
||||
plan = make(probePlan)
|
||||
|
||||
for _, reg := range dm.Regions {
|
||||
if len(reg.Nodes) == 0 {
|
||||
if reg.NoMeasureNoHome || len(reg.Nodes) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -742,6 +775,12 @@ func (o *GetReportOpts) getLastDERPActivity(region int) time.Time {
|
||||
return o.GetLastDERPActivity(region)
|
||||
}
|
||||
|
||||
func (c *Client) SetForcePreferredDERP(region int) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.ForcePreferredDERP = region
|
||||
}
|
||||
|
||||
// GetReport gets a report. The 'opts' argument is optional and can be nil.
|
||||
// Callers are discouraged from passing a ctx with an arbitrary deadline as this
|
||||
// may cause GetReport to return prematurely before all reporting methods have
|
||||
@@ -788,9 +827,10 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap, opts *GetRe
|
||||
c.curState = rs
|
||||
last := c.last
|
||||
|
||||
// Even if we're doing a non-incremental update, we may want to try our
|
||||
// preferred DERP region for captive portal detection. Save that, if we
|
||||
// have it.
|
||||
// Extract preferredDERP from the last report, if available. This will be used
|
||||
// in captive portal detection and DERP flapping suppression. Ideally this would
|
||||
// be the current active home DERP rather than the last report preferred DERP,
|
||||
// but only the latter is presently available.
|
||||
var preferredDERP int
|
||||
if last != nil {
|
||||
preferredDERP = last.PreferredDERP
|
||||
@@ -847,7 +887,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap, opts *GetRe
|
||||
|
||||
var plan probePlan
|
||||
if opts == nil || !opts.OnlyTCP443 {
|
||||
plan = makeProbePlan(dm, ifState, last)
|
||||
plan = makeProbePlan(dm, ifState, last, preferredDERP)
|
||||
}
|
||||
|
||||
// If we're doing a full probe, also check for a captive portal. We
|
||||
@@ -1061,10 +1101,11 @@ func (c *Client) runHTTPOnlyChecks(ctx context.Context, last *Report, rs *report
|
||||
return nil
|
||||
}
|
||||
|
||||
// measureHTTPSLatency measures HTTP request latency to the DERP region, but
|
||||
// only returns success if an HTTPS request to the region succeeds.
|
||||
func (c *Client) measureHTTPSLatency(ctx context.Context, reg *tailcfg.DERPRegion) (time.Duration, netip.Addr, error) {
|
||||
metricHTTPSend.Add(1)
|
||||
var result httpstat.Result
|
||||
ctx, cancel := context.WithTimeout(httpstat.WithHTTPStat(ctx, &result), httpsProbeTimeout)
|
||||
ctx, cancel := context.WithTimeout(ctx, httpsProbeTimeout)
|
||||
defer cancel()
|
||||
|
||||
var ip netip.Addr
|
||||
@@ -1072,6 +1113,8 @@ func (c *Client) measureHTTPSLatency(ctx context.Context, reg *tailcfg.DERPRegio
|
||||
dc := derphttp.NewNetcheckClient(c.logf, c.NetMon)
|
||||
defer dc.Close()
|
||||
|
||||
// DialRegionTLS may dial multiple times if a node is not available, as such
|
||||
// it does not have stable timing to measure.
|
||||
tlsConn, tcpConn, node, err := dc.DialRegionTLS(ctx, reg)
|
||||
if err != nil {
|
||||
return 0, ip, err
|
||||
@@ -1089,6 +1132,8 @@ func (c *Client) measureHTTPSLatency(ctx context.Context, reg *tailcfg.DERPRegio
|
||||
connc := make(chan *tls.Conn, 1)
|
||||
connc <- tlsConn
|
||||
|
||||
// make an HTTP request to measure, as this enables us to account for MITM
|
||||
// overhead in e.g. corp environments that have HTTP MITM in front of DERP.
|
||||
tr := &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return nil, errors.New("unexpected DialContext dial")
|
||||
@@ -1104,12 +1149,17 @@ func (c *Client) measureHTTPSLatency(ctx context.Context, reg *tailcfg.DERPRegio
|
||||
}
|
||||
hc := &http.Client{Transport: tr}
|
||||
|
||||
// This is the request that will be measured, the request and response
|
||||
// should be small enough to fit into a single packet each way unless the
|
||||
// connection has already become unstable.
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", "https://"+node.HostName+"/derp/latency-check", nil)
|
||||
if err != nil {
|
||||
return 0, ip, err
|
||||
}
|
||||
|
||||
startTime := c.timeNow()
|
||||
resp, err := hc.Do(req)
|
||||
reqDur := c.timeNow().Sub(startTime)
|
||||
if err != nil {
|
||||
return 0, ip, err
|
||||
}
|
||||
@@ -1126,11 +1176,12 @@ func (c *Client) measureHTTPSLatency(ctx context.Context, reg *tailcfg.DERPRegio
|
||||
if err != nil {
|
||||
return 0, ip, err
|
||||
}
|
||||
result.End(c.timeNow())
|
||||
|
||||
// TODO: decide best timing heuristic here.
|
||||
// Maybe the server should return the tcpinfo_rtt?
|
||||
return result.ServerProcessing, ip, nil
|
||||
// return the connection duration, not the request duration, as this is the
|
||||
// best approximation of the RTT latency to the node. Note that the
|
||||
// connection setup performs happy-eyeballs and TLS so there are additional
|
||||
// overheads.
|
||||
return reqDur, ip, nil
|
||||
}
|
||||
|
||||
func (c *Client) measureAllICMPLatency(ctx context.Context, rs *reportState, need []*tailcfg.DERPRegion) error {
|
||||
@@ -1182,17 +1233,19 @@ func (c *Client) measureICMPLatency(ctx context.Context, reg *tailcfg.DERPRegion
|
||||
// Try pinging the first node in the region
|
||||
node := reg.Nodes[0]
|
||||
|
||||
// Get the IPAddr by asking for the UDP address that we would use for
|
||||
// STUN and then using that IP.
|
||||
//
|
||||
// TODO(andrew-d): this is a bit ugly
|
||||
nodeAddr := c.nodeAddr(ctx, node, probeIPv4)
|
||||
if !nodeAddr.IsValid() {
|
||||
if node.STUNPort < 0 {
|
||||
// If STUN is disabled on a node, interpret that as meaning don't measure latency.
|
||||
return 0, false, nil
|
||||
}
|
||||
const unusedPort = 0
|
||||
stunAddrPort, ok := c.nodeAddrPort(ctx, node, unusedPort, probeIPv4)
|
||||
if !ok {
|
||||
return 0, false, fmt.Errorf("no address for node %v (v4-for-icmp)", node.Name)
|
||||
}
|
||||
ip := stunAddrPort.Addr()
|
||||
addr := &net.IPAddr{
|
||||
IP: net.IP(nodeAddr.Addr().AsSlice()),
|
||||
Zone: nodeAddr.Addr().Zone(),
|
||||
IP: net.IP(ip.AsSlice()),
|
||||
Zone: ip.Zone(),
|
||||
}
|
||||
|
||||
// Use the unique node.Name field as the packet data to reduce the
|
||||
@@ -1236,6 +1289,9 @@ func (c *Client) logConciseReport(r *Report, dm *tailcfg.DERPMap) {
|
||||
if r.CaptivePortal != "" {
|
||||
fmt.Fprintf(w, " captiveportal=%v", r.CaptivePortal)
|
||||
}
|
||||
if c.ForcePreferredDERP != 0 {
|
||||
fmt.Fprintf(w, " force=%v", c.ForcePreferredDERP)
|
||||
}
|
||||
fmt.Fprintf(w, " derp=%v", r.PreferredDERP)
|
||||
if r.PreferredDERP != 0 {
|
||||
fmt.Fprintf(w, " derpdist=")
|
||||
@@ -1297,6 +1353,7 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(rs *reportState, r *Report,
|
||||
c.prev = map[time.Time]*Report{}
|
||||
}
|
||||
now := c.timeNow()
|
||||
r.Now = now.UTC()
|
||||
c.prev[now] = r
|
||||
c.last = r
|
||||
|
||||
@@ -1393,6 +1450,21 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(rs *reportState, r *Report,
|
||||
// which undoes any region change we made above.
|
||||
r.PreferredDERP = prevDERP
|
||||
}
|
||||
if c.ForcePreferredDERP != 0 {
|
||||
// If the forced DERP region probed successfully, or has recent traffic,
|
||||
// use it.
|
||||
_, haveLatencySample := r.RegionLatency[c.ForcePreferredDERP]
|
||||
var recentActivity bool
|
||||
if lastHeard := rs.opts.getLastDERPActivity(c.ForcePreferredDERP); !lastHeard.IsZero() {
|
||||
now := c.timeNow()
|
||||
recentActivity = lastHeard.After(rs.start)
|
||||
recentActivity = recentActivity || lastHeard.After(now.Add(-PreferredDERPFrameTime))
|
||||
}
|
||||
|
||||
if haveLatencySample || recentActivity {
|
||||
r.PreferredDERP = c.ForcePreferredDERP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateLatency(m map[int]time.Duration, regionID int, d time.Duration) {
|
||||
@@ -1438,8 +1510,8 @@ func (rs *reportState) runProbe(ctx context.Context, dm *tailcfg.DERPMap, probe
|
||||
return
|
||||
}
|
||||
|
||||
addr := c.nodeAddr(ctx, node, probe.proto)
|
||||
if !addr.IsValid() {
|
||||
addr, ok := c.nodeAddrPort(ctx, node, node.STUNPort, probe.proto)
|
||||
if !ok {
|
||||
c.logf("netcheck.runProbe: named node %q has no %v address", probe.node, probe.proto)
|
||||
return
|
||||
}
|
||||
@@ -1488,12 +1560,20 @@ func (rs *reportState) runProbe(ctx context.Context, dm *tailcfg.DERPMap, probe
|
||||
c.vlogf("sent to %v", addr)
|
||||
}
|
||||
|
||||
// proto is 4 or 6
|
||||
// If it returns nil, the node is skipped.
|
||||
func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeProto) (ap netip.AddrPort) {
|
||||
port := cmp.Or(n.STUNPort, 3478)
|
||||
// nodeAddrPort returns the IP:port to send a STUN queries to for a given node.
|
||||
//
|
||||
// The provided port should be n.STUNPort, which may be negative to disable STUN.
|
||||
// If STUN is disabled for this node, it returns ok=false.
|
||||
// The port parameter is separate for the ICMP caller to provide a fake value.
|
||||
//
|
||||
// proto is [probeIPv4] or [probeIPv6].
|
||||
func (c *Client) nodeAddrPort(ctx context.Context, n *tailcfg.DERPNode, port int, proto probeProto) (_ netip.AddrPort, ok bool) {
|
||||
var zero netip.AddrPort
|
||||
if port < 0 || port > 1<<16-1 {
|
||||
return
|
||||
return zero, false
|
||||
}
|
||||
if port == 0 {
|
||||
port = 3478
|
||||
}
|
||||
if n.STUNTestIP != "" {
|
||||
ip, err := netip.ParseAddr(n.STUNTestIP)
|
||||
@@ -1506,7 +1586,7 @@ func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeP
|
||||
if proto == probeIPv6 && ip.Is4() {
|
||||
return
|
||||
}
|
||||
return netip.AddrPortFrom(ip, uint16(port))
|
||||
return netip.AddrPortFrom(ip, uint16(port)), true
|
||||
}
|
||||
|
||||
switch proto {
|
||||
@@ -1514,20 +1594,20 @@ func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeP
|
||||
if n.IPv4 != "" {
|
||||
ip, _ := netip.ParseAddr(n.IPv4)
|
||||
if !ip.Is4() {
|
||||
return
|
||||
return zero, false
|
||||
}
|
||||
return netip.AddrPortFrom(ip, uint16(port))
|
||||
return netip.AddrPortFrom(ip, uint16(port)), true
|
||||
}
|
||||
case probeIPv6:
|
||||
if n.IPv6 != "" {
|
||||
ip, _ := netip.ParseAddr(n.IPv6)
|
||||
if !ip.Is6() {
|
||||
return
|
||||
return zero, false
|
||||
}
|
||||
return netip.AddrPortFrom(ip, uint16(port))
|
||||
return netip.AddrPortFrom(ip, uint16(port)), true
|
||||
}
|
||||
default:
|
||||
return
|
||||
return zero, false
|
||||
}
|
||||
|
||||
// The default lookup function if we don't set UseDNSCache is to use net.DefaultResolver.
|
||||
@@ -1569,13 +1649,13 @@ func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeP
|
||||
addrs, err := lookupIPAddr(ctx, n.HostName)
|
||||
for _, a := range addrs {
|
||||
if (a.Is4() && probeIsV4) || (a.Is6() && !probeIsV4) {
|
||||
return netip.AddrPortFrom(a, uint16(port))
|
||||
return netip.AddrPortFrom(a, uint16(port)), true
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
c.logf("netcheck: DNS lookup error for %q (node %q region %v): %v", n.HostName, n.Name, n.RegionID, err)
|
||||
}
|
||||
return
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func regionHasDERPNode(r *tailcfg.DERPRegion) bool {
|
||||
|
||||
51
vendor/tailscale.com/net/netmon/interfaces_android.go
generated
vendored
51
vendor/tailscale.com/net/netmon/interfaces_android.go
generated
vendored
@@ -5,7 +5,6 @@ package netmon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"log"
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
@@ -15,7 +14,7 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -34,11 +33,6 @@ func init() {
|
||||
|
||||
var procNetRouteErr atomic.Bool
|
||||
|
||||
// errStopReading is a sentinel error value used internally by
|
||||
// lineread.File callers to stop reading. It doesn't escape to
|
||||
// callers/users.
|
||||
var errStopReading = errors.New("stop reading")
|
||||
|
||||
/*
|
||||
Parse 10.0.0.1 out of:
|
||||
|
||||
@@ -54,44 +48,42 @@ func likelyHomeRouterIPAndroid() (ret netip.Addr, myIP netip.Addr, ok bool) {
|
||||
}
|
||||
lineNum := 0
|
||||
var f []mem.RO
|
||||
err := lineread.File(procNetRoutePath, func(line []byte) error {
|
||||
for lr := range lineiter.File(procNetRoutePath) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
procNetRouteErr.Store(true)
|
||||
return likelyHomeRouterIP()
|
||||
}
|
||||
|
||||
lineNum++
|
||||
if lineNum == 1 {
|
||||
// Skip header line.
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
if lineNum > maxProcNetRouteRead {
|
||||
return errStopReading
|
||||
break
|
||||
}
|
||||
f = mem.AppendFields(f[:0], mem.B(line))
|
||||
if len(f) < 4 {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
gwHex, flagsHex := f[2], f[3]
|
||||
flags, err := mem.ParseUint(flagsHex, 16, 16)
|
||||
if err != nil {
|
||||
return nil // ignore error, skip line and keep going
|
||||
continue // ignore error, skip line and keep going
|
||||
}
|
||||
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
ipu32, err := mem.ParseUint(gwHex, 16, 32)
|
||||
if err != nil {
|
||||
return nil // ignore error, skip line and keep going
|
||||
continue // ignore error, skip line and keep going
|
||||
}
|
||||
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
|
||||
if ip.IsPrivate() {
|
||||
ret = ip
|
||||
return errStopReading
|
||||
break
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if errors.Is(err, errStopReading) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
procNetRouteErr.Store(true)
|
||||
return likelyHomeRouterIP()
|
||||
}
|
||||
if ret.IsValid() {
|
||||
// Try to get the local IP of the interface associated with
|
||||
@@ -144,23 +136,26 @@ func likelyHomeRouterIPHelper() (ret netip.Addr, _ netip.Addr, ok bool) {
|
||||
return
|
||||
}
|
||||
// Search for line like "default via 10.0.2.2 dev radio0 table 1016 proto static mtu 1500 "
|
||||
lineread.Reader(out, func(line []byte) error {
|
||||
for lr := range lineiter.Reader(out) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
const pfx = "default via "
|
||||
if !mem.HasPrefix(mem.B(line), mem.S(pfx)) {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
line = line[len(pfx):]
|
||||
sp := bytes.IndexByte(line, ' ')
|
||||
if sp == -1 {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
ipb := line[:sp]
|
||||
if ip, err := netip.ParseAddr(string(ipb)); err == nil && ip.Is4() {
|
||||
ret = ip
|
||||
log.Printf("interfaces: found Android default route %v", ip)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
return ret, netip.Addr{}, ret.IsValid()
|
||||
|
||||
37
vendor/tailscale.com/net/netmon/interfaces_linux.go
generated
vendored
37
vendor/tailscale.com/net/netmon/interfaces_linux.go
generated
vendored
@@ -23,7 +23,7 @@ import (
|
||||
"go4.org/mem"
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -32,11 +32,6 @@ func init() {
|
||||
|
||||
var procNetRouteErr atomic.Bool
|
||||
|
||||
// errStopReading is a sentinel error value used internally by
|
||||
// lineread.File callers to stop reading. It doesn't escape to
|
||||
// callers/users.
|
||||
var errStopReading = errors.New("stop reading")
|
||||
|
||||
/*
|
||||
Parse 10.0.0.1 out of:
|
||||
|
||||
@@ -52,44 +47,42 @@ func likelyHomeRouterIPLinux() (ret netip.Addr, myIP netip.Addr, ok bool) {
|
||||
}
|
||||
lineNum := 0
|
||||
var f []mem.RO
|
||||
err := lineread.File(procNetRoutePath, func(line []byte) error {
|
||||
for lr := range lineiter.File(procNetRoutePath) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
procNetRouteErr.Store(true)
|
||||
log.Printf("interfaces: failed to read /proc/net/route: %v", err)
|
||||
return ret, myIP, false
|
||||
}
|
||||
lineNum++
|
||||
if lineNum == 1 {
|
||||
// Skip header line.
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
if lineNum > maxProcNetRouteRead {
|
||||
return errStopReading
|
||||
break
|
||||
}
|
||||
f = mem.AppendFields(f[:0], mem.B(line))
|
||||
if len(f) < 4 {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
gwHex, flagsHex := f[2], f[3]
|
||||
flags, err := mem.ParseUint(flagsHex, 16, 16)
|
||||
if err != nil {
|
||||
return nil // ignore error, skip line and keep going
|
||||
continue // ignore error, skip line and keep going
|
||||
}
|
||||
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
ipu32, err := mem.ParseUint(gwHex, 16, 32)
|
||||
if err != nil {
|
||||
return nil // ignore error, skip line and keep going
|
||||
continue // ignore error, skip line and keep going
|
||||
}
|
||||
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
|
||||
if ip.IsPrivate() {
|
||||
ret = ip
|
||||
return errStopReading
|
||||
break
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if errors.Is(err, errStopReading) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
procNetRouteErr.Store(true)
|
||||
log.Printf("interfaces: failed to read /proc/net/route: %v", err)
|
||||
}
|
||||
if ret.IsValid() {
|
||||
// Try to get the local IP of the interface associated with
|
||||
|
||||
42
vendor/tailscale.com/net/netmon/loghelper.go
generated
vendored
Normal file
42
vendor/tailscale.com/net/netmon/loghelper.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netmon
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// LinkChangeLogLimiter returns a new [logger.Logf] that logs each unique
|
||||
// format string to the underlying logger only once per major LinkChange event.
|
||||
//
|
||||
// The returned function should be called when the logger is no longer needed,
|
||||
// to release resources from the Monitor.
|
||||
func LinkChangeLogLimiter(logf logger.Logf, nm *Monitor) (_ logger.Logf, unregister func()) {
|
||||
var formatSeen sync.Map // map[string]bool
|
||||
unregister = nm.RegisterChangeCallback(func(cd *ChangeDelta) {
|
||||
// If we're in a major change or a time jump, clear the seen map.
|
||||
if cd.Major || cd.TimeJumped {
|
||||
formatSeen.Clear()
|
||||
}
|
||||
})
|
||||
|
||||
return func(format string, args ...any) {
|
||||
// We only store 'true' in the map, so if it's present then it
|
||||
// means we've already logged this format string.
|
||||
_, loaded := formatSeen.LoadOrStore(format, true)
|
||||
if loaded {
|
||||
// TODO(andrew-d): we may still want to log this
|
||||
// message every N minutes (1x/hour?) even if it's been
|
||||
// seen, so that debugging doesn't require searching
|
||||
// back in the logs for an unbounded amount of time.
|
||||
//
|
||||
// See: https://github.com/tailscale/tailscale/issues/13145
|
||||
return
|
||||
}
|
||||
|
||||
logf(format, args...)
|
||||
}, unregister
|
||||
}
|
||||
2
vendor/tailscale.com/net/netmon/netmon.go
generated
vendored
2
vendor/tailscale.com/net/netmon/netmon.go
generated
vendored
@@ -161,7 +161,7 @@ func (m *Monitor) InterfaceState() *State {
|
||||
}
|
||||
|
||||
func (m *Monitor) interfaceStateUncached() (*State, error) {
|
||||
return GetState()
|
||||
return getState(m.tsIfName)
|
||||
}
|
||||
|
||||
// SetTailscaleInterfaceName sets the name of the Tailscale interface. For
|
||||
|
||||
14
vendor/tailscale.com/net/netmon/netmon_darwin.go
generated
vendored
14
vendor/tailscale.com/net/netmon/netmon_darwin.go
generated
vendored
@@ -56,7 +56,19 @@ func (m *darwinRouteMon) Receive() (message, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgs, err := route.ParseRIB(route.RIBTypeRoute, m.buf[:n])
|
||||
msgs, err := func() (msgs []route.Message, err error) {
|
||||
defer func() {
|
||||
// #14201: permanent panic protection, as we have been burned by
|
||||
// ParseRIB panics too many times.
|
||||
msg := recover()
|
||||
if msg != nil {
|
||||
msgs = nil
|
||||
m.logf("[unexpected] netmon: panic in route.ParseRIB from % 02x", m.buf[:n])
|
||||
err = fmt.Errorf("panic in route.ParseRIB: %s", msg)
|
||||
}
|
||||
}()
|
||||
return route.ParseRIB(route.RIBTypeRoute, m.buf[:n])
|
||||
}()
|
||||
if err != nil {
|
||||
if debugRouteMessages {
|
||||
m.logf("read %d bytes (% 02x), failed to parse RIB: %v", n, m.buf[:n], err)
|
||||
|
||||
31
vendor/tailscale.com/net/netmon/state.go
generated
vendored
31
vendor/tailscale.com/net/netmon/state.go
generated
vendored
@@ -19,8 +19,14 @@ import (
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
|
||||
// forceAllIPv6Endpoints is a debug knob that when set forces the client to
|
||||
// report all IPv6 endpoints rather than trim endpoints that are siblings on the
|
||||
// same interface and subnet.
|
||||
var forceAllIPv6Endpoints = envknob.RegisterBool("TS_DEBUG_FORCE_ALL_IPV6_ENDPOINTS")
|
||||
|
||||
// LoginEndpointForProxyDetermination is the URL used for testing
|
||||
// which HTTP proxy the system should use.
|
||||
var LoginEndpointForProxyDetermination = "https://controlplane.tailscale.com/"
|
||||
@@ -65,6 +71,7 @@ func LocalAddresses() (regular, loopback []netip.Addr, err error) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var subnets map[netip.Addr]int
|
||||
for _, a := range addrs {
|
||||
switch v := a.(type) {
|
||||
case *net.IPNet:
|
||||
@@ -102,7 +109,15 @@ func LocalAddresses() (regular, loopback []netip.Addr, err error) {
|
||||
if ip.Is4() {
|
||||
regular4 = append(regular4, ip)
|
||||
} else {
|
||||
regular6 = append(regular6, ip)
|
||||
curMask, _ := netip.AddrFromSlice(v.IP.Mask(v.Mask))
|
||||
// Limit the number of addresses reported per subnet for
|
||||
// IPv6, as we have seen some nodes with extremely large
|
||||
// numbers of assigned addresses being carved out of
|
||||
// same-subnet allocations.
|
||||
if forceAllIPv6Endpoints() || subnets[curMask] < 2 {
|
||||
regular6 = append(regular6, ip)
|
||||
}
|
||||
mak.Set(&subnets, curMask, subnets[curMask]+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,21 +461,22 @@ func isTailscaleInterface(name string, ips []netip.Prefix) bool {
|
||||
// getPAC, if non-nil, returns the current PAC file URL.
|
||||
var getPAC func() string
|
||||
|
||||
// GetState returns the state of all the current machine's network interfaces.
|
||||
// getState returns the state of all the current machine's network interfaces.
|
||||
//
|
||||
// It does not set the returned State.IsExpensive. The caller can populate that.
|
||||
//
|
||||
// Deprecated: use netmon.Monitor.InterfaceState instead.
|
||||
func GetState() (*State, error) {
|
||||
// optTSInterfaceName is the name of the Tailscale interface, if known.
|
||||
func getState(optTSInterfaceName string) (*State, error) {
|
||||
s := &State{
|
||||
InterfaceIPs: make(map[string][]netip.Prefix),
|
||||
Interface: make(map[string]Interface),
|
||||
}
|
||||
if err := ForeachInterface(func(ni Interface, pfxs []netip.Prefix) {
|
||||
isTSInterfaceName := optTSInterfaceName != "" && ni.Name == optTSInterfaceName
|
||||
ifUp := ni.IsUp()
|
||||
s.Interface[ni.Name] = ni
|
||||
s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], pfxs...)
|
||||
if !ifUp || isTailscaleInterface(ni.Name, pfxs) {
|
||||
if !ifUp || isTSInterfaceName || isTailscaleInterface(ni.Name, pfxs) {
|
||||
return
|
||||
}
|
||||
for _, pfx := range pfxs {
|
||||
@@ -740,11 +756,12 @@ func DefaultRoute() (DefaultRouteDetails, error) {
|
||||
|
||||
// HasCGNATInterface reports whether there are any non-Tailscale interfaces that
|
||||
// use a CGNAT IP range.
|
||||
func HasCGNATInterface() (bool, error) {
|
||||
func (m *Monitor) HasCGNATInterface() (bool, error) {
|
||||
hasCGNATInterface := false
|
||||
cgnatRange := tsaddr.CGNATRange()
|
||||
err := ForeachInterface(func(i Interface, pfxs []netip.Prefix) {
|
||||
if hasCGNATInterface || !i.IsUp() || isTailscaleInterface(i.Name, pfxs) {
|
||||
isTSInterfaceName := m.tsIfName != "" && i.Name == m.tsIfName
|
||||
if hasCGNATInterface || !i.IsUp() || isTSInterfaceName || isTailscaleInterface(i.Name, pfxs) {
|
||||
return
|
||||
}
|
||||
for _, pfx := range pfxs {
|
||||
|
||||
26
vendor/tailscale.com/net/netutil/ip_forward.go
generated
vendored
26
vendor/tailscale.com/net/netutil/ip_forward.go
generated
vendored
@@ -63,6 +63,11 @@ func CheckIPForwarding(routes []netip.Prefix, state *netmon.State) (warn, err er
|
||||
switch runtime.GOOS {
|
||||
case "dragonfly", "freebsd", "netbsd", "openbsd":
|
||||
return fmt.Errorf("Subnet routing and exit nodes only work with additional manual configuration on %v, and is not currently officially supported.", runtime.GOOS), nil
|
||||
case "illumos", "solaris":
|
||||
_, err := ipForwardingEnabledSunOS(ipv4, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, "")
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -325,3 +330,24 @@ func reversePathFilterValueLinux(iface string) (int, error) {
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func ipForwardingEnabledSunOS(p protocol, iface string) (bool, error) {
|
||||
var proto string
|
||||
if p == ipv4 {
|
||||
proto = "ipv4"
|
||||
} else if p == ipv6 {
|
||||
proto = "ipv6"
|
||||
} else {
|
||||
return false, fmt.Errorf("unknown protocol")
|
||||
}
|
||||
|
||||
ipadmCmd := "\"ipadm show-prop " + proto + " -p forwarding -o CURRENT -c\""
|
||||
bs, err := exec.Command("ipadm", "show-prop", proto, "-p", "forwarding", "-o", "CURRENT", "-c").Output()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("couldn't check %s (%v).\nSubnet routes won't work without IP forwarding.", ipadmCmd, err)
|
||||
}
|
||||
if string(bs) != "on\n" {
|
||||
return false, fmt.Errorf("IP forwarding is set to off. Subnet routes won't work. Try 'routeadm -u -e %s-forwarding'", proto)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
75
vendor/tailscale.com/net/packet/capture.go
generated
vendored
Normal file
75
vendor/tailscale.com/net/packet/capture.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/netip"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Callback describes a function which is called to
|
||||
// record packets when debugging packet-capture.
|
||||
// Such callbacks must not take ownership of the
|
||||
// provided data slice: it may only copy out of it
|
||||
// within the lifetime of the function.
|
||||
type CaptureCallback func(CapturePath, time.Time, []byte, CaptureMeta)
|
||||
|
||||
// CaptureSink is the minimal interface from [tailscale.com/feature/capture]'s
|
||||
// Sink type that is needed by the core (magicsock/LocalBackend/wgengine/etc).
|
||||
// This lets the relativel heavy feature/capture package be optionally linked.
|
||||
type CaptureSink interface {
|
||||
// Close closes
|
||||
Close() error
|
||||
|
||||
// NumOutputs returns the number of outputs registered with the sink.
|
||||
NumOutputs() int
|
||||
|
||||
// CaptureCallback returns a callback which can be used to
|
||||
// write packets to the sink.
|
||||
CaptureCallback() CaptureCallback
|
||||
|
||||
// WaitCh returns a channel which blocks until
|
||||
// the sink is closed.
|
||||
WaitCh() <-chan struct{}
|
||||
|
||||
// RegisterOutput connects an output to this sink, which
|
||||
// will be written to with a pcap stream as packets are logged.
|
||||
// A function is returned which unregisters the output when
|
||||
// called.
|
||||
//
|
||||
// If w implements io.Closer, it will be closed upon error
|
||||
// or when the sink is closed. If w implements http.Flusher,
|
||||
// it will be flushed periodically.
|
||||
RegisterOutput(w io.Writer) (unregister func())
|
||||
}
|
||||
|
||||
// CaptureMeta contains metadata that is used when debugging.
|
||||
type CaptureMeta struct {
|
||||
DidSNAT bool // SNAT was performed & the address was updated.
|
||||
OriginalSrc netip.AddrPort // The source address before SNAT was performed.
|
||||
DidDNAT bool // DNAT was performed & the address was updated.
|
||||
OriginalDst netip.AddrPort // The destination address before DNAT was performed.
|
||||
}
|
||||
|
||||
// CapturePath describes where in the data path the packet was captured.
|
||||
type CapturePath uint8
|
||||
|
||||
// CapturePath values
|
||||
const (
|
||||
// FromLocal indicates the packet was logged as it traversed the FromLocal path:
|
||||
// i.e.: A packet from the local system into the TUN.
|
||||
FromLocal CapturePath = 0
|
||||
// FromPeer indicates the packet was logged upon reception from a remote peer.
|
||||
FromPeer CapturePath = 1
|
||||
// SynthesizedToLocal indicates the packet was generated from within tailscaled,
|
||||
// and is being routed to the local machine's network stack.
|
||||
SynthesizedToLocal CapturePath = 2
|
||||
// SynthesizedToPeer indicates the packet was generated from within tailscaled,
|
||||
// and is being routed to a remote Wireguard peer.
|
||||
SynthesizedToPeer CapturePath = 3
|
||||
|
||||
// PathDisco indicates the packet is information about a disco frame.
|
||||
PathDisco CapturePath = 254
|
||||
)
|
||||
104
vendor/tailscale.com/net/packet/geneve.go
generated
vendored
Normal file
104
vendor/tailscale.com/net/packet/geneve.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
// GeneveFixedHeaderLength is the length of the fixed size portion of the
|
||||
// Geneve header, in bytes.
|
||||
GeneveFixedHeaderLength = 8
|
||||
)
|
||||
|
||||
const (
|
||||
// GeneveProtocolDisco is the IEEE 802 Ethertype number used to represent
|
||||
// the Tailscale Disco protocol in a Geneve header.
|
||||
GeneveProtocolDisco uint16 = 0x7A11
|
||||
// GeneveProtocolWireGuard is the IEEE 802 Ethertype number used to represent the
|
||||
// WireGuard protocol in a Geneve header.
|
||||
GeneveProtocolWireGuard uint16 = 0x7A12
|
||||
)
|
||||
|
||||
// GeneveHeader represents the fixed size Geneve header from RFC8926.
|
||||
// TLVs/options are not implemented/supported.
|
||||
//
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |Ver| Opt Len |O|C| Rsvd. | Protocol Type |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Virtual Network Identifier (VNI) | Reserved |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type GeneveHeader struct {
|
||||
// Ver (2 bits): The current version number is 0. Packets received by a
|
||||
// tunnel endpoint with an unknown version MUST be dropped. Transit devices
|
||||
// interpreting Geneve packets with an unknown version number MUST treat
|
||||
// them as UDP packets with an unknown payload.
|
||||
Version uint8
|
||||
|
||||
// Protocol Type (16 bits): The type of protocol data unit appearing after
|
||||
// the Geneve header. This follows the Ethertype [ETYPES] convention, with
|
||||
// Ethernet itself being represented by the value 0x6558.
|
||||
Protocol uint16
|
||||
|
||||
// Virtual Network Identifier (VNI) (24 bits): An identifier for a unique
|
||||
// element of a virtual network. In many situations, this may represent an
|
||||
// L2 segment; however, the control plane defines the forwarding semantics
|
||||
// of decapsulated packets. The VNI MAY be used as part of ECMP forwarding
|
||||
// decisions or MAY be used as a mechanism to distinguish between
|
||||
// overlapping address spaces contained in the encapsulated packet when load
|
||||
// balancing across CPUs.
|
||||
VNI uint32
|
||||
|
||||
// O (1 bit): Control packet. This packet contains a control message.
|
||||
// Control messages are sent between tunnel endpoints. Tunnel endpoints MUST
|
||||
// NOT forward the payload, and transit devices MUST NOT attempt to
|
||||
// interpret it. Since control messages are less frequent, it is RECOMMENDED
|
||||
// that tunnel endpoints direct these packets to a high-priority control
|
||||
// queue (for example, to direct the packet to a general purpose CPU from a
|
||||
// forwarding Application-Specific Integrated Circuit (ASIC) or to separate
|
||||
// out control traffic on a NIC). Transit devices MUST NOT alter forwarding
|
||||
// behavior on the basis of this bit, such as ECMP link selection.
|
||||
Control bool
|
||||
}
|
||||
|
||||
// Encode encodes GeneveHeader into b. If len(b) < GeneveFixedHeaderLength an
|
||||
// io.ErrShortBuffer error is returned.
|
||||
func (h *GeneveHeader) Encode(b []byte) error {
|
||||
if len(b) < GeneveFixedHeaderLength {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
if h.Version > 3 {
|
||||
return errors.New("version must be <= 3")
|
||||
}
|
||||
b[0] = 0
|
||||
b[1] = 0
|
||||
b[0] |= h.Version << 6
|
||||
if h.Control {
|
||||
b[1] |= 0x80
|
||||
}
|
||||
binary.BigEndian.PutUint16(b[2:], h.Protocol)
|
||||
if h.VNI > 1<<24-1 {
|
||||
return errors.New("VNI must be <= 2^24-1")
|
||||
}
|
||||
binary.BigEndian.PutUint32(b[4:], h.VNI<<8)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode decodes GeneveHeader from b. If len(b) < GeneveFixedHeaderLength an
|
||||
// io.ErrShortBuffer error is returned.
|
||||
func (h *GeneveHeader) Decode(b []byte) error {
|
||||
if len(b) < GeneveFixedHeaderLength {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
h.Version = b[0] >> 6
|
||||
if b[1]&0x80 != 0 {
|
||||
h.Control = true
|
||||
}
|
||||
h.Protocol = binary.BigEndian.Uint16(b[2:])
|
||||
h.VNI = binary.BigEndian.Uint32(b[4:]) >> 8
|
||||
return nil
|
||||
}
|
||||
8
vendor/tailscale.com/net/packet/packet.go
generated
vendored
8
vendor/tailscale.com/net/packet/packet.go
generated
vendored
@@ -34,14 +34,6 @@ const (
|
||||
TCPECNBits TCPFlag = TCPECNEcho | TCPCWR
|
||||
)
|
||||
|
||||
// CaptureMeta contains metadata that is used when debugging.
|
||||
type CaptureMeta struct {
|
||||
DidSNAT bool // SNAT was performed & the address was updated.
|
||||
OriginalSrc netip.AddrPort // The source address before SNAT was performed.
|
||||
DidDNAT bool // DNAT was performed & the address was updated.
|
||||
OriginalDst netip.AddrPort // The destination address before DNAT was performed.
|
||||
}
|
||||
|
||||
// Parsed is a minimal decoding of a packet suitable for use in filters.
|
||||
type Parsed struct {
|
||||
// b is the byte buffer that this decodes.
|
||||
|
||||
5
vendor/tailscale.com/net/portmapper/upnp.go
generated
vendored
5
vendor/tailscale.com/net/portmapper/upnp.go
generated
vendored
@@ -610,8 +610,9 @@ func (c *Client) tryUPnPPortmapWithDevice(
|
||||
}
|
||||
|
||||
// From the UPnP spec: http://upnp.org/specs/gw/UPnP-gw-WANIPConnection-v2-Service.pdf
|
||||
// 402: Invalid Args (see: https://github.com/tailscale/tailscale/issues/15223)
|
||||
// 725: OnlyPermanentLeasesSupported
|
||||
if ok && code == 725 {
|
||||
if ok && (code == 402 || code == 725) {
|
||||
newPort, err = addAnyPortMapping(
|
||||
ctx,
|
||||
client,
|
||||
@@ -620,7 +621,7 @@ func (c *Client) tryUPnPPortmapWithDevice(
|
||||
internal.Addr().String(),
|
||||
0, // permanent
|
||||
)
|
||||
c.vlogf("addAnyPortMapping: 725 retry %v, err=%q", newPort, err)
|
||||
c.vlogf("addAnyPortMapping: errcode=%d retried: port=%v err=%v", code, newPort, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
131
vendor/tailscale.com/net/socks5/socks5.go
generated
vendored
131
vendor/tailscale.com/net/socks5/socks5.go
generated
vendored
@@ -81,6 +81,12 @@ const (
|
||||
addrTypeNotSupported replyCode = 8
|
||||
)
|
||||
|
||||
// UDP conn default buffer size and read timeout.
|
||||
const (
|
||||
bufferSize = 8 * 1024
|
||||
readTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// Server is a SOCKS5 proxy server.
|
||||
type Server struct {
|
||||
// Logf optionally specifies the logger to use.
|
||||
@@ -143,7 +149,8 @@ type Conn struct {
|
||||
clientConn net.Conn
|
||||
request *request
|
||||
|
||||
udpClientAddr net.Addr
|
||||
udpClientAddr net.Addr
|
||||
udpTargetConns map[socksAddr]net.Conn
|
||||
}
|
||||
|
||||
// Run starts the new connection.
|
||||
@@ -276,15 +283,6 @@ func (c *Conn) handleUDP() error {
|
||||
}
|
||||
defer clientUDPConn.Close()
|
||||
|
||||
serverUDPConn, err := net.ListenPacket("udp", "[::]:0")
|
||||
if err != nil {
|
||||
res := errorResponse(generalFailure)
|
||||
buf, _ := res.marshal()
|
||||
c.clientConn.Write(buf)
|
||||
return err
|
||||
}
|
||||
defer serverUDPConn.Close()
|
||||
|
||||
bindAddr, bindPort, err := splitHostPort(clientUDPConn.LocalAddr().String())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -305,25 +303,32 @@ func (c *Conn) handleUDP() error {
|
||||
}
|
||||
c.clientConn.Write(buf)
|
||||
|
||||
return c.transferUDP(c.clientConn, clientUDPConn, serverUDPConn)
|
||||
return c.transferUDP(c.clientConn, clientUDPConn)
|
||||
}
|
||||
|
||||
func (c *Conn) transferUDP(associatedTCP net.Conn, clientConn net.PacketConn, targetConn net.PacketConn) error {
|
||||
func (c *Conn) transferUDP(associatedTCP net.Conn, clientConn net.PacketConn) error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
const bufferSize = 8 * 1024
|
||||
const readTimeout = 5 * time.Second
|
||||
|
||||
// client -> target
|
||||
go func() {
|
||||
defer cancel()
|
||||
|
||||
c.udpTargetConns = make(map[socksAddr]net.Conn)
|
||||
// close all target udp connections when the client connection is closed
|
||||
defer func() {
|
||||
for _, conn := range c.udpTargetConns {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
buf := make([]byte, bufferSize)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
err := c.handleUDPRequest(clientConn, targetConn, buf, readTimeout)
|
||||
err := c.handleUDPRequest(ctx, clientConn, buf)
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
continue
|
||||
@@ -337,29 +342,6 @@ func (c *Conn) transferUDP(associatedTCP net.Conn, clientConn net.PacketConn, ta
|
||||
}
|
||||
}()
|
||||
|
||||
// target -> client
|
||||
go func() {
|
||||
defer cancel()
|
||||
buf := make([]byte, bufferSize)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
err := c.handleUDPResponse(targetConn, clientConn, buf, readTimeout)
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
return
|
||||
}
|
||||
c.logf("udp transfer: handle udp response fail: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// A UDP association terminates when the TCP connection that the UDP
|
||||
// ASSOCIATE request arrived on terminates. RFC1928
|
||||
_, err := io.Copy(io.Discard, associatedTCP)
|
||||
@@ -369,11 +351,50 @@ func (c *Conn) transferUDP(associatedTCP net.Conn, clientConn net.PacketConn, ta
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) handleUDPRequest(
|
||||
func (c *Conn) getOrDialTargetConn(
|
||||
ctx context.Context,
|
||||
clientConn net.PacketConn,
|
||||
targetAddr socksAddr,
|
||||
) (net.Conn, error) {
|
||||
conn, exist := c.udpTargetConns[targetAddr]
|
||||
if exist {
|
||||
return conn, nil
|
||||
}
|
||||
conn, err := c.srv.dial(ctx, "udp", targetAddr.hostPort())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.udpTargetConns[targetAddr] = conn
|
||||
|
||||
// target -> client
|
||||
go func() {
|
||||
buf := make([]byte, bufferSize)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
err := c.handleUDPResponse(clientConn, targetAddr, conn, buf)
|
||||
if err != nil {
|
||||
if isTimeout(err) {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, net.ErrClosed) || errors.Is(err, io.EOF) {
|
||||
return
|
||||
}
|
||||
c.logf("udp transfer: handle udp response fail: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Conn) handleUDPRequest(
|
||||
ctx context.Context,
|
||||
clientConn net.PacketConn,
|
||||
targetConn net.PacketConn,
|
||||
buf []byte,
|
||||
readTimeout time.Duration,
|
||||
) error {
|
||||
// add a deadline for the read to avoid blocking forever
|
||||
_ = clientConn.SetReadDeadline(time.Now().Add(readTimeout))
|
||||
@@ -386,38 +407,35 @@ func (c *Conn) handleUDPRequest(
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse udp request: %w", err)
|
||||
}
|
||||
targetAddr, err := net.ResolveUDPAddr("udp", req.addr.hostPort())
|
||||
|
||||
targetConn, err := c.getOrDialTargetConn(ctx, clientConn, req.addr)
|
||||
if err != nil {
|
||||
c.logf("resolve target addr fail: %v", err)
|
||||
return fmt.Errorf("dial target %s fail: %w", req.addr, err)
|
||||
}
|
||||
|
||||
nn, err := targetConn.WriteTo(data, targetAddr)
|
||||
nn, err := targetConn.Write(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write to target %s fail: %w", targetAddr, err)
|
||||
return fmt.Errorf("write to target %s fail: %w", req.addr, err)
|
||||
}
|
||||
if nn != len(data) {
|
||||
return fmt.Errorf("write to target %s fail: %w", targetAddr, io.ErrShortWrite)
|
||||
return fmt.Errorf("write to target %s fail: %w", req.addr, io.ErrShortWrite)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) handleUDPResponse(
|
||||
targetConn net.PacketConn,
|
||||
clientConn net.PacketConn,
|
||||
targetAddr socksAddr,
|
||||
targetConn net.Conn,
|
||||
buf []byte,
|
||||
readTimeout time.Duration,
|
||||
) error {
|
||||
// add a deadline for the read to avoid blocking forever
|
||||
_ = targetConn.SetReadDeadline(time.Now().Add(readTimeout))
|
||||
n, addr, err := targetConn.ReadFrom(buf)
|
||||
n, err := targetConn.Read(buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read from target: %w", err)
|
||||
}
|
||||
host, port, err := splitHostPort(addr.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("split host port: %w", err)
|
||||
}
|
||||
hdr := udpRequest{addr: socksAddr{addrType: getAddrType(host), addr: host, port: port}}
|
||||
hdr := udpRequest{addr: targetAddr}
|
||||
pkt, err := hdr.marshal()
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal udp request: %w", err)
|
||||
@@ -627,10 +645,15 @@ func (s socksAddr) marshal() ([]byte, error) {
|
||||
pkt = binary.BigEndian.AppendUint16(pkt, s.port)
|
||||
return pkt, nil
|
||||
}
|
||||
|
||||
func (s socksAddr) hostPort() string {
|
||||
return net.JoinHostPort(s.addr, strconv.Itoa(int(s.port)))
|
||||
}
|
||||
|
||||
func (s socksAddr) String() string {
|
||||
return s.hostPort()
|
||||
}
|
||||
|
||||
// response contains the contents of
|
||||
// a response packet sent from the proxy
|
||||
// to the client.
|
||||
|
||||
8
vendor/tailscale.com/net/sockstats/sockstats_tsgo.go
generated
vendored
8
vendor/tailscale.com/net/sockstats/sockstats_tsgo.go
generated
vendored
@@ -279,7 +279,13 @@ func setNetMon(netMon *netmon.Monitor) {
|
||||
if ifName == "" {
|
||||
return
|
||||
}
|
||||
ifIndex := state.Interface[ifName].Index
|
||||
// DefaultRouteInterface and Interface are gathered at different points in time.
|
||||
// Check for existence first, to avoid a nil pointer dereference.
|
||||
iface, ok := state.Interface[ifName]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ifIndex := iface.Index
|
||||
sockStats.mu.Lock()
|
||||
defer sockStats.mu.Unlock()
|
||||
// Ignore changes to unknown interfaces -- it would require
|
||||
|
||||
104
vendor/tailscale.com/net/tlsdial/blockblame/blockblame.go
generated
vendored
Normal file
104
vendor/tailscale.com/net/tlsdial/blockblame/blockblame.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package blockblame blames specific firewall manufacturers for blocking Tailscale,
|
||||
// by analyzing the SSL certificate presented when attempting to connect to a remote
|
||||
// server.
|
||||
package blockblame
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// VerifyCertificate checks if the given certificate c is issued by a firewall manufacturer
|
||||
// that is known to block Tailscale connections. It returns true and the Manufacturer of
|
||||
// the equipment if it is, or false and nil if it is not.
|
||||
func VerifyCertificate(c *x509.Certificate) (m *Manufacturer, ok bool) {
|
||||
for _, m := range Manufacturers {
|
||||
if m.match != nil && m.match(c) {
|
||||
return m, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Manufacturer represents a firewall manufacturer that may be blocking Tailscale.
|
||||
type Manufacturer struct {
|
||||
// Name is the name of the firewall manufacturer to be
|
||||
// mentioned in health warning messages, e.g. "Fortinet".
|
||||
Name string
|
||||
// match is a function that returns true if the given certificate looks like it might
|
||||
// be issued by this manufacturer.
|
||||
match matchFunc
|
||||
}
|
||||
|
||||
var Manufacturers = []*Manufacturer{
|
||||
{
|
||||
Name: "Aruba Networks",
|
||||
match: issuerContains("Aruba"),
|
||||
},
|
||||
{
|
||||
Name: "Cisco",
|
||||
match: issuerContains("Cisco"),
|
||||
},
|
||||
{
|
||||
Name: "Fortinet",
|
||||
match: matchAny(
|
||||
issuerContains("Fortinet"),
|
||||
certEmail("support@fortinet.com"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "Huawei",
|
||||
match: certEmail("mobile@huawei.com"),
|
||||
},
|
||||
{
|
||||
Name: "Palo Alto Networks",
|
||||
match: matchAny(
|
||||
issuerContains("Palo Alto Networks"),
|
||||
issuerContains("PAN-FW"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "Sophos",
|
||||
match: issuerContains("Sophos"),
|
||||
},
|
||||
{
|
||||
Name: "Ubiquiti",
|
||||
match: matchAny(
|
||||
issuerContains("UniFi"),
|
||||
issuerContains("Ubiquiti"),
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
type matchFunc func(*x509.Certificate) bool
|
||||
|
||||
func issuerContains(s string) matchFunc {
|
||||
return func(c *x509.Certificate) bool {
|
||||
return strings.Contains(strings.ToLower(c.Issuer.String()), strings.ToLower(s))
|
||||
}
|
||||
}
|
||||
|
||||
func certEmail(v string) matchFunc {
|
||||
return func(c *x509.Certificate) bool {
|
||||
for _, email := range c.EmailAddresses {
|
||||
if strings.Contains(strings.ToLower(email), strings.ToLower(v)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func matchAny(fs ...matchFunc) matchFunc {
|
||||
return func(c *x509.Certificate) bool {
|
||||
for _, f := range fs {
|
||||
if f(c) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
163
vendor/tailscale.com/net/tlsdial/tlsdial.go
generated
vendored
163
vendor/tailscale.com/net/tlsdial/tlsdial.go
generated
vendored
@@ -12,6 +12,7 @@ package tlsdial
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
@@ -27,6 +28,8 @@ import (
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/hostinfo"
|
||||
"tailscale.com/net/bakedroots"
|
||||
"tailscale.com/net/tlsdial/blockblame"
|
||||
)
|
||||
|
||||
var counterFallbackOK int32 // atomic
|
||||
@@ -44,6 +47,16 @@ var debug = envknob.RegisterBool("TS_DEBUG_TLS_DIAL")
|
||||
// Headscale, etc.
|
||||
var tlsdialWarningPrinted sync.Map // map[string]bool
|
||||
|
||||
var mitmBlockWarnable = health.Register(&health.Warnable{
|
||||
Code: "blockblame-mitm-detected",
|
||||
Title: "Network may be blocking Tailscale",
|
||||
Text: func(args health.Args) string {
|
||||
return fmt.Sprintf("Network equipment from %q may be blocking Tailscale traffic on this network. Connect to another network, or contact your network administrator for assistance.", args["manufacturer"])
|
||||
},
|
||||
Severity: health.SeverityMedium,
|
||||
ImpactsConnectivity: true,
|
||||
})
|
||||
|
||||
// Config returns a tls.Config for connecting to a server.
|
||||
// If base is non-nil, it's cloned as the base config before
|
||||
// being configured and returned.
|
||||
@@ -78,20 +91,37 @@ func Config(host string, ht *health.Tracker, base *tls.Config) *tls.Config {
|
||||
// (with the baked-in fallback root) in the VerifyConnection hook.
|
||||
conf.InsecureSkipVerify = true
|
||||
conf.VerifyConnection = func(cs tls.ConnectionState) (retErr error) {
|
||||
if host == "log.tailscale.io" && hostinfo.IsNATLabGuestVM() {
|
||||
// Allow log.tailscale.io TLS MITM for integration tests when
|
||||
if host == "log.tailscale.com" && hostinfo.IsNATLabGuestVM() {
|
||||
// Allow log.tailscale.com TLS MITM for integration tests when
|
||||
// the client's running within a NATLab VM.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Perform some health checks on this certificate before we do
|
||||
// any verification.
|
||||
var cert *x509.Certificate
|
||||
var selfSignedIssuer string
|
||||
if certs := cs.PeerCertificates; len(certs) > 0 && certIsSelfSigned(certs[0]) {
|
||||
selfSignedIssuer = certs[0].Issuer.String()
|
||||
if certs := cs.PeerCertificates; len(certs) > 0 {
|
||||
cert = certs[0]
|
||||
if certIsSelfSigned(cert) {
|
||||
selfSignedIssuer = cert.Issuer.String()
|
||||
}
|
||||
}
|
||||
if ht != nil {
|
||||
defer func() {
|
||||
if retErr != nil && cert != nil {
|
||||
// Is it a MITM SSL certificate from a well-known network appliance manufacturer?
|
||||
// Show a dedicated warning.
|
||||
m, ok := blockblame.VerifyCertificate(cert)
|
||||
if ok {
|
||||
log.Printf("tlsdial: server cert for %q looks like %q equipment (could be blocking Tailscale)", host, m.Name)
|
||||
ht.SetUnhealthy(mitmBlockWarnable, health.Args{"manufacturer": m.Name})
|
||||
} else {
|
||||
ht.SetHealthy(mitmBlockWarnable)
|
||||
}
|
||||
} else {
|
||||
ht.SetHealthy(mitmBlockWarnable)
|
||||
}
|
||||
if retErr != nil && selfSignedIssuer != "" {
|
||||
// Self-signed certs are never valid.
|
||||
//
|
||||
@@ -126,7 +156,7 @@ func Config(host string, ht *health.Tracker, base *tls.Config) *tls.Config {
|
||||
// Always verify with our baked-in Let's Encrypt certificate,
|
||||
// so we can log an informational message. This is useful for
|
||||
// detecting SSL MiTM.
|
||||
opts.Roots = bakedInRoots()
|
||||
opts.Roots = bakedroots.Get()
|
||||
_, bakedErr := cs.PeerCertificates[0].Verify(opts)
|
||||
if debug() {
|
||||
log.Printf("tlsdial(bake %q): %v", host, bakedErr)
|
||||
@@ -205,7 +235,7 @@ func SetConfigExpectedCert(c *tls.Config, certDNSName string) {
|
||||
if errSys == nil {
|
||||
return nil
|
||||
}
|
||||
opts.Roots = bakedInRoots()
|
||||
opts.Roots = bakedroots.Get()
|
||||
_, err := certs[0].Verify(opts)
|
||||
if debug() {
|
||||
log.Printf("tlsdial(bake %q/%q): %v", c.ServerName, certDNSName, err)
|
||||
@@ -217,6 +247,46 @@ func SetConfigExpectedCert(c *tls.Config, certDNSName string) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetConfigExpectedCertHash configures c's VerifyPeerCertificate function
|
||||
// to require that exactly 1 cert is presented, and that the hex of its SHA256 hash
|
||||
// is equal to wantFullCertSHA256Hex and that it's a valid cert for c.ServerName.
|
||||
func SetConfigExpectedCertHash(c *tls.Config, wantFullCertSHA256Hex string) {
|
||||
if c.VerifyPeerCertificate != nil {
|
||||
panic("refusing to override tls.Config.VerifyPeerCertificate")
|
||||
}
|
||||
// Set InsecureSkipVerify to prevent crypto/tls from doing its
|
||||
// own cert verification, but do the same work that it'd do
|
||||
// (but using certDNSName) in the VerifyPeerCertificate hook.
|
||||
c.InsecureSkipVerify = true
|
||||
c.VerifyConnection = nil
|
||||
c.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
if len(rawCerts) == 0 {
|
||||
return errors.New("no certs presented")
|
||||
}
|
||||
if len(rawCerts) > 1 {
|
||||
return errors.New("unexpected multiple certs presented")
|
||||
}
|
||||
if fmt.Sprintf("%02x", sha256.Sum256(rawCerts[0])) != wantFullCertSHA256Hex {
|
||||
return fmt.Errorf("cert hash does not match expected cert hash")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(rawCerts[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("ParseCertificate: %w", err)
|
||||
}
|
||||
if err := cert.VerifyHostname(c.ServerName); err != nil {
|
||||
return fmt.Errorf("cert does not match server name %q: %w", c.ServerName, err)
|
||||
}
|
||||
now := time.Now()
|
||||
if now.After(cert.NotAfter) {
|
||||
return fmt.Errorf("cert expired %v", cert.NotAfter)
|
||||
}
|
||||
if now.Before(cert.NotBefore) {
|
||||
return fmt.Errorf("cert not yet valid until %v; is your clock correct?", cert.NotBefore)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewTransport returns a new HTTP transport that verifies TLS certs using this
|
||||
// package, including its baked-in LetsEncrypt fallback roots.
|
||||
func NewTransport() *http.Transport {
|
||||
@@ -232,84 +302,3 @@ func NewTransport() *http.Transport {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
letsEncryptX1 is the LetsEncrypt X1 root:
|
||||
|
||||
Certificate:
|
||||
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
82:10:cf:b0:d2:40:e3:59:44:63:e0:bb:63:82:8b:00
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X1
|
||||
Validity
|
||||
Not Before: Jun 4 11:04:38 2015 GMT
|
||||
Not After : Jun 4 11:04:38 2035 GMT
|
||||
Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X1
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
RSA Public-Key: (4096 bit)
|
||||
|
||||
We bake it into the binary as a fallback verification root,
|
||||
in case the system we're running on doesn't have it.
|
||||
(Tailscale runs on some ancient devices.)
|
||||
|
||||
To test that this code is working on Debian/Ubuntu:
|
||||
|
||||
$ sudo mv /usr/share/ca-certificates/mozilla/ISRG_Root_X1.crt{,.old}
|
||||
$ sudo update-ca-certificates
|
||||
|
||||
Then restart tailscaled. To also test dnsfallback's use of it, nuke
|
||||
your /etc/resolv.conf and it should still start & run fine.
|
||||
*/
|
||||
const letsEncryptX1 = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
var bakedInRootsOnce struct {
|
||||
sync.Once
|
||||
p *x509.CertPool
|
||||
}
|
||||
|
||||
func bakedInRoots() *x509.CertPool {
|
||||
bakedInRootsOnce.Do(func() {
|
||||
p := x509.NewCertPool()
|
||||
if !p.AppendCertsFromPEM([]byte(letsEncryptX1)) {
|
||||
panic("bogus PEM")
|
||||
}
|
||||
bakedInRootsOnce.p = p
|
||||
})
|
||||
return bakedInRootsOnce.p
|
||||
}
|
||||
|
||||
21
vendor/tailscale.com/net/tsaddr/tsaddr.go
generated
vendored
21
vendor/tailscale.com/net/tsaddr/tsaddr.go
generated
vendored
@@ -66,15 +66,21 @@ const (
|
||||
TailscaleServiceIPv6String = "fd7a:115c:a1e0::53"
|
||||
)
|
||||
|
||||
// IsTailscaleIP reports whether ip is an IP address in a range that
|
||||
// IsTailscaleIP reports whether IP is an IP address in a range that
|
||||
// Tailscale assigns from.
|
||||
func IsTailscaleIP(ip netip.Addr) bool {
|
||||
if ip.Is4() {
|
||||
return CGNATRange().Contains(ip) && !ChromeOSVMRange().Contains(ip)
|
||||
return IsTailscaleIPv4(ip)
|
||||
}
|
||||
return TailscaleULARange().Contains(ip)
|
||||
}
|
||||
|
||||
// IsTailscaleIPv4 reports whether an IPv4 IP is an IP address that
|
||||
// Tailscale assigns from.
|
||||
func IsTailscaleIPv4(ip netip.Addr) bool {
|
||||
return CGNATRange().Contains(ip) && !ChromeOSVMRange().Contains(ip)
|
||||
}
|
||||
|
||||
// TailscaleULARange returns the IPv6 Unique Local Address range that
|
||||
// is the superset range that Tailscale assigns out of.
|
||||
func TailscaleULARange() netip.Prefix {
|
||||
@@ -180,8 +186,7 @@ func PrefixIs6(p netip.Prefix) bool { return p.Addr().Is6() }
|
||||
// IPv6 /0 route.
|
||||
func ContainsExitRoutes(rr views.Slice[netip.Prefix]) bool {
|
||||
var v4, v6 bool
|
||||
for i := range rr.Len() {
|
||||
r := rr.At(i)
|
||||
for _, r := range rr.All() {
|
||||
if r == allIPv4 {
|
||||
v4 = true
|
||||
} else if r == allIPv6 {
|
||||
@@ -194,8 +199,8 @@ func ContainsExitRoutes(rr views.Slice[netip.Prefix]) bool {
|
||||
// ContainsExitRoute reports whether rr contains at least one of IPv4 or
|
||||
// IPv6 /0 (exit) routes.
|
||||
func ContainsExitRoute(rr views.Slice[netip.Prefix]) bool {
|
||||
for i := range rr.Len() {
|
||||
if rr.At(i).Bits() == 0 {
|
||||
for _, r := range rr.All() {
|
||||
if r.Bits() == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -205,8 +210,8 @@ func ContainsExitRoute(rr views.Slice[netip.Prefix]) bool {
|
||||
// ContainsNonExitSubnetRoutes reports whether v contains Subnet
|
||||
// Routes other than ExitNode Routes.
|
||||
func ContainsNonExitSubnetRoutes(rr views.Slice[netip.Prefix]) bool {
|
||||
for i := range rr.Len() {
|
||||
if rr.At(i).Bits() != 0 {
|
||||
for _, r := range rr.All() {
|
||||
if r.Bits() != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
9
vendor/tailscale.com/net/tsdial/dnsmap.go
generated
vendored
9
vendor/tailscale.com/net/tsdial/dnsmap.go
generated
vendored
@@ -42,8 +42,8 @@ func dnsMapFromNetworkMap(nm *netmap.NetworkMap) dnsMap {
|
||||
if dnsname.HasSuffix(nm.Name, suffix) {
|
||||
ret[canonMapKey(dnsname.TrimSuffix(nm.Name, suffix))] = ip
|
||||
}
|
||||
for i := range addrs.Len() {
|
||||
if addrs.At(i).Addr().Is4() {
|
||||
for _, p := range addrs.All() {
|
||||
if p.Addr().Is4() {
|
||||
have4 = true
|
||||
}
|
||||
}
|
||||
@@ -52,9 +52,8 @@ func dnsMapFromNetworkMap(nm *netmap.NetworkMap) dnsMap {
|
||||
if p.Name() == "" {
|
||||
continue
|
||||
}
|
||||
for i := range p.Addresses().Len() {
|
||||
a := p.Addresses().At(i)
|
||||
ip := a.Addr()
|
||||
for _, pfx := range p.Addresses().All() {
|
||||
ip := pfx.Addr()
|
||||
if ip.Is4() && !have4 {
|
||||
continue
|
||||
}
|
||||
|
||||
17
vendor/tailscale.com/net/tshttpproxy/tshttpproxy_synology.go
generated
vendored
17
vendor/tailscale.com/net/tshttpproxy/tshttpproxy_synology.go
generated
vendored
@@ -17,7 +17,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
)
|
||||
|
||||
// These vars are overridden for tests.
|
||||
@@ -47,7 +47,7 @@ func synologyProxyFromConfigCached(req *http.Request) (*url.URL, error) {
|
||||
var err error
|
||||
modtime := mtime(synologyProxyConfigPath)
|
||||
|
||||
if modtime != cache.updated {
|
||||
if !modtime.Equal(cache.updated) {
|
||||
cache.httpProxy, cache.httpsProxy, err = synologyProxiesFromConfig()
|
||||
cache.updated = modtime
|
||||
}
|
||||
@@ -76,21 +76,22 @@ func synologyProxiesFromConfig() (*url.URL, *url.URL, error) {
|
||||
func parseSynologyConfig(r io.Reader) (*url.URL, *url.URL, error) {
|
||||
cfg := map[string]string{}
|
||||
|
||||
if err := lineread.Reader(r, func(line []byte) error {
|
||||
for lr := range lineiter.Reader(r) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// accept and skip over empty lines
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
|
||||
key, value, ok := strings.Cut(string(line), "=")
|
||||
if !ok {
|
||||
return fmt.Errorf("missing \"=\" in proxy.conf line: %q", line)
|
||||
return nil, nil, fmt.Errorf("missing \"=\" in proxy.conf line: %q", line)
|
||||
}
|
||||
cfg[string(key)] = string(value)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if cfg["proxy_enabled"] != "yes" {
|
||||
|
||||
453
vendor/tailscale.com/net/tstun/tap_linux.go
generated
vendored
453
vendor/tailscale.com/net/tstun/tap_linux.go
generated
vendored
@@ -1,453 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_tap
|
||||
|
||||
package tstun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/util/multierr"
|
||||
)
|
||||
|
||||
// TODO: this was randomly generated once. Maybe do it per process start? But
|
||||
// then an upgraded tailscaled would be visible to devices behind it. So
|
||||
// maybe instead make it a function of the tailscaled's wireguard public key?
|
||||
// For now just hard code it.
|
||||
var ourMAC = net.HardwareAddr{0x30, 0x2D, 0x66, 0xEC, 0x7A, 0x93}
|
||||
|
||||
func init() { createTAP = createTAPLinux }
|
||||
|
||||
func createTAPLinux(tapName, bridgeName string) (tun.Device, error) {
|
||||
fd, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dev, err := openDevice(fd, tapName, bridgeName)
|
||||
if err != nil {
|
||||
unix.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dev, nil
|
||||
}
|
||||
|
||||
func openDevice(fd int, tapName, bridgeName string) (tun.Device, error) {
|
||||
ifr, err := unix.NewIfreq(tapName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Flags are stored as a uint16 in the ifreq union.
|
||||
ifr.SetUint16(unix.IFF_TAP | unix.IFF_NO_PI)
|
||||
if err := unix.IoctlIfreq(fd, unix.TUNSETIFF, ifr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := run("ip", "link", "set", "dev", tapName, "up"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bridgeName != "" {
|
||||
if err := run("brctl", "addif", bridgeName, tapName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return newTAPDevice(fd, tapName)
|
||||
}
|
||||
|
||||
type etherType [2]byte
|
||||
|
||||
var (
|
||||
etherTypeARP = etherType{0x08, 0x06}
|
||||
etherTypeIPv4 = etherType{0x08, 0x00}
|
||||
etherTypeIPv6 = etherType{0x86, 0xDD}
|
||||
)
|
||||
|
||||
const ipv4HeaderLen = 20
|
||||
|
||||
const (
|
||||
consumePacket = true
|
||||
passOnPacket = false
|
||||
)
|
||||
|
||||
// handleTAPFrame handles receiving a raw TAP ethernet frame and reports whether
|
||||
// it's been handled (that is, whether it should NOT be passed to wireguard).
|
||||
func (t *Wrapper) handleTAPFrame(ethBuf []byte) bool {
|
||||
|
||||
if len(ethBuf) < ethernetFrameSize {
|
||||
// Corrupt. Ignore.
|
||||
if tapDebug {
|
||||
t.logf("tap: short TAP frame")
|
||||
}
|
||||
return consumePacket
|
||||
}
|
||||
ethDstMAC, ethSrcMAC := ethBuf[:6], ethBuf[6:12]
|
||||
_ = ethDstMAC
|
||||
et := etherType{ethBuf[12], ethBuf[13]}
|
||||
switch et {
|
||||
default:
|
||||
if tapDebug {
|
||||
t.logf("tap: ignoring etherType %v", et)
|
||||
}
|
||||
return consumePacket // filter out packet we should ignore
|
||||
case etherTypeIPv6:
|
||||
// TODO: support DHCPv6/ND/etc later. For now pass all to WireGuard.
|
||||
if tapDebug {
|
||||
t.logf("tap: ignoring IPv6 %v", et)
|
||||
}
|
||||
return passOnPacket
|
||||
case etherTypeIPv4:
|
||||
if len(ethBuf) < ethernetFrameSize+ipv4HeaderLen {
|
||||
// Bogus IPv4. Eat.
|
||||
if tapDebug {
|
||||
t.logf("tap: short ipv4")
|
||||
}
|
||||
return consumePacket
|
||||
}
|
||||
return t.handleDHCPRequest(ethBuf)
|
||||
case etherTypeARP:
|
||||
arpPacket := header.ARP(ethBuf[ethernetFrameSize:])
|
||||
if !arpPacket.IsValid() {
|
||||
// Bogus ARP. Eat.
|
||||
return consumePacket
|
||||
}
|
||||
switch arpPacket.Op() {
|
||||
case header.ARPRequest:
|
||||
req := arpPacket // better name at this point
|
||||
buf := make([]byte, header.EthernetMinimumSize+header.ARPSize)
|
||||
|
||||
// Our ARP "Table" of one:
|
||||
var srcMAC [6]byte
|
||||
copy(srcMAC[:], ethSrcMAC)
|
||||
if old := t.destMAC(); old != srcMAC {
|
||||
t.destMACAtomic.Store(srcMAC)
|
||||
}
|
||||
|
||||
eth := header.Ethernet(buf)
|
||||
eth.Encode(&header.EthernetFields{
|
||||
SrcAddr: tcpip.LinkAddress(ourMAC[:]),
|
||||
DstAddr: tcpip.LinkAddress(ethSrcMAC),
|
||||
Type: 0x0806, // arp
|
||||
})
|
||||
res := header.ARP(buf[header.EthernetMinimumSize:])
|
||||
res.SetIPv4OverEthernet()
|
||||
res.SetOp(header.ARPReply)
|
||||
|
||||
// If the client's asking about their own IP, tell them it's
|
||||
// their own MAC. TODO(bradfitz): remove String allocs.
|
||||
if net.IP(req.ProtocolAddressTarget()).String() == theClientIP {
|
||||
copy(res.HardwareAddressSender(), ethSrcMAC)
|
||||
} else {
|
||||
copy(res.HardwareAddressSender(), ourMAC[:])
|
||||
}
|
||||
|
||||
copy(res.ProtocolAddressSender(), req.ProtocolAddressTarget())
|
||||
copy(res.HardwareAddressTarget(), req.HardwareAddressSender())
|
||||
copy(res.ProtocolAddressTarget(), req.ProtocolAddressSender())
|
||||
|
||||
// TODO(raggi): reduce allocs!
|
||||
n, err := t.tdev.Write([][]byte{buf}, 0)
|
||||
if tapDebug {
|
||||
t.logf("tap: wrote ARP reply %v, %v", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
return consumePacket
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(bradfitz): remove these hard-coded values and move from a /24 to a /10 CGNAT as the range.
|
||||
const theClientIP = "100.70.145.3" // TODO: make dynamic from netmap
|
||||
const routerIP = "100.70.145.1" // must be in same netmask (currently hack at /24) as theClientIP
|
||||
|
||||
// handleDHCPRequest handles receiving a raw TAP ethernet frame and reports whether
|
||||
// it's been handled as a DHCP request. That is, it reports whether the frame should
|
||||
// be ignored by the caller and not passed on.
|
||||
func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
|
||||
const udpHeader = 8
|
||||
if len(ethBuf) < ethernetFrameSize+ipv4HeaderLen+udpHeader {
|
||||
if tapDebug {
|
||||
t.logf("tap: DHCP short")
|
||||
}
|
||||
return passOnPacket
|
||||
}
|
||||
ethDstMAC, ethSrcMAC := ethBuf[:6], ethBuf[6:12]
|
||||
|
||||
if string(ethDstMAC) != "\xff\xff\xff\xff\xff\xff" {
|
||||
// Not a broadcast
|
||||
if tapDebug {
|
||||
t.logf("tap: dhcp no broadcast")
|
||||
}
|
||||
return passOnPacket
|
||||
}
|
||||
|
||||
p := parsedPacketPool.Get().(*packet.Parsed)
|
||||
defer parsedPacketPool.Put(p)
|
||||
p.Decode(ethBuf[ethernetFrameSize:])
|
||||
|
||||
if p.IPProto != ipproto.UDP || p.Src.Port() != 68 || p.Dst.Port() != 67 {
|
||||
// Not a DHCP request.
|
||||
if tapDebug {
|
||||
t.logf("tap: DHCP wrong meta")
|
||||
}
|
||||
return passOnPacket
|
||||
}
|
||||
|
||||
dp, err := dhcpv4.FromBytes(ethBuf[ethernetFrameSize+ipv4HeaderLen+udpHeader:])
|
||||
if err != nil {
|
||||
// Bogus. Trash it.
|
||||
if tapDebug {
|
||||
t.logf("tap: DHCP FromBytes bad")
|
||||
}
|
||||
return consumePacket
|
||||
}
|
||||
if tapDebug {
|
||||
t.logf("tap: DHCP request: %+v", dp)
|
||||
}
|
||||
switch dp.MessageType() {
|
||||
case dhcpv4.MessageTypeDiscover:
|
||||
offer, err := dhcpv4.New(
|
||||
dhcpv4.WithReply(dp),
|
||||
dhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),
|
||||
dhcpv4.WithRouter(net.ParseIP(routerIP)), // the default route
|
||||
dhcpv4.WithDNS(net.ParseIP("100.100.100.100")),
|
||||
dhcpv4.WithServerIP(net.ParseIP("100.100.100.100")), // TODO: what is this?
|
||||
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(net.ParseIP("100.100.100.100"))),
|
||||
dhcpv4.WithYourIP(net.ParseIP(theClientIP)),
|
||||
dhcpv4.WithLeaseTime(3600), // hour works
|
||||
//dhcpv4.WithHwAddr(ethSrcMAC),
|
||||
dhcpv4.WithNetmask(net.IPMask(net.ParseIP("255.255.255.0").To4())), // TODO: wrong
|
||||
//dhcpv4.WithTransactionID(dp.TransactionID),
|
||||
)
|
||||
if err != nil {
|
||||
t.logf("error building DHCP offer: %v", err)
|
||||
return consumePacket
|
||||
}
|
||||
// Make a layer 2 packet to write out:
|
||||
pkt := packLayer2UDP(
|
||||
offer.ToBytes(),
|
||||
ourMAC, ethSrcMAC,
|
||||
netip.AddrPortFrom(netaddr.IPv4(100, 100, 100, 100), 67), // src
|
||||
netip.AddrPortFrom(netaddr.IPv4(255, 255, 255, 255), 68), // dst
|
||||
)
|
||||
|
||||
// TODO(raggi): reduce allocs!
|
||||
n, err := t.tdev.Write([][]byte{pkt}, 0)
|
||||
if tapDebug {
|
||||
t.logf("tap: wrote DHCP OFFER %v, %v", n, err)
|
||||
}
|
||||
case dhcpv4.MessageTypeRequest:
|
||||
ack, err := dhcpv4.New(
|
||||
dhcpv4.WithReply(dp),
|
||||
dhcpv4.WithMessageType(dhcpv4.MessageTypeAck),
|
||||
dhcpv4.WithDNS(net.ParseIP("100.100.100.100")),
|
||||
dhcpv4.WithRouter(net.ParseIP(routerIP)), // the default route
|
||||
dhcpv4.WithServerIP(net.ParseIP("100.100.100.100")), // TODO: what is this?
|
||||
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(net.ParseIP("100.100.100.100"))),
|
||||
dhcpv4.WithYourIP(net.ParseIP(theClientIP)), // Hello world
|
||||
dhcpv4.WithLeaseTime(3600), // hour works
|
||||
dhcpv4.WithNetmask(net.IPMask(net.ParseIP("255.255.255.0").To4())),
|
||||
)
|
||||
if err != nil {
|
||||
t.logf("error building DHCP ack: %v", err)
|
||||
return consumePacket
|
||||
}
|
||||
// Make a layer 2 packet to write out:
|
||||
pkt := packLayer2UDP(
|
||||
ack.ToBytes(),
|
||||
ourMAC, ethSrcMAC,
|
||||
netip.AddrPortFrom(netaddr.IPv4(100, 100, 100, 100), 67), // src
|
||||
netip.AddrPortFrom(netaddr.IPv4(255, 255, 255, 255), 68), // dst
|
||||
)
|
||||
// TODO(raggi): reduce allocs!
|
||||
n, err := t.tdev.Write([][]byte{pkt}, 0)
|
||||
if tapDebug {
|
||||
t.logf("tap: wrote DHCP ACK %v, %v", n, err)
|
||||
}
|
||||
default:
|
||||
if tapDebug {
|
||||
t.logf("tap: unknown DHCP type")
|
||||
}
|
||||
}
|
||||
return consumePacket
|
||||
}
|
||||
|
||||
func packLayer2UDP(payload []byte, srcMAC, dstMAC net.HardwareAddr, src, dst netip.AddrPort) []byte {
|
||||
buf := make([]byte, header.EthernetMinimumSize+header.UDPMinimumSize+header.IPv4MinimumSize+len(payload))
|
||||
payloadStart := len(buf) - len(payload)
|
||||
copy(buf[payloadStart:], payload)
|
||||
srcB := src.Addr().As4()
|
||||
srcIP := tcpip.AddrFromSlice(srcB[:])
|
||||
dstB := dst.Addr().As4()
|
||||
dstIP := tcpip.AddrFromSlice(dstB[:])
|
||||
// Ethernet header
|
||||
eth := header.Ethernet(buf)
|
||||
eth.Encode(&header.EthernetFields{
|
||||
SrcAddr: tcpip.LinkAddress(srcMAC),
|
||||
DstAddr: tcpip.LinkAddress(dstMAC),
|
||||
Type: ipv4.ProtocolNumber,
|
||||
})
|
||||
// IP header
|
||||
ipbuf := buf[header.EthernetMinimumSize:]
|
||||
ip := header.IPv4(ipbuf)
|
||||
ip.Encode(&header.IPv4Fields{
|
||||
TotalLength: uint16(len(ipbuf)),
|
||||
TTL: 65,
|
||||
Protocol: uint8(udp.ProtocolNumber),
|
||||
SrcAddr: srcIP,
|
||||
DstAddr: dstIP,
|
||||
})
|
||||
ip.SetChecksum(^ip.CalculateChecksum())
|
||||
// UDP header
|
||||
u := header.UDP(buf[header.EthernetMinimumSize+header.IPv4MinimumSize:])
|
||||
u.Encode(&header.UDPFields{
|
||||
SrcPort: src.Port(),
|
||||
DstPort: dst.Port(),
|
||||
Length: uint16(header.UDPMinimumSize + len(payload)),
|
||||
})
|
||||
// Calculate the UDP pseudo-header checksum.
|
||||
xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, srcIP, dstIP, uint16(len(u)))
|
||||
// Calculate the UDP checksum and set it.
|
||||
xsum = checksum.Checksum(payload, xsum)
|
||||
u.SetChecksum(^u.CalculateChecksum(xsum))
|
||||
return []byte(buf)
|
||||
}
|
||||
|
||||
func run(prog string, args ...string) error {
|
||||
cmd := exec.Command(prog, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("error running %v: %v", cmd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Wrapper) destMAC() [6]byte {
|
||||
return t.destMACAtomic.Load()
|
||||
}
|
||||
|
||||
func newTAPDevice(fd int, tapName string) (tun.Device, error) {
|
||||
err := unix.SetNonblock(fd, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file := os.NewFile(uintptr(fd), "/dev/tap")
|
||||
d := &tapDevice{
|
||||
file: file,
|
||||
events: make(chan tun.Event),
|
||||
name: tapName,
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ setWrapperer = &tapDevice{}
|
||||
)
|
||||
|
||||
type tapDevice struct {
|
||||
file *os.File
|
||||
events chan tun.Event
|
||||
name string
|
||||
wrapper *Wrapper
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
func (t *tapDevice) setWrapper(wrapper *Wrapper) {
|
||||
t.wrapper = wrapper
|
||||
}
|
||||
|
||||
func (t *tapDevice) File() *os.File {
|
||||
return t.file
|
||||
}
|
||||
|
||||
func (t *tapDevice) Name() (string, error) {
|
||||
return t.name, nil
|
||||
}
|
||||
|
||||
func (t *tapDevice) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
|
||||
n, err := t.file.Read(buffs[0][offset:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (t *tapDevice) Write(buffs [][]byte, offset int) (int, error) {
|
||||
errs := make([]error, 0)
|
||||
wrote := 0
|
||||
for _, buff := range buffs {
|
||||
if offset < ethernetFrameSize {
|
||||
errs = append(errs, fmt.Errorf("[unexpected] weird offset %d for TAP write", offset))
|
||||
return 0, multierr.New(errs...)
|
||||
}
|
||||
eth := buff[offset-ethernetFrameSize:]
|
||||
dst := t.wrapper.destMAC()
|
||||
copy(eth[:6], dst[:])
|
||||
copy(eth[6:12], ourMAC[:])
|
||||
et := etherTypeIPv4
|
||||
if buff[offset]>>4 == 6 {
|
||||
et = etherTypeIPv6
|
||||
}
|
||||
eth[12], eth[13] = et[0], et[1]
|
||||
if tapDebug {
|
||||
t.wrapper.logf("tap: tapWrite off=%v % x", offset, buff)
|
||||
}
|
||||
_, err := t.file.Write(buff[offset-ethernetFrameSize:])
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
wrote++
|
||||
}
|
||||
}
|
||||
return wrote, multierr.New(errs...)
|
||||
}
|
||||
|
||||
func (t *tapDevice) MTU() (int, error) {
|
||||
ifr, err := unix.NewIfreq(t.name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = unix.IoctlIfreq(int(t.file.Fd()), unix.SIOCGIFMTU, ifr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(ifr.Uint32()), nil
|
||||
}
|
||||
|
||||
func (t *tapDevice) Events() <-chan tun.Event {
|
||||
return t.events
|
||||
}
|
||||
|
||||
func (t *tapDevice) Close() error {
|
||||
var err error
|
||||
t.closeOnce.Do(func() {
|
||||
close(t.events)
|
||||
err = t.file.Close()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *tapDevice) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
8
vendor/tailscale.com/net/tstun/tap_unsupported.go
generated
vendored
8
vendor/tailscale.com/net/tstun/tap_unsupported.go
generated
vendored
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !linux || ts_omit_tap
|
||||
|
||||
package tstun
|
||||
|
||||
func (*Wrapper) handleTAPFrame([]byte) bool { panic("unreachable") }
|
||||
2
vendor/tailscale.com/net/tstun/tstun_stub.go
generated
vendored
2
vendor/tailscale.com/net/tstun/tstun_stub.go
generated
vendored
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build plan9 || aix
|
||||
//go:build plan9 || aix || solaris || illumos
|
||||
|
||||
package tstun
|
||||
|
||||
|
||||
11
vendor/tailscale.com/net/tstun/tun.go
generated
vendored
11
vendor/tailscale.com/net/tstun/tun.go
generated
vendored
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !wasm && !plan9 && !tamago && !aix
|
||||
//go:build !wasm && !plan9 && !tamago && !aix && !solaris && !illumos
|
||||
|
||||
// Package tun creates a tuntap device, working around OS-specific
|
||||
// quirks if necessary.
|
||||
@@ -14,11 +14,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// createTAP is non-nil on Linux.
|
||||
var createTAP func(tapName, bridgeName string) (tun.Device, error)
|
||||
// CrateTAP is the hook set by feature/tap.
|
||||
var CreateTAP feature.Hook[func(logf logger.Logf, tapName, bridgeName string) (tun.Device, error)]
|
||||
|
||||
// New returns a tun.Device for the requested device name, along with
|
||||
// the OS-dependent name that was allocated to the device.
|
||||
@@ -29,7 +30,7 @@ func New(logf logger.Logf, tunName string) (tun.Device, string, error) {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil, "", errors.New("tap only works on Linux")
|
||||
}
|
||||
if createTAP == nil { // if the ts_omit_tap tag is used
|
||||
if !CreateTAP.IsSet() { // if the ts_omit_tap tag is used
|
||||
return nil, "", errors.New("tap is not supported in this build")
|
||||
}
|
||||
f := strings.Split(tunName, ":")
|
||||
@@ -42,7 +43,7 @@ func New(logf logger.Logf, tunName string) (tun.Device, string, error) {
|
||||
default:
|
||||
return nil, "", errors.New("bogus tap argument")
|
||||
}
|
||||
dev, err = createTAP(tapName, bridgeName)
|
||||
dev, err = CreateTAP.Get()(logf, tapName, bridgeName)
|
||||
} else {
|
||||
dev, err = tun.CreateTUN(tunName, int(DefaultTUNMTU()))
|
||||
}
|
||||
|
||||
140
vendor/tailscale.com/net/tstun/wrap.go
generated
vendored
140
vendor/tailscale.com/net/tstun/wrap.go
generated
vendored
@@ -36,7 +36,6 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/usermetric"
|
||||
"tailscale.com/wgengine/capture"
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/netstack/gro"
|
||||
"tailscale.com/wgengine/wgcfg"
|
||||
@@ -53,7 +52,8 @@ const PacketStartOffset = device.MessageTransportHeaderSize
|
||||
// of a packet that can be injected into a tstun.Wrapper.
|
||||
const MaxPacketSize = device.MaxContentSize
|
||||
|
||||
const tapDebug = false // for super verbose TAP debugging
|
||||
// TAPDebug is whether super verbose TAP debugging is enabled.
|
||||
const TAPDebug = false
|
||||
|
||||
var (
|
||||
// ErrClosed is returned when attempting an operation on a closed Wrapper.
|
||||
@@ -109,9 +109,7 @@ type Wrapper struct {
|
||||
lastActivityAtomic mono.Time // time of last send or receive
|
||||
|
||||
destIPActivity syncs.AtomicValue[map[netip.Addr]func()]
|
||||
//lint:ignore U1000 used in tap_linux.go
|
||||
destMACAtomic syncs.AtomicValue[[6]byte]
|
||||
discoKey syncs.AtomicValue[key.DiscoPublic]
|
||||
discoKey syncs.AtomicValue[key.DiscoPublic]
|
||||
|
||||
// timeNow, if non-nil, will be used to obtain the current time.
|
||||
timeNow func() time.Time
|
||||
@@ -209,30 +207,20 @@ type Wrapper struct {
|
||||
// stats maintains per-connection counters.
|
||||
stats atomic.Pointer[connstats.Statistics]
|
||||
|
||||
captureHook syncs.AtomicValue[capture.Callback]
|
||||
captureHook syncs.AtomicValue[packet.CaptureCallback]
|
||||
|
||||
metrics *metrics
|
||||
}
|
||||
|
||||
type metrics struct {
|
||||
inboundDroppedPacketsTotal *tsmetrics.MultiLabelMap[dropPacketLabel]
|
||||
outboundDroppedPacketsTotal *tsmetrics.MultiLabelMap[dropPacketLabel]
|
||||
inboundDroppedPacketsTotal *tsmetrics.MultiLabelMap[usermetric.DropLabels]
|
||||
outboundDroppedPacketsTotal *tsmetrics.MultiLabelMap[usermetric.DropLabels]
|
||||
}
|
||||
|
||||
func registerMetrics(reg *usermetric.Registry) *metrics {
|
||||
return &metrics{
|
||||
inboundDroppedPacketsTotal: usermetric.NewMultiLabelMapWithRegistry[dropPacketLabel](
|
||||
reg,
|
||||
"tailscaled_inbound_dropped_packets_total",
|
||||
"counter",
|
||||
"Counts the number of dropped packets received by the node from other peers",
|
||||
),
|
||||
outboundDroppedPacketsTotal: usermetric.NewMultiLabelMapWithRegistry[dropPacketLabel](
|
||||
reg,
|
||||
"tailscaled_outbound_dropped_packets_total",
|
||||
"counter",
|
||||
"Counts the number of packets dropped while being sent to other peers",
|
||||
),
|
||||
inboundDroppedPacketsTotal: reg.DroppedPacketsInbound(),
|
||||
outboundDroppedPacketsTotal: reg.DroppedPacketsOutbound(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,12 +245,6 @@ type tunVectorReadResult struct {
|
||||
dataOffset int
|
||||
}
|
||||
|
||||
type setWrapperer interface {
|
||||
// setWrapper enables the underlying TUN/TAP to have access to the Wrapper.
|
||||
// It MUST be called only once during initialization, other usage is unsafe.
|
||||
setWrapper(*Wrapper)
|
||||
}
|
||||
|
||||
// Start unblocks any Wrapper.Read calls that have already started
|
||||
// and makes the Wrapper functional.
|
||||
//
|
||||
@@ -313,10 +295,6 @@ func wrap(logf logger.Logf, tdev tun.Device, isTAP bool, m *usermetric.Registry)
|
||||
w.bufferConsumed <- struct{}{}
|
||||
w.noteActivity()
|
||||
|
||||
if sw, ok := w.tdev.(setWrapperer); ok {
|
||||
sw.setWrapper(w)
|
||||
}
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
@@ -459,12 +437,18 @@ const ethernetFrameSize = 14 // 2 six byte MACs, 2 bytes ethertype
|
||||
func (t *Wrapper) pollVector() {
|
||||
sizes := make([]int, len(t.vectorBuffer))
|
||||
readOffset := PacketStartOffset
|
||||
reader := t.tdev.Read
|
||||
if t.isTAP {
|
||||
readOffset = PacketStartOffset - ethernetFrameSize
|
||||
type tapReader interface {
|
||||
ReadEthernet(buffs [][]byte, sizes []int, offset int) (int, error)
|
||||
}
|
||||
if r, ok := t.tdev.(tapReader); ok {
|
||||
readOffset = PacketStartOffset - ethernetFrameSize
|
||||
reader = r.ReadEthernet
|
||||
}
|
||||
}
|
||||
|
||||
for range t.bufferConsumed {
|
||||
DoRead:
|
||||
for i := range t.vectorBuffer {
|
||||
t.vectorBuffer[i] = t.vectorBuffer[i][:cap(t.vectorBuffer[i])]
|
||||
}
|
||||
@@ -474,8 +458,8 @@ func (t *Wrapper) pollVector() {
|
||||
if t.isClosed() {
|
||||
return
|
||||
}
|
||||
n, err = t.tdev.Read(t.vectorBuffer[:], sizes, readOffset)
|
||||
if t.isTAP && tapDebug {
|
||||
n, err = reader(t.vectorBuffer[:], sizes, readOffset)
|
||||
if t.isTAP && TAPDebug {
|
||||
s := fmt.Sprintf("% x", t.vectorBuffer[0][:])
|
||||
for strings.HasSuffix(s, " 00") {
|
||||
s = strings.TrimSuffix(s, " 00")
|
||||
@@ -486,21 +470,6 @@ func (t *Wrapper) pollVector() {
|
||||
for i := range sizes[:n] {
|
||||
t.vectorBuffer[i] = t.vectorBuffer[i][:readOffset+sizes[i]]
|
||||
}
|
||||
if t.isTAP {
|
||||
if err == nil {
|
||||
ethernetFrame := t.vectorBuffer[0][readOffset:]
|
||||
if t.handleTAPFrame(ethernetFrame) {
|
||||
goto DoRead
|
||||
}
|
||||
}
|
||||
// Fall through. We got an IP packet.
|
||||
if sizes[0] >= ethernetFrameSize {
|
||||
t.vectorBuffer[0] = t.vectorBuffer[0][:readOffset+sizes[0]-ethernetFrameSize]
|
||||
}
|
||||
if tapDebug {
|
||||
t.logf("tap regular frame: %x", t.vectorBuffer[0][PacketStartOffset:PacketStartOffset+sizes[0]])
|
||||
}
|
||||
}
|
||||
t.sendVectorOutbound(tunVectorReadResult{
|
||||
data: t.vectorBuffer[:n],
|
||||
dataOffset: PacketStartOffset,
|
||||
@@ -823,10 +792,21 @@ func (pc *peerConfigTable) outboundPacketIsJailed(p *packet.Parsed) bool {
|
||||
return c.jailed
|
||||
}
|
||||
|
||||
// SetIPer is the interface expected to be implemented by the TAP implementation
|
||||
// of tun.Device.
|
||||
type SetIPer interface {
|
||||
// SetIP sets the IP addresses of the TAP device.
|
||||
SetIP(ipV4, ipV6 netip.Addr) error
|
||||
}
|
||||
|
||||
// SetWGConfig is called when a new NetworkMap is received.
|
||||
func (t *Wrapper) SetWGConfig(wcfg *wgcfg.Config) {
|
||||
if t.isTAP {
|
||||
if sip, ok := t.tdev.(SetIPer); ok {
|
||||
sip.SetIP(findV4(wcfg.Addresses), findV6(wcfg.Addresses))
|
||||
}
|
||||
}
|
||||
cfg := peerConfigTableFromWGConfig(wcfg)
|
||||
|
||||
old := t.peerConfig.Swap(cfg)
|
||||
if !reflect.DeepEqual(old, cfg) {
|
||||
t.logf("peer config: %v", cfg)
|
||||
@@ -896,11 +876,13 @@ func (t *Wrapper) filterPacketOutboundToWireGuard(p *packet.Parsed, pc *peerConf
|
||||
return filter.Drop, gro
|
||||
}
|
||||
|
||||
if filt.RunOut(p, t.filterFlags) != filter.Accept {
|
||||
if resp, reason := filt.RunOut(p, t.filterFlags); resp != filter.Accept {
|
||||
metricPacketOutDropFilter.Add(1)
|
||||
t.metrics.outboundDroppedPacketsTotal.Add(dropPacketLabel{
|
||||
Reason: DropReasonACL,
|
||||
}, 1)
|
||||
if reason != "" {
|
||||
t.metrics.outboundDroppedPacketsTotal.Add(usermetric.DropLabels{
|
||||
Reason: reason,
|
||||
}, 1)
|
||||
}
|
||||
return filter.Drop, gro
|
||||
}
|
||||
|
||||
@@ -925,9 +907,23 @@ func (t *Wrapper) IdleDuration() time.Duration {
|
||||
return mono.Since(t.lastActivityAtomic.LoadAtomic())
|
||||
}
|
||||
|
||||
func (t *Wrapper) awaitStart() {
|
||||
for {
|
||||
select {
|
||||
case <-t.startCh:
|
||||
return
|
||||
case <-time.After(1 * time.Second):
|
||||
// Multiple times while remixing tailscaled I (Brad) have forgotten
|
||||
// to call Start and then wasted far too much time debugging.
|
||||
// I do not wish that debugging on anyone else. Hopefully this'll help:
|
||||
t.logf("tstun: awaiting Wrapper.Start call")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
|
||||
if !t.started.Load() {
|
||||
<-t.startCh
|
||||
t.awaitStart()
|
||||
}
|
||||
// packet from OS read and sent to WG
|
||||
res, ok := <-t.vectorOutbound
|
||||
@@ -958,7 +954,7 @@ func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
|
||||
}
|
||||
}
|
||||
if captHook != nil {
|
||||
captHook(capture.FromLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
captHook(packet.FromLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
}
|
||||
if !t.disableFilter {
|
||||
var response filter.Response
|
||||
@@ -1104,9 +1100,9 @@ func (t *Wrapper) injectedRead(res tunInjectedRead, outBuffs [][]byte, sizes []i
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (t *Wrapper) filterPacketInboundFromWireGuard(p *packet.Parsed, captHook capture.Callback, pc *peerConfigTable, gro *gro.GRO) (filter.Response, *gro.GRO) {
|
||||
func (t *Wrapper) filterPacketInboundFromWireGuard(p *packet.Parsed, captHook packet.CaptureCallback, pc *peerConfigTable, gro *gro.GRO) (filter.Response, *gro.GRO) {
|
||||
if captHook != nil {
|
||||
captHook(capture.FromPeer, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
captHook(packet.FromPeer, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
}
|
||||
|
||||
if p.IPProto == ipproto.TSMP {
|
||||
@@ -1170,8 +1166,8 @@ func (t *Wrapper) filterPacketInboundFromWireGuard(p *packet.Parsed, captHook ca
|
||||
|
||||
if outcome != filter.Accept {
|
||||
metricPacketInDropFilter.Add(1)
|
||||
t.metrics.inboundDroppedPacketsTotal.Add(dropPacketLabel{
|
||||
Reason: DropReasonACL,
|
||||
t.metrics.inboundDroppedPacketsTotal.Add(usermetric.DropLabels{
|
||||
Reason: usermetric.ReasonACL,
|
||||
}, 1)
|
||||
|
||||
// Tell them, via TSMP, we're dropping them due to the ACL.
|
||||
@@ -1251,8 +1247,8 @@ func (t *Wrapper) Write(buffs [][]byte, offset int) (int, error) {
|
||||
t.noteActivity()
|
||||
_, err := t.tdevWrite(buffs, offset)
|
||||
if err != nil {
|
||||
t.metrics.inboundDroppedPacketsTotal.Add(dropPacketLabel{
|
||||
Reason: DropReasonError,
|
||||
t.metrics.inboundDroppedPacketsTotal.Add(usermetric.DropLabels{
|
||||
Reason: usermetric.ReasonError,
|
||||
}, int64(len(buffs)))
|
||||
}
|
||||
return len(buffs), err
|
||||
@@ -1320,7 +1316,7 @@ func (t *Wrapper) InjectInboundPacketBuffer(pkt *stack.PacketBuffer, buffs [][]b
|
||||
p.Decode(buf)
|
||||
captHook := t.captureHook.Load()
|
||||
if captHook != nil {
|
||||
captHook(capture.SynthesizedToLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
captHook(packet.SynthesizedToLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
}
|
||||
|
||||
invertGSOChecksum(buf, pkt.GSOOptions)
|
||||
@@ -1452,7 +1448,7 @@ func (t *Wrapper) InjectOutboundPacketBuffer(pkt *stack.PacketBuffer) error {
|
||||
}
|
||||
if capt := t.captureHook.Load(); capt != nil {
|
||||
b := pkt.ToBuffer()
|
||||
capt(capture.SynthesizedToPeer, t.now(), b.Flatten(), packet.CaptureMeta{})
|
||||
capt(packet.SynthesizedToPeer, t.now(), b.Flatten(), packet.CaptureMeta{})
|
||||
}
|
||||
|
||||
t.injectOutbound(tunInjectedRead{packet: pkt})
|
||||
@@ -1494,20 +1490,6 @@ var (
|
||||
metricPacketOutDropSelfDisco = clientmetric.NewCounter("tstun_out_to_wg_drop_self_disco")
|
||||
)
|
||||
|
||||
type DropReason string
|
||||
|
||||
const (
|
||||
DropReasonACL DropReason = "acl"
|
||||
DropReasonError DropReason = "error"
|
||||
)
|
||||
|
||||
type dropPacketLabel struct {
|
||||
// Reason indicates what we have done with the packet, and has the following values:
|
||||
// - acl (rejected packets because of ACL)
|
||||
// - error (rejected packets because of an error)
|
||||
Reason DropReason
|
||||
}
|
||||
|
||||
func (t *Wrapper) InstallCaptureHook(cb capture.Callback) {
|
||||
func (t *Wrapper) InstallCaptureHook(cb packet.CaptureCallback) {
|
||||
t.captureHook.Store(cb)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user