Compare commits
8 Commits
jonathan/d
...
bradfitz/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c525b20511 | ||
|
|
5aa1c27aad | ||
|
|
725c8d298a | ||
|
|
08c8ccb48e | ||
|
|
e78055eb01 | ||
|
|
ea79dc161d | ||
|
|
b3455fa99a | ||
|
|
14db99241f |
365
cmd/natc/natc_test.go
Normal file
365
cmd/natc/natc_test.go
Normal file
@@ -0,0 +1,365 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
func prefixEqual(a, b netip.Prefix) bool {
|
||||
return a.Bits() == b.Bits() && a.Addr() == b.Addr()
|
||||
}
|
||||
|
||||
func TestULA(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
siteID uint16
|
||||
expected string
|
||||
}{
|
||||
{"zero", 0, "fd7a:115c:a1e0:a99c:0000::/80"},
|
||||
{"one", 1, "fd7a:115c:a1e0:a99c:0001::/80"},
|
||||
{"max", 65535, "fd7a:115c:a1e0:a99c:ffff::/80"},
|
||||
{"random", 12345, "fd7a:115c:a1e0:a99c:3039::/80"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := ula(tc.siteID)
|
||||
expected := netip.MustParsePrefix(tc.expected)
|
||||
if !prefixEqual(got, expected) {
|
||||
t.Errorf("ula(%d) = %s; want %s", tc.siteID, got, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandV4(t *testing.T) {
|
||||
pfx := netip.MustParsePrefix("100.64.1.0/24")
|
||||
|
||||
for i := 0; i < 512; i++ {
|
||||
ip := randV4(pfx)
|
||||
if !pfx.Contains(ip) {
|
||||
t.Errorf("randV4(%s) = %s; not contained in prefix", pfx, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSResponse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
questions []dnsmessage.Question
|
||||
addrs []netip.Addr
|
||||
wantEmpty bool
|
||||
wantAnswers []struct {
|
||||
name string
|
||||
qType dnsmessage.Type
|
||||
addr netip.Addr
|
||||
}
|
||||
}{
|
||||
{
|
||||
name: "empty_request",
|
||||
questions: []dnsmessage.Question{},
|
||||
addrs: []netip.Addr{},
|
||||
wantEmpty: false,
|
||||
wantAnswers: nil,
|
||||
},
|
||||
{
|
||||
name: "a_record",
|
||||
questions: []dnsmessage.Question{
|
||||
{
|
||||
Name: dnsmessage.MustNewName("example.com."),
|
||||
Type: dnsmessage.TypeA,
|
||||
Class: dnsmessage.ClassINET,
|
||||
},
|
||||
},
|
||||
addrs: []netip.Addr{netip.MustParseAddr("100.64.1.5")},
|
||||
wantAnswers: []struct {
|
||||
name string
|
||||
qType dnsmessage.Type
|
||||
addr netip.Addr
|
||||
}{
|
||||
{
|
||||
name: "example.com.",
|
||||
qType: dnsmessage.TypeA,
|
||||
addr: netip.MustParseAddr("100.64.1.5"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "aaaa_record",
|
||||
questions: []dnsmessage.Question{
|
||||
{
|
||||
Name: dnsmessage.MustNewName("example.com."),
|
||||
Type: dnsmessage.TypeAAAA,
|
||||
Class: dnsmessage.ClassINET,
|
||||
},
|
||||
},
|
||||
addrs: []netip.Addr{netip.MustParseAddr("fd7a:115c:a1e0:a99c:0001:0505:0505:0505")},
|
||||
wantAnswers: []struct {
|
||||
name string
|
||||
qType dnsmessage.Type
|
||||
addr netip.Addr
|
||||
}{
|
||||
{
|
||||
name: "example.com.",
|
||||
qType: dnsmessage.TypeAAAA,
|
||||
addr: netip.MustParseAddr("fd7a:115c:a1e0:a99c:0001:0505:0505:0505"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "soa_record",
|
||||
questions: []dnsmessage.Question{
|
||||
{
|
||||
Name: dnsmessage.MustNewName("example.com."),
|
||||
Type: dnsmessage.TypeSOA,
|
||||
Class: dnsmessage.ClassINET,
|
||||
},
|
||||
},
|
||||
addrs: []netip.Addr{},
|
||||
wantAnswers: nil,
|
||||
},
|
||||
{
|
||||
name: "ns_record",
|
||||
questions: []dnsmessage.Question{
|
||||
{
|
||||
Name: dnsmessage.MustNewName("example.com."),
|
||||
Type: dnsmessage.TypeNS,
|
||||
Class: dnsmessage.ClassINET,
|
||||
},
|
||||
},
|
||||
addrs: []netip.Addr{},
|
||||
wantAnswers: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := &dnsmessage.Message{
|
||||
Header: dnsmessage.Header{
|
||||
ID: 1234,
|
||||
},
|
||||
Questions: tc.questions,
|
||||
}
|
||||
|
||||
resp, err := dnsResponse(req, tc.addrs)
|
||||
if err != nil {
|
||||
t.Fatalf("dnsResponse() error = %v", err)
|
||||
}
|
||||
|
||||
if tc.wantEmpty && len(resp) != 0 {
|
||||
t.Errorf("dnsResponse() returned non-empty response when expected empty")
|
||||
}
|
||||
|
||||
if !tc.wantEmpty && len(resp) == 0 {
|
||||
t.Errorf("dnsResponse() returned empty response when expected non-empty")
|
||||
}
|
||||
|
||||
if len(resp) > 0 {
|
||||
var msg dnsmessage.Message
|
||||
err = msg.Unpack(resp)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unpack response: %v", err)
|
||||
}
|
||||
|
||||
if !msg.Header.Response {
|
||||
t.Errorf("Response header is not set")
|
||||
}
|
||||
|
||||
if msg.Header.ID != req.Header.ID {
|
||||
t.Errorf("Response ID = %d, want %d", msg.Header.ID, req.Header.ID)
|
||||
}
|
||||
|
||||
if len(tc.wantAnswers) > 0 {
|
||||
if len(msg.Answers) != len(tc.wantAnswers) {
|
||||
t.Errorf("got %d answers, want %d", len(msg.Answers), len(tc.wantAnswers))
|
||||
} else {
|
||||
for i, want := range tc.wantAnswers {
|
||||
ans := msg.Answers[i]
|
||||
|
||||
gotName := ans.Header.Name.String()
|
||||
if gotName != want.name {
|
||||
t.Errorf("answer[%d] name = %s, want %s", i, gotName, want.name)
|
||||
}
|
||||
|
||||
if ans.Header.Type != want.qType {
|
||||
t.Errorf("answer[%d] type = %v, want %v", i, ans.Header.Type, want.qType)
|
||||
}
|
||||
|
||||
var gotIP netip.Addr
|
||||
switch want.qType {
|
||||
case dnsmessage.TypeA:
|
||||
if ans.Body.(*dnsmessage.AResource) == nil {
|
||||
t.Errorf("answer[%d] not an A record", i)
|
||||
continue
|
||||
}
|
||||
resource := ans.Body.(*dnsmessage.AResource)
|
||||
gotIP = netip.AddrFrom4([4]byte(resource.A))
|
||||
case dnsmessage.TypeAAAA:
|
||||
if ans.Body.(*dnsmessage.AAAAResource) == nil {
|
||||
t.Errorf("answer[%d] not an AAAA record", i)
|
||||
continue
|
||||
}
|
||||
resource := ans.Body.(*dnsmessage.AAAAResource)
|
||||
gotIP = netip.AddrFrom16([16]byte(resource.AAAA))
|
||||
}
|
||||
|
||||
if gotIP != want.addr {
|
||||
t.Errorf("answer[%d] IP = %s, want %s", i, gotIP, want.addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerPeerState(t *testing.T) {
|
||||
c := &connector{
|
||||
v4Ranges: []netip.Prefix{netip.MustParsePrefix("100.64.1.0/24")},
|
||||
v6ULA: netip.MustParsePrefix("fd7a:115c:a1e0:a99c:0001::/80"),
|
||||
dnsAddr: netip.MustParseAddr("100.64.1.1"),
|
||||
}
|
||||
|
||||
ps := &perPeerState{c: c}
|
||||
|
||||
addrs, err := ps.ipForDomain("example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("ipForDomain() error = %v", err)
|
||||
}
|
||||
|
||||
if len(addrs) != 2 {
|
||||
t.Fatalf("ipForDomain() returned %d addresses, want 2", len(addrs))
|
||||
}
|
||||
|
||||
v4 := addrs[0]
|
||||
v6 := addrs[1]
|
||||
|
||||
if !v4.Is4() {
|
||||
t.Errorf("First address is not IPv4: %s", v4)
|
||||
}
|
||||
|
||||
if !v6.Is6() {
|
||||
t.Errorf("Second address is not IPv6: %s", v6)
|
||||
}
|
||||
|
||||
if !c.v4Ranges[0].Contains(v4) {
|
||||
t.Errorf("IPv4 address %s not in range %s", v4, c.v4Ranges[0])
|
||||
}
|
||||
|
||||
domain, ok := ps.domainForIP(v4)
|
||||
if !ok {
|
||||
t.Errorf("domainForIP(%s) not found", v4)
|
||||
} else if domain != "example.com" {
|
||||
t.Errorf("domainForIP(%s) = %s, want %s", v4, domain, "example.com")
|
||||
}
|
||||
|
||||
domain, ok = ps.domainForIP(v6)
|
||||
if !ok {
|
||||
t.Errorf("domainForIP(%s) not found", v6)
|
||||
} else if domain != "example.com" {
|
||||
t.Errorf("domainForIP(%s) = %s, want %s", v6, domain, "example.com")
|
||||
}
|
||||
|
||||
addrs2, err := ps.ipForDomain("example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("ipForDomain() second call error = %v", err)
|
||||
}
|
||||
|
||||
if !slices.Equal(addrs, addrs2) {
|
||||
t.Errorf("ipForDomain() second call = %v, want %v", addrs2, addrs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreDestination(t *testing.T) {
|
||||
ignoreDstTable := &bart.Table[bool]{}
|
||||
ignoreDstTable.Insert(netip.MustParsePrefix("192.168.1.0/24"), true)
|
||||
ignoreDstTable.Insert(netip.MustParsePrefix("10.0.0.0/8"), true)
|
||||
|
||||
c := &connector{
|
||||
ignoreDsts: ignoreDstTable,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
addrs []netip.Addr
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "no_match",
|
||||
addrs: []netip.Addr{netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("1.1.1.1")},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "one_match",
|
||||
addrs: []netip.Addr{netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("192.168.1.5")},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "all_match",
|
||||
addrs: []netip.Addr{netip.MustParseAddr("10.0.0.1"), netip.MustParseAddr("192.168.1.5")},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "empty_addrs",
|
||||
addrs: []netip.Addr{},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := c.ignoreDestination(tc.addrs)
|
||||
if got != tc.expected {
|
||||
t.Errorf("ignoreDestination(%v) = %v, want %v", tc.addrs, got, tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectorGenerateDNSResponse(t *testing.T) {
|
||||
c := &connector{
|
||||
v4Ranges: []netip.Prefix{netip.MustParsePrefix("100.64.1.0/24")},
|
||||
v6ULA: netip.MustParsePrefix("fd7a:115c:a1e0:a99c:0001::/80"),
|
||||
dnsAddr: netip.MustParseAddr("100.64.1.1"),
|
||||
}
|
||||
|
||||
req := &dnsmessage.Message{
|
||||
Header: dnsmessage.Header{ID: 1234},
|
||||
Questions: []dnsmessage.Question{
|
||||
{
|
||||
Name: dnsmessage.MustNewName("example.com."),
|
||||
Type: dnsmessage.TypeA,
|
||||
Class: dnsmessage.ClassINET,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
nodeID := tailcfg.NodeID(12345)
|
||||
|
||||
resp1, err := c.generateDNSResponse(req, nodeID)
|
||||
if err != nil {
|
||||
t.Fatalf("generateDNSResponse() error = %v", err)
|
||||
}
|
||||
if len(resp1) == 0 {
|
||||
t.Fatalf("generateDNSResponse() returned empty response")
|
||||
}
|
||||
|
||||
resp2, err := c.generateDNSResponse(req, nodeID)
|
||||
if err != nil {
|
||||
t.Fatalf("generateDNSResponse() second call error = %v", err)
|
||||
}
|
||||
|
||||
if !cmp.Equal(resp1, resp2) {
|
||||
t.Errorf("generateDNSResponse() responses differ between calls")
|
||||
}
|
||||
}
|
||||
@@ -96,6 +96,9 @@ func (a *Dialer) httpsFallbackDelay() time.Duration {
|
||||
var _ = envknob.RegisterBool("TS_USE_CONTROL_DIAL_PLAN") // to record at init time whether it's in use
|
||||
|
||||
func (a *Dialer) dial(ctx context.Context) (*ClientConn, error) {
|
||||
|
||||
a.logPort80Failure.Store(true)
|
||||
|
||||
// If we don't have a dial plan, just fall back to dialing the single
|
||||
// host we know about.
|
||||
useDialPlan := envknob.BoolDefaultTrue("TS_USE_CONTROL_DIAL_PLAN")
|
||||
@@ -278,7 +281,9 @@ func (d *Dialer) forceNoise443() bool {
|
||||
// This heuristic works around networks where port 80 is MITMed and
|
||||
// appears to work for a bit post-Upgrade but then gets closed,
|
||||
// such as seen in https://github.com/tailscale/tailscale/issues/13597.
|
||||
d.logf("controlhttp: forcing port 443 dial due to recent noise dial")
|
||||
if d.logPort80Failure.CompareAndSwap(true, false) {
|
||||
d.logf("controlhttp: forcing port 443 dial due to recent noise dial")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ package controlhttp
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"tailscale.com/health"
|
||||
@@ -90,6 +91,11 @@ type Dialer struct {
|
||||
|
||||
proxyFunc func(*http.Request) (*url.URL, error) // or nil
|
||||
|
||||
// logPort80Failure is whether we should log about port 80 interceptions
|
||||
// and forcing a port 443 dial. We do this only once per "dial" method
|
||||
// which can result in many concurrent racing dialHost calls.
|
||||
logPort80Failure atomic.Bool
|
||||
|
||||
// For tests only
|
||||
drainFinished chan struct{}
|
||||
omitCertErrorLogging bool
|
||||
|
||||
@@ -958,7 +958,9 @@ func (b *LocalBackend) linkChange(delta *netmon.ChangeDelta) {
|
||||
|
||||
if peerAPIListenAsync && b.netMap != nil && b.state == ipn.Running {
|
||||
want := b.netMap.GetAddresses().Len()
|
||||
if len(b.peerAPIListeners) < want {
|
||||
have := len(b.peerAPIListeners)
|
||||
b.logf("[v1] linkChange: have %d peerAPIListeners, want %d", have, want)
|
||||
if have < want {
|
||||
b.logf("linkChange: peerAPIListeners too low; trying again")
|
||||
b.goTracker.Go(b.initPeerAPIListener)
|
||||
}
|
||||
@@ -2402,11 +2404,9 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
}
|
||||
|
||||
var auditLogShutdown func()
|
||||
// Audit logging is only available if the client has set up a proper persistent
|
||||
// store for the logs in sys.
|
||||
store, ok := b.sys.AuditLogStore.GetOK()
|
||||
if !ok {
|
||||
b.logf("auditlog: [unexpected] no persistent audit log storage configured. using memory store.")
|
||||
// Use memory store by default if no explicit store is provided.
|
||||
store = auditlog.NewLogStore(&memstore.Store{})
|
||||
}
|
||||
|
||||
@@ -4968,7 +4968,7 @@ func (b *LocalBackend) authReconfig() {
|
||||
return
|
||||
}
|
||||
|
||||
oneCGNATRoute := shouldUseOneCGNATRoute(b.logf, b.sys.ControlKnobs(), version.OS())
|
||||
oneCGNATRoute := shouldUseOneCGNATRoute(b.logf, b.sys.NetMon.Get(), b.sys.ControlKnobs(), version.OS())
|
||||
rcfg := b.routerConfig(cfg, prefs, oneCGNATRoute)
|
||||
|
||||
err = b.e.Reconfig(cfg, rcfg, dcfg)
|
||||
@@ -4992,7 +4992,7 @@ func (b *LocalBackend) authReconfig() {
|
||||
//
|
||||
// The versionOS is a Tailscale-style version ("iOS", "macOS") and not
|
||||
// a runtime.GOOS.
|
||||
func shouldUseOneCGNATRoute(logf logger.Logf, controlKnobs *controlknobs.Knobs, versionOS string) bool {
|
||||
func shouldUseOneCGNATRoute(logf logger.Logf, mon *netmon.Monitor, controlKnobs *controlknobs.Knobs, versionOS string) bool {
|
||||
if controlKnobs != nil {
|
||||
// Explicit enabling or disabling always take precedence.
|
||||
if v, ok := controlKnobs.OneCGNAT.Load().Get(); ok {
|
||||
@@ -5007,7 +5007,7 @@ func shouldUseOneCGNATRoute(logf logger.Logf, controlKnobs *controlknobs.Knobs,
|
||||
// use fine-grained routes if another interfaces is also using the CGNAT
|
||||
// IP range.
|
||||
if versionOS == "macOS" {
|
||||
hasCGNATInterface, err := netmon.HasCGNATInterface()
|
||||
hasCGNATInterface, err := mon.HasCGNATInterface()
|
||||
if err != nil {
|
||||
logf("shouldUseOneCGNATRoute: Could not determine if any interfaces use CGNAT: %v", err)
|
||||
return false
|
||||
@@ -5369,6 +5369,7 @@ func (b *LocalBackend) initPeerAPIListener() {
|
||||
ln, err = ps.listen(a.Addr(), b.prevIfState)
|
||||
if err != nil {
|
||||
if peerAPIListenAsync {
|
||||
b.logf("possibly transient peerapi listen(%q) error, will try again on linkChange: %v", a.Addr(), err)
|
||||
// Expected. But we fix it later in linkChange
|
||||
// ("peerAPIListeners too low").
|
||||
continue
|
||||
|
||||
@@ -481,7 +481,7 @@ func (h *peerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Re
|
||||
fmt.Fprintf(w, "<h3>Could not get the default route: %s</h3>\n", html.EscapeString(err.Error()))
|
||||
}
|
||||
|
||||
if hasCGNATInterface, err := netmon.HasCGNATInterface(); hasCGNATInterface {
|
||||
if hasCGNATInterface, err := h.ps.b.sys.NetMon.Get().HasCGNATInterface(); hasCGNATInterface {
|
||||
fmt.Fprintln(w, "<p>There is another interface using the CGNAT range.</p>")
|
||||
} else if err != nil {
|
||||
fmt.Fprintf(w, "<p>Could not check for CGNAT interfaces: %s</p>\n", html.EscapeString(err.Error()))
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"net/netip"
|
||||
"sort"
|
||||
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/dns/publicdns"
|
||||
"tailscale.com/net/dns/resolver"
|
||||
"tailscale.com/net/tsaddr"
|
||||
@@ -47,11 +49,28 @@ type Config struct {
|
||||
OnlyIPv6 bool
|
||||
}
|
||||
|
||||
func (c *Config) serviceIP() netip.Addr {
|
||||
var magicDNSDualStack = envknob.RegisterBool("TS_DEBUG_MAGIC_DNS_DUAL_STACK")
|
||||
|
||||
// serviceIPs returns the list of service IPs where MagicDNS is reachable.
|
||||
//
|
||||
// The provided knobs may be nil.
|
||||
func (c *Config) serviceIPs(knobs *controlknobs.Knobs) []netip.Addr {
|
||||
if c.OnlyIPv6 {
|
||||
return tsaddr.TailscaleServiceIPv6()
|
||||
return []netip.Addr{tsaddr.TailscaleServiceIPv6()}
|
||||
}
|
||||
return tsaddr.TailscaleServiceIP()
|
||||
|
||||
// TODO(bradfitz,mikeodr,raggi): include IPv6 here too; tailscale/tailscale#15404
|
||||
// And add a controlknobs knob to disable dual stack.
|
||||
//
|
||||
// For now, opt-in for testing.
|
||||
if magicDNSDualStack() {
|
||||
return []netip.Addr{
|
||||
tsaddr.TailscaleServiceIP(),
|
||||
tsaddr.TailscaleServiceIPv6(),
|
||||
}
|
||||
}
|
||||
|
||||
return []netip.Addr{tsaddr.TailscaleServiceIP()}
|
||||
}
|
||||
|
||||
// WriteToBufioWriter write a debug version of c for logs to w, omitting
|
||||
|
||||
@@ -307,7 +307,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
|
||||
// through quad-100.
|
||||
rcfg.Routes = routes
|
||||
rcfg.Routes["."] = cfg.DefaultResolvers
|
||||
ocfg.Nameservers = []netip.Addr{cfg.serviceIP()}
|
||||
ocfg.Nameservers = cfg.serviceIPs(m.knobs)
|
||||
return rcfg, ocfg, nil
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
|
||||
// or routes + MagicDNS, or just MagicDNS, or on an OS that cannot
|
||||
// split-DNS. Install a split config pointing at quad-100.
|
||||
rcfg.Routes = routes
|
||||
ocfg.Nameservers = []netip.Addr{cfg.serviceIP()}
|
||||
ocfg.Nameservers = cfg.serviceIPs(m.knobs)
|
||||
|
||||
var baseCfg *OSConfig // base config; non-nil if/when known
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func TestGetState(t *testing.T) {
|
||||
st, err := getState()
|
||||
st, err := getState("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -461,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 {
|
||||
@@ -755,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 {
|
||||
|
||||
@@ -596,11 +596,23 @@ func (d *derpProber) updateMap(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (d *derpProber) ProbeUDP(ipaddr string, port int) ProbeClass {
|
||||
initLabels := make(Labels)
|
||||
ip := net.ParseIP(ipaddr)
|
||||
|
||||
if ip.To4() != nil {
|
||||
initLabels["address_family"] = "ipv4"
|
||||
} else if ip.To16() != nil { // Will return an IPv4 as 16 byte, so ensure the check for IPv4 precedes this
|
||||
initLabels["address_family"] = "ipv6"
|
||||
} else {
|
||||
initLabels["address_family"] = "unknown"
|
||||
}
|
||||
|
||||
return ProbeClass{
|
||||
Probe: func(ctx context.Context) error {
|
||||
return derpProbeUDP(ctx, ipaddr, port)
|
||||
},
|
||||
Class: "derp_udp",
|
||||
Class: "derp_udp",
|
||||
Labels: initLabels,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -404,10 +404,14 @@ func (p *Probe) recordEndLocked(err error) {
|
||||
p.mSeconds.WithLabelValues("ok").Add(latency.Seconds())
|
||||
p.latencyHist.Value = latency
|
||||
p.latencyHist = p.latencyHist.Next()
|
||||
p.mAttempts.WithLabelValues("fail").Add(0)
|
||||
p.mSeconds.WithLabelValues("fail").Add(0)
|
||||
} else {
|
||||
p.latency = 0
|
||||
p.mAttempts.WithLabelValues("fail").Inc()
|
||||
p.mSeconds.WithLabelValues("fail").Add(latency.Seconds())
|
||||
p.mAttempts.WithLabelValues("ok").Add(0)
|
||||
p.mSeconds.WithLabelValues("ok").Add(0)
|
||||
}
|
||||
p.successHist.Value = p.succeeded
|
||||
p.successHist = p.successHist.Next()
|
||||
|
||||
@@ -839,15 +839,17 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey key.Machi
|
||||
|
||||
w.WriteHeader(200)
|
||||
for {
|
||||
if resBytes, ok := s.takeRawMapMessage(req.NodeKey); ok {
|
||||
if err := s.sendMapMsg(w, compress, resBytes); err != nil {
|
||||
s.logf("sendMapMsg of raw message: %v", err)
|
||||
return
|
||||
}
|
||||
if streaming {
|
||||
// Only send raw map responses to the streaming poll, to avoid a
|
||||
// non-streaming map request beating the streaming poll in a race and
|
||||
// potentially dropping the map response.
|
||||
if streaming {
|
||||
if resBytes, ok := s.takeRawMapMessage(req.NodeKey); ok {
|
||||
if err := s.sendMapMsg(w, compress, resBytes); err != nil {
|
||||
s.logf("sendMapMsg of raw message: %v", err)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if s.canGenerateAutomaticMapResponseFor(req.NodeKey) {
|
||||
|
||||
Reference in New Issue
Block a user