net/netns, net/dns/resolver, etc: make netmon required in most places

The goal is to move more network state accessors to netmon.Monitor
where they can be cheaper/cached. But first (this change and others)
we need to make sure the one netmon.Monitor is plumbed everywhere.

Some notable bits:

* tsdial.NewDialer is added, taking a now-required netmon

* because a tsdial.Dialer always has a netmon, anything taking both
  a Dialer and a NetMon is now redundant; take only the Dialer and
  get the NetMon from that if/when needed.

* netmon.NewStatic is added, primarily for tests

Updates tailscale/corp#10910
Updates tailscale/corp#18960
Updates #7967
Updates #3299

Change-Id: I877f9cb87618c4eb037cee098241d18da9c01691
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2024-04-26 22:06:20 -07:00 committed by Brad Fitzpatrick
parent 4f73a26ea5
commit 3672f29a4e
34 changed files with 228 additions and 86 deletions

View File

@ -15,6 +15,7 @@
"tailscale.com/derp" "tailscale.com/derp"
"tailscale.com/derp/derphttp" "tailscale.com/derp/derphttp"
"tailscale.com/net/netmon"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
@ -36,7 +37,8 @@ func startMesh(s *derp.Server) error {
func startMeshWithHost(s *derp.Server, host string) error { func startMeshWithHost(s *derp.Server, host string) error {
logf := logger.WithPrefix(log.Printf, fmt.Sprintf("mesh(%q): ", host)) logf := logger.WithPrefix(log.Printf, fmt.Sprintf("mesh(%q): ", host))
c, err := derphttp.NewClient(s.PrivateKey(), "https://"+host+"/derp", logf) netMon := netmon.NewStatic() // good enough for cmd/derper; no need for netns fanciness
c, err := derphttp.NewClient(s.PrivateKey(), "https://"+host+"/derp", logf, netMon)
if err != nil { if err != nil {
return err return err
} }

View File

@ -393,8 +393,8 @@ func run() (err error) {
// Always clean up, even if we're going to run the server. This covers cases // Always clean up, even if we're going to run the server. This covers cases
// such as when a system was rebooted without shutting down, or tailscaled // such as when a system was rebooted without shutting down, or tailscaled
// crashed, and would for example restore system DNS configuration. // crashed, and would for example restore system DNS configuration.
dns.CleanUp(logf, args.tunname) dns.CleanUp(logf, netMon, args.tunname)
router.CleanUp(logf, args.tunname) router.CleanUp(logf, netMon, args.tunname)
// If the cleanUp flag was passed, then exit. // If the cleanUp flag was passed, then exit.
if args.cleanUp { if args.cleanUp {
return nil return nil

View File

@ -56,6 +56,7 @@
"tailscale.com/util/singleflight" "tailscale.com/util/singleflight"
"tailscale.com/util/syspolicy" "tailscale.com/util/syspolicy"
"tailscale.com/util/systemd" "tailscale.com/util/systemd"
"tailscale.com/util/testenv"
"tailscale.com/util/zstdframe" "tailscale.com/util/zstdframe"
) )
@ -68,7 +69,7 @@ type Direct struct {
serverURL string // URL of the tailcontrol server serverURL string // URL of the tailcontrol server
clock tstime.Clock clock tstime.Clock
logf logger.Logf logf logger.Logf
netMon *netmon.Monitor // or nil netMon *netmon.Monitor // non-nil
health *health.Tracker health *health.Tracker
discoPubKey key.DiscoPublic discoPubKey key.DiscoPublic
getMachinePrivKey func() (key.MachinePrivate, error) getMachinePrivKey func() (key.MachinePrivate, error)
@ -120,10 +121,9 @@ type Options struct {
Hostinfo *tailcfg.Hostinfo // non-nil passes ownership, nil means to use default using os.Hostname, etc Hostinfo *tailcfg.Hostinfo // non-nil passes ownership, nil means to use default using os.Hostname, etc
DiscoPublicKey key.DiscoPublic DiscoPublicKey key.DiscoPublic
Logf logger.Logf Logf logger.Logf
HTTPTestClient *http.Client // optional HTTP client to use (for tests only) HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
NoiseTestClient *http.Client // optional HTTP client to use for noise RPCs (tests only) NoiseTestClient *http.Client // optional HTTP client to use for noise RPCs (tests only)
DebugFlags []string // debug settings to send to control DebugFlags []string // debug settings to send to control
NetMon *netmon.Monitor // optional network monitor
HealthTracker *health.Tracker HealthTracker *health.Tracker
PopBrowserURL func(url string) // optional func to open browser PopBrowserURL func(url string) // optional func to open browser
OnClientVersion func(*tailcfg.ClientVersion) // optional func to inform GUI of client version status OnClientVersion func(*tailcfg.ClientVersion) // optional func to inform GUI of client version status
@ -213,6 +213,19 @@ func NewDirect(opts Options) (*Direct, error) {
if opts.GetMachinePrivateKey == nil { if opts.GetMachinePrivateKey == nil {
return nil, errors.New("controlclient.New: no GetMachinePrivateKey specified") return nil, errors.New("controlclient.New: no GetMachinePrivateKey specified")
} }
if opts.Dialer == nil {
if testenv.InTest() {
panic("no Dialer")
}
return nil, errors.New("controlclient.New: no Dialer specified")
}
netMon := opts.Dialer.NetMon()
if netMon == nil {
if testenv.InTest() {
panic("no NetMon in Dialer")
}
return nil, errors.New("controlclient.New: Dialer has nil NetMon")
}
if opts.ControlKnobs == nil { if opts.ControlKnobs == nil {
opts.ControlKnobs = &controlknobs.Knobs{} opts.ControlKnobs = &controlknobs.Knobs{}
} }
@ -233,9 +246,8 @@ func NewDirect(opts Options) (*Direct, error) {
dnsCache := &dnscache.Resolver{ dnsCache := &dnscache.Resolver{
Forward: dnscache.Get().Forward, // use default cache's forwarder Forward: dnscache.Get().Forward, // use default cache's forwarder
UseLastGood: true, UseLastGood: true,
LookupIPFallback: dnsfallback.MakeLookupFunc(opts.Logf, opts.NetMon), LookupIPFallback: dnsfallback.MakeLookupFunc(opts.Logf, netMon),
Logf: opts.Logf, Logf: opts.Logf,
NetMon: opts.NetMon,
} }
httpc := opts.HTTPTestClient httpc := opts.HTTPTestClient
@ -272,7 +284,7 @@ func NewDirect(opts Options) (*Direct, error) {
authKey: opts.AuthKey, authKey: opts.AuthKey,
discoPubKey: opts.DiscoPublicKey, discoPubKey: opts.DiscoPublicKey,
debugFlags: opts.DebugFlags, debugFlags: opts.DebugFlags,
netMon: opts.NetMon, netMon: netMon,
health: opts.HealthTracker, health: opts.HealthTracker,
skipIPForwardingCheck: opts.SkipIPForwardingCheck, skipIPForwardingCheck: opts.SkipIPForwardingCheck,
pinger: opts.Pinger, pinger: opts.Pinger,

View File

@ -14,6 +14,7 @@
"tailscale.com/hostinfo" "tailscale.com/hostinfo"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/net/netmon"
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
@ -31,7 +32,7 @@ func TestNewDirect(t *testing.T) {
GetMachinePrivateKey: func() (key.MachinePrivate, error) { GetMachinePrivateKey: func() (key.MachinePrivate, error) {
return k, nil return k, nil
}, },
Dialer: new(tsdial.Dialer), Dialer: tsdial.NewDialer(netmon.NewStatic()),
} }
c, err := NewDirect(opts) c, err := NewDirect(opts)
if err != nil { if err != nil {
@ -107,7 +108,7 @@ func TestTsmpPing(t *testing.T) {
GetMachinePrivateKey: func() (key.MachinePrivate, error) { GetMachinePrivateKey: func() (key.MachinePrivate, error) {
return k, nil return k, nil
}, },
Dialer: new(tsdial.Dialer), Dialer: tsdial.NewDialer(netmon.NewStatic()),
} }
c, err := NewDirect(opts) c, err := NewDirect(opts)

View File

@ -16,6 +16,7 @@
"golang.org/x/net/http2" "golang.org/x/net/http2"
"tailscale.com/control/controlhttp" "tailscale.com/control/controlhttp"
"tailscale.com/net/netmon"
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
@ -73,7 +74,7 @@ func (tt noiseClientTest) run(t *testing.T) {
}) })
defer hs.Close() defer hs.Close()
dialer := new(tsdial.Dialer) dialer := tsdial.NewDialer(netmon.NewStatic())
nc, err := NewNoiseClient(NoiseOpts{ nc, err := NewNoiseClient(NoiseOpts{
PrivKey: clientPrivate, PrivKey: clientPrivate,
ServerPubKey: serverPrivate.Public(), ServerPubKey: serverPrivate.Public(),

View File

@ -393,7 +393,6 @@ func (a *Dialer) resolver() *dnscache.Resolver {
LookupIPFallback: dnsfallback.MakeLookupFunc(a.logf, a.NetMon), LookupIPFallback: dnsfallback.MakeLookupFunc(a.logf, a.NetMon),
UseLastGood: true, UseLastGood: true,
Logf: a.Logf, // not a.logf method; we want to propagate nil-ness Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
NetMon: a.NetMon,
} }
} }
@ -412,7 +411,6 @@ func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, addr netip.Addr,
SingleHostStaticResult: []netip.Addr{addr}, SingleHostStaticResult: []netip.Addr{addr},
SingleHost: u.Hostname(), SingleHost: u.Hostname(),
Logf: a.Logf, // not a.logf method; we want to propagate nil-ness Logf: a.Logf, // not a.logf method; we want to propagate nil-ness
NetMon: a.NetMon,
} }
} else { } else {
dns = a.resolver() dns = a.resolver()

View File

@ -22,6 +22,7 @@
"tailscale.com/control/controlbase" "tailscale.com/control/controlbase"
"tailscale.com/net/dnscache" "tailscale.com/net/dnscache"
"tailscale.com/net/netmon"
"tailscale.com/net/socks5" "tailscale.com/net/socks5"
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
@ -199,14 +200,17 @@ func testControlHTTP(t *testing.T, param httpTestParam) {
defer cancel() defer cancel()
} }
netMon := netmon.NewStatic()
dialer := tsdial.NewDialer(netMon)
a := &Dialer{ a := &Dialer{
Hostname: "localhost", Hostname: "localhost",
HTTPPort: strconv.Itoa(httpLn.Addr().(*net.TCPAddr).Port), HTTPPort: strconv.Itoa(httpLn.Addr().(*net.TCPAddr).Port),
HTTPSPort: strconv.Itoa(httpsLn.Addr().(*net.TCPAddr).Port), HTTPSPort: strconv.Itoa(httpsLn.Addr().(*net.TCPAddr).Port),
MachineKey: client, MachineKey: client,
ControlKey: server.Public(), ControlKey: server.Public(),
NetMon: netMon,
ProtocolVersion: testProtocolVersion, ProtocolVersion: testProtocolVersion,
Dialer: new(tsdial.Dialer).SystemDial, Dialer: dialer.SystemDial,
Logf: t.Logf, Logf: t.Logf,
omitCertErrorLogging: true, omitCertErrorLogging: true,
testFallbackDelay: fallbackDelay, testFallbackDelay: fallbackDelay,
@ -643,7 +647,7 @@ func TestDialPlan(t *testing.T) {
dialer := closeTrackDialer{ dialer := closeTrackDialer{
t: t, t: t,
inner: new(tsdial.Dialer).SystemDial, inner: tsdial.NewDialer(netmon.NewStatic()).SystemDial,
conns: make(map[*closeTrackConn]bool), conns: make(map[*closeTrackConn]bool),
} }
defer dialer.Done() defer dialer.Done()

View File

@ -71,7 +71,7 @@ type Client struct {
privateKey key.NodePrivate privateKey key.NodePrivate
logf logger.Logf logf logger.Logf
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand netMon *netmon.Monitor // always non-nil
dialer func(ctx context.Context, network, addr string) (net.Conn, error) dialer func(ctx context.Context, network, addr string) (net.Conn, error)
// Either url or getRegion is non-nil: // Either url or getRegion is non-nil:
@ -116,9 +116,11 @@ func (c *Client) String() string {
// NewRegionClient returns a new DERP-over-HTTP client. It connects lazily. // NewRegionClient returns a new DERP-over-HTTP client. It connects lazily.
// To trigger a connection, use Connect. // To trigger a connection, use Connect.
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
// The healthTracker parameter is also optional. // The healthTracker parameter is also optional.
func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, netMon *netmon.Monitor, getRegion func() *tailcfg.DERPRegion) *Client { func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, netMon *netmon.Monitor, getRegion func() *tailcfg.DERPRegion) *Client {
if netMon == nil {
panic("nil netMon")
}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
c := &Client{ c := &Client{
privateKey: privateKey, privateKey: privateKey,
@ -140,7 +142,10 @@ func NewNetcheckClient(logf logger.Logf) *Client {
// NewClient returns a new DERP-over-HTTP client. It connects lazily. // NewClient returns a new DERP-over-HTTP client. It connects lazily.
// To trigger a connection, use Connect. // To trigger a connection, use Connect.
func NewClient(privateKey key.NodePrivate, serverURL string, logf logger.Logf) (*Client, error) { func NewClient(privateKey key.NodePrivate, serverURL string, logf logger.Logf, netMon *netmon.Monitor) (*Client, error) {
if netMon == nil {
panic("nil netMon")
}
u, err := url.Parse(serverURL) u, err := url.Parse(serverURL)
if err != nil { if err != nil {
return nil, fmt.Errorf("derphttp.NewClient: %v", err) return nil, fmt.Errorf("derphttp.NewClient: %v", err)
@ -157,6 +162,7 @@ func NewClient(privateKey key.NodePrivate, serverURL string, logf logger.Logf) (
ctx: ctx, ctx: ctx,
cancelCtx: cancel, cancelCtx: cancel,
clock: tstime.StdClock{}, clock: tstime.StdClock{},
netMon: netMon,
} }
return c, nil return c, nil
} }

View File

@ -16,12 +16,15 @@
"time" "time"
"tailscale.com/derp" "tailscale.com/derp"
"tailscale.com/net/netmon"
"tailscale.com/types/key" "tailscale.com/types/key"
) )
func TestSendRecv(t *testing.T) { func TestSendRecv(t *testing.T) {
serverPrivateKey := key.NewNode() serverPrivateKey := key.NewNode()
netMon := netmon.NewStatic()
const numClients = 3 const numClients = 3
var clientPrivateKeys []key.NodePrivate var clientPrivateKeys []key.NodePrivate
var clientKeys []key.NodePublic var clientKeys []key.NodePublic
@ -68,7 +71,7 @@ func TestSendRecv(t *testing.T) {
}() }()
for i := range numClients { for i := range numClients {
key := clientPrivateKeys[i] key := clientPrivateKeys[i]
c, err := NewClient(key, serverURL, t.Logf) c, err := NewClient(key, serverURL, t.Logf, netMon)
if err != nil { if err != nil {
t.Fatalf("client %d: %v", i, err) t.Fatalf("client %d: %v", i, err)
} }
@ -183,7 +186,7 @@ func TestPing(t *testing.T) {
} }
}() }()
c, err := NewClient(key.NewNode(), serverURL, t.Logf) c, err := NewClient(key.NewNode(), serverURL, t.Logf, netmon.NewStatic())
if err != nil { if err != nil {
t.Fatalf("NewClient: %v", err) t.Fatalf("NewClient: %v", err)
} }
@ -236,7 +239,7 @@ func newTestServer(t *testing.T, k key.NodePrivate) (serverURL string, s *derp.S
} }
func newWatcherClient(t *testing.T, watcherPrivateKey key.NodePrivate, serverToWatchURL string) (c *Client) { func newWatcherClient(t *testing.T, watcherPrivateKey key.NodePrivate, serverToWatchURL string) (c *Client) {
c, err := NewClient(watcherPrivateKey, serverToWatchURL, t.Logf) c, err := NewClient(watcherPrivateKey, serverToWatchURL, t.Logf, netmon.NewStatic())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -353,6 +353,12 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
e := sys.Engine.Get() e := sys.Engine.Get()
store := sys.StateStore.Get() store := sys.StateStore.Get()
dialer := sys.Dialer.Get() dialer := sys.Dialer.Get()
if dialer == nil {
return nil, errors.New("dialer to NewLocalBackend must be set")
}
if dialer.NetMon() == nil {
return nil, errors.New("dialer to NewLocalBackend must have a NetMon")
}
_ = sys.MagicSock.Get() // or panic _ = sys.MagicSock.Get() // or panic
goos := envknob.GOOS() goos := envknob.GOOS()
@ -1762,7 +1768,6 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
HTTPTestClient: httpTestClient, HTTPTestClient: httpTestClient,
DiscoPublicKey: discoPublic, DiscoPublicKey: discoPublic,
DebugFlags: debugFlags, DebugFlags: debugFlags,
NetMon: b.sys.NetMon.Get(),
HealthTracker: b.health, HealthTracker: b.health,
Pinger: b, Pinger: b,
PopBrowserURL: b.tellClientToBrowseToURL, PopBrowserURL: b.tellClientToBrowseToURL,

View File

@ -2273,7 +2273,6 @@ func TestOnTailnetDefaultAutoUpdate(t *testing.T) {
t.Skip("test broken on macOS; see https://github.com/tailscale/tailscale/issues/11894") t.Skip("test broken on macOS; see https://github.com/tailscale/tailscale/issues/11894")
} }
tests := []struct { tests := []struct {
desc string
before, after opt.Bool before, after opt.Bool
tailnetDefault bool tailnetDefault bool
}{ }{
@ -2309,7 +2308,7 @@ func TestOnTailnetDefaultAutoUpdate(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(fmt.Sprintf("before=%s after=%s", tt.before, tt.after), func(t *testing.T) { t.Run(fmt.Sprintf("before=%s,after=%s", tt.before, tt.after), func(t *testing.T) {
b := newTestBackend(t) b := newTestBackend(t)
p := ipn.NewPrefs() p := ipn.NewPrefs()
p.AutoUpdate.Apply = tt.before p.AutoUpdate.Apply = tt.before

View File

@ -20,6 +20,8 @@
"tailscale.com/hostinfo" "tailscale.com/hostinfo"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/store/mem" "tailscale.com/ipn/store/mem"
"tailscale.com/net/netmon"
"tailscale.com/net/tsdial"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tka" "tailscale.com/tka"
"tailscale.com/types/key" "tailscale.com/types/key"
@ -50,6 +52,7 @@ func fakeControlClient(t *testing.T, c *http.Client) *controlclient.Auto {
HTTPTestClient: c, HTTPTestClient: c,
NoiseTestClient: c, NoiseTestClient: c,
Observer: observerFunc(func(controlclient.Status) {}), Observer: observerFunc(func(controlclient.Status) {}),
Dialer: tsdial.NewDialer(netmon.NewStatic()),
} }
cc, err := controlclient.NewNoStart(opts) cc, err := controlclient.NewNoStart(opts)

View File

@ -28,6 +28,7 @@
"tailscale.com/ipn/store/mem" "tailscale.com/ipn/store/mem"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tsd" "tailscale.com/tsd"
"tailscale.com/types/logger"
"tailscale.com/types/logid" "tailscale.com/types/logid"
"tailscale.com/types/netmap" "tailscale.com/types/netmap"
"tailscale.com/util/mak" "tailscale.com/util/mak"
@ -663,14 +664,21 @@ func mustCreateURL(t *testing.T, u string) url.URL {
} }
func newTestBackend(t *testing.T) *LocalBackend { func newTestBackend(t *testing.T) *LocalBackend {
var logf logger.Logf = logger.Discard
const debug = true
if debug {
logf = logger.WithPrefix(t.Logf, "... ")
}
sys := &tsd.System{} sys := &tsd.System{}
e, err := wgengine.NewUserspaceEngine(t.Logf, wgengine.Config{SetSubsystem: sys.Set}) e, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{SetSubsystem: sys.Set})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
sys.Set(e) sys.Set(e)
sys.Set(new(mem.Store)) sys.Set(new(mem.Store))
b, err := NewLocalBackend(t.Logf, logid.PublicID{}, sys, 0)
b, err := NewLocalBackend(logf, logid.PublicID{}, sys, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -678,7 +686,7 @@ func newTestBackend(t *testing.T) *LocalBackend {
dir := t.TempDir() dir := t.TempDir()
b.SetVarRoot(dir) b.SetVarRoot(dir)
pm := must.Get(newProfileManager(new(mem.Store), t.Logf)) pm := must.Get(newProfileManager(new(mem.Store), logf))
pm.currentProfile = &ipn.LoginProfile{ID: "id0"} pm.currentProfile = &ipn.LoginProfile{ID: "id0"}
b.pm = pm b.pm = pm

View File

@ -93,11 +93,16 @@ func SockstatLogID(logID logid.PublicID) logid.PrivateID {
// On platforms that do not support sockstat logging, a nil Logger will be returned. // On platforms that do not support sockstat logging, a nil Logger will be returned.
// The returned Logger is not yet enabled, and must be shut down with Shutdown when it is no longer needed. // The returned Logger is not yet enabled, and must be shut down with Shutdown when it is no longer needed.
// Logs will be uploaded to the log server using a new log ID derived from the provided backend logID. // Logs will be uploaded to the log server using a new log ID derived from the provided backend logID.
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. //
// The netMon parameter is optional. It should be specified in environments where
// Tailscaled is manipulating the routing table.
func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor, health *health.Tracker) (*Logger, error) { func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor, health *health.Tracker) (*Logger, error) {
if !sockstats.IsAvailable { if !sockstats.IsAvailable {
return nil, nil return nil, nil
} }
if netMon == nil {
netMon = netmon.NewStatic()
}
if err := os.MkdirAll(logdir, 0755); err != nil && !os.IsExist(err) { if err := os.MkdirAll(logdir, 0755); err != nil && !os.IsExist(err) {
return nil, err return nil, err

View File

@ -447,8 +447,8 @@ func tryFixLogStateLocation(dir, cmdname string, logf logger.Logf) {
// New returns a new log policy (a logger and its instance ID) for a given // New returns a new log policy (a logger and its instance ID) for a given
// collection name. // collection name.
// //
// The netMon parameter is optional; if non-nil it's used to do faster // The netMon parameter is optional. It should be specified in environments where
// interface lookups. // Tailscaled is manipulating the routing table.
// //
// The logf parameter is optional; if non-nil, information logs (e.g. when // The logf parameter is optional; if non-nil, information logs (e.g. when
// migrating state) are sent to that logger, and global changes to the log // migrating state) are sent to that logger, and global changes to the log
@ -459,6 +459,9 @@ func New(collection string, netMon *netmon.Monitor, health *health.Tracker, logf
// NewWithConfigPath is identical to New, but uses the specified directory and // NewWithConfigPath is identical to New, but uses the specified directory and
// command name. If either is empty, it derives them automatically. // command name. If either is empty, it derives them automatically.
//
// The netMon parameter is optional. It should be specified in environments where
// Tailscaled is manipulating the routing table.
func NewWithConfigPath(collection, dir, cmdName string, netMon *netmon.Monitor, health *health.Tracker, logf logger.Logf) *Policy { func NewWithConfigPath(collection, dir, cmdName string, netMon *netmon.Monitor, health *health.Tracker, logf logger.Logf) *Policy {
var lflags int var lflags int
if term.IsTerminal(2) || runtime.GOOS == "windows" { if term.IsTerminal(2) || runtime.GOOS == "windows" {
@ -681,8 +684,12 @@ func (p *Policy) Shutdown(ctx context.Context) error {
// - If TLS connection fails, try again using LetsEncrypt's built-in root certificate, // - If TLS connection fails, try again using LetsEncrypt's built-in root certificate,
// for the benefit of older OS platforms which might not include it. // for the benefit of older OS platforms which might not include it.
// //
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. // The netMon parameter is optional. It should be specified in environments where
// Tailscaled is manipulating the routing table.
func MakeDialFunc(netMon *netmon.Monitor, logf logger.Logf) func(ctx context.Context, netw, addr string) (net.Conn, error) { func MakeDialFunc(netMon *netmon.Monitor, logf logger.Logf) func(ctx context.Context, netw, addr string) (net.Conn, error) {
if netMon == nil {
netMon = netmon.NewStatic()
}
return func(ctx context.Context, netw, addr string) (net.Conn, error) { return func(ctx context.Context, netw, addr string) (net.Conn, error) {
return dialContext(ctx, netw, addr, netMon, logf) return dialContext(ctx, netw, addr, netMon, logf)
} }
@ -725,7 +732,6 @@ func dialContext(ctx context.Context, netw, addr string, netMon *netmon.Monitor,
Forward: dnscache.Get().Forward, // use default cache's forwarder Forward: dnscache.Get().Forward, // use default cache's forwarder
UseLastGood: true, UseLastGood: true,
LookupIPFallback: dnsfallback.MakeLookupFunc(logf, netMon), LookupIPFallback: dnsfallback.MakeLookupFunc(logf, netMon),
NetMon: netMon,
} }
dialer := dnscache.Dialer(nd.DialContext, dnsCache) dialer := dnscache.Dialer(nd.DialContext, dnsCache)
c, err = dialer(ctx, netw, addr) c, err = dialer(ctx, netw, addr)
@ -738,7 +744,8 @@ func dialContext(ctx context.Context, netw, addr string, netMon *netmon.Monitor,
// NewLogtailTransport returns an HTTP Transport particularly suited to uploading // NewLogtailTransport returns an HTTP Transport particularly suited to uploading
// logs to the given host name. See DialContext for details on how it works. // logs to the given host name. See DialContext for details on how it works.
// //
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. // The netMon parameter is optional. It should be specified in environments where
// Tailscaled is manipulating the routing table.
// //
// The logf parameter is optional; if non-nil, logs are printed using the // The logf parameter is optional; if non-nil, logs are printed using the
// provided function; if nil, log.Printf will be used instead. // provided function; if nil, log.Printf will be used instead.
@ -746,6 +753,9 @@ func NewLogtailTransport(host string, netMon *netmon.Monitor, health *health.Tra
if testenv.InTest() { if testenv.InTest() {
return noopPretendSuccessTransport{} return noopPretendSuccessTransport{}
} }
if netMon == nil {
netMon = netmon.NewStatic()
}
// Start with a copy of http.DefaultTransport and tweak it a bit. // Start with a copy of http.DefaultTransport and tweak it a bit.
tr := http.DefaultTransport.(*http.Transport).Clone() tr := http.DefaultTransport.(*http.Transport).Clone()

View File

@ -232,7 +232,7 @@ func (l *Logger) SetVerbosityLevel(level int) {
atomic.StoreInt64(&l.stderrLevel, int64(level)) atomic.StoreInt64(&l.stderrLevel, int64(level))
} }
// SetNetMon sets the optional the network monitor. // SetNetMon sets the network monitor.
// //
// It should not be changed concurrently with log writes and should // It should not be changed concurrently with log writes and should
// only be set once. // only be set once.

View File

@ -55,15 +55,17 @@ type Manager struct {
} }
// NewManagers created a new manager from the given config. // NewManagers created a new manager from the given config.
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. func NewManager(logf logger.Logf, oscfg OSConfigurator, health *health.Tracker, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector, knobs *controlknobs.Knobs) *Manager {
func NewManager(logf logger.Logf, oscfg OSConfigurator, netMon *netmon.Monitor, health *health.Tracker, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector, knobs *controlknobs.Knobs) *Manager {
if dialer == nil { if dialer == nil {
panic("nil Dialer") panic("nil Dialer")
} }
if dialer.NetMon() == nil {
panic("Dialer has nil NetMon")
}
logf = logger.WithPrefix(logf, "dns: ") logf = logger.WithPrefix(logf, "dns: ")
m := &Manager{ m := &Manager{
logf: logf, logf: logf,
resolver: resolver.New(logf, netMon, linkSel, dialer, knobs), resolver: resolver.New(logf, linkSel, dialer, knobs),
os: oscfg, os: oscfg,
health: health, health: health,
} }
@ -454,13 +456,15 @@ func (m *Manager) FlushCaches() error {
// CleanUp restores the system DNS configuration to its original state // CleanUp restores the system DNS configuration to its original state
// in case the Tailscale daemon terminated without closing the router. // in case the Tailscale daemon terminated without closing the router.
// No other state needs to be instantiated before this runs. // No other state needs to be instantiated before this runs.
func CleanUp(logf logger.Logf, interfaceName string) { func CleanUp(logf logger.Logf, netMon *netmon.Monitor, interfaceName string) {
oscfg, err := NewOSConfigurator(logf, nil, interfaceName) oscfg, err := NewOSConfigurator(logf, nil, interfaceName)
if err != nil { if err != nil {
logf("creating dns cleanup: %v", err) logf("creating dns cleanup: %v", err)
return return
} }
dns := NewManager(logf, oscfg, nil, nil, &tsdial.Dialer{Logf: logf}, nil, nil) d := &tsdial.Dialer{Logf: logf}
d.SetNetMon(netMon)
dns := NewManager(logf, oscfg, nil, d, nil, nil)
if err := dns.Down(); err != nil { if err := dns.Down(); err != nil {
logf("dns down: %v", err) logf("dns down: %v", err)
} }

View File

@ -15,6 +15,7 @@
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
dns "golang.org/x/net/dns/dnsmessage" dns "golang.org/x/net/dns/dnsmessage"
"tailscale.com/net/netmon"
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
@ -87,7 +88,7 @@ func TestDNSOverTCP(t *testing.T) {
SearchDomains: fqdns("coffee.shop"), SearchDomains: fqdns("coffee.shop"),
}, },
} }
m := NewManager(t.Logf, &f, nil, nil, new(tsdial.Dialer), nil, nil) m := NewManager(t.Logf, &f, nil, tsdial.NewDialer(netmon.NewStatic()), nil, nil)
m.resolver.TestOnlySetHook(f.SetResolver) m.resolver.TestOnlySetHook(f.SetResolver)
m.Set(Config{ m.Set(Config{
Hosts: hosts( Hosts: hosts(
@ -172,7 +173,7 @@ func TestDNSOverTCP_TooLarge(t *testing.T) {
SearchDomains: fqdns("coffee.shop"), SearchDomains: fqdns("coffee.shop"),
}, },
} }
m := NewManager(log, &f, nil, nil, new(tsdial.Dialer), nil, nil) m := NewManager(log, &f, nil, tsdial.NewDialer(netmon.NewStatic()), nil, nil)
m.resolver.TestOnlySetHook(f.SetResolver) m.resolver.TestOnlySetHook(f.SetResolver)
m.Set(Config{ m.Set(Config{
Hosts: hosts("andrew.ts.com.", "1.2.3.4"), Hosts: hosts("andrew.ts.com.", "1.2.3.4"),

View File

@ -12,6 +12,7 @@
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
"tailscale.com/net/dns/resolver" "tailscale.com/net/dns/resolver"
"tailscale.com/net/netmon"
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/types/dnstype" "tailscale.com/types/dnstype"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
@ -613,7 +614,7 @@ func TestManager(t *testing.T) {
SplitDNS: test.split, SplitDNS: test.split,
BaseConfig: test.bs, BaseConfig: test.bs,
} }
m := NewManager(t.Logf, &f, nil, nil, new(tsdial.Dialer), nil, nil) m := NewManager(t.Logf, &f, nil, tsdial.NewDialer(netmon.NewStatic()), nil, nil)
m.resolver.TestOnlySetHook(f.SetResolver) m.resolver.TestOnlySetHook(f.SetResolver)
if err := m.Set(test.in); err != nil { if err := m.Set(test.in); err != nil {

View File

@ -10,7 +10,6 @@
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math/rand"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
@ -186,7 +185,7 @@ type resolverAndDelay struct {
// forwarder forwards DNS packets to a number of upstream nameservers. // forwarder forwards DNS packets to a number of upstream nameservers.
type forwarder struct { type forwarder struct {
logf logger.Logf logf logger.Logf
netMon *netmon.Monitor netMon *netmon.Monitor // always non-nil
linkSel ForwardLinkSelector // TODO(bradfitz): remove this when tsdial.Dialer absorbs it linkSel ForwardLinkSelector // TODO(bradfitz): remove this when tsdial.Dialer absorbs it
dialer *tsdial.Dialer dialer *tsdial.Dialer
@ -214,11 +213,10 @@ type forwarder struct {
cloudHostFallback []resolverAndDelay cloudHostFallback []resolverAndDelay
} }
func init() {
rand.Seed(time.Now().UnixNano())
}
func newForwarder(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkSelector, dialer *tsdial.Dialer, knobs *controlknobs.Knobs) *forwarder { func newForwarder(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkSelector, dialer *tsdial.Dialer, knobs *controlknobs.Knobs) *forwarder {
if netMon == nil {
panic("nil netMon")
}
f := &forwarder{ f := &forwarder{
logf: logger.WithPrefix(logf, "forward: "), logf: logger.WithPrefix(logf, "forward: "),
netMon: netMon, netMon: netMon,
@ -410,7 +408,6 @@ func (f *forwarder) getKnownDoHClientForProvider(urlBase string) (c *http.Client
SingleHost: dohURL.Hostname(), SingleHost: dohURL.Hostname(),
SingleHostStaticResult: allIPs, SingleHostStaticResult: allIPs,
Logf: f.logf, Logf: f.logf,
NetMon: f.netMon,
}) })
c = &http.Client{ c = &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
@ -584,7 +581,7 @@ func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDe
} }
// Kick off the race between the UDP and TCP queries. // Kick off the race between the UDP and TCP queries.
rh := race.New[[]byte](timeout, firstUDP, thenTCP) rh := race.New(timeout, firstUDP, thenTCP)
resp, err := rh.Start(ctx) resp, err := rh.Start(ctx)
if err == nil { if err == nil {
return resp, nil return resp, nil

View File

@ -181,7 +181,7 @@ func WriteRoutes(w *bufio.Writer, routes map[dnsname.FQDN][]*dnstype.Resolver) {
// it delegates to upstream nameservers if any are set. // it delegates to upstream nameservers if any are set.
type Resolver struct { type Resolver struct {
logf logger.Logf logf logger.Logf
netMon *netmon.Monitor // or nil netMon *netmon.Monitor // non-nil
dialer *tsdial.Dialer // non-nil dialer *tsdial.Dialer // non-nil
saveConfigForTests func(cfg Config) // used in tests to capture resolver config saveConfigForTests func(cfg Config) // used in tests to capture resolver config
// forwarder forwards requests to upstream nameservers. // forwarder forwards requests to upstream nameservers.
@ -205,11 +205,14 @@ type ForwardLinkSelector interface {
} }
// New returns a new resolver. // New returns a new resolver.
// netMon optionally specifies a network monitor to use for socket rebinding. func New(logf logger.Logf, linkSel ForwardLinkSelector, dialer *tsdial.Dialer, knobs *controlknobs.Knobs) *Resolver {
func New(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkSelector, dialer *tsdial.Dialer, knobs *controlknobs.Knobs) *Resolver {
if dialer == nil { if dialer == nil {
panic("nil Dialer") panic("nil Dialer")
} }
netMon := dialer.NetMon()
if netMon == nil {
logf("nil netMon")
}
r := &Resolver{ r := &Resolver{
logf: logger.WithPrefix(logf, "resolver: "), logf: logger.WithPrefix(logf, "resolver: "),
netMon: netMon, netMon: netMon,

View File

@ -28,6 +28,7 @@
"tailscale.com/net/tsdial" "tailscale.com/net/tsdial"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/types/dnstype" "tailscale.com/types/dnstype"
"tailscale.com/types/logger"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
) )
@ -313,7 +314,11 @@ func TestRDNSNameToIPv6(t *testing.T) {
} }
func newResolver(t testing.TB) *Resolver { func newResolver(t testing.TB) *Resolver {
return New(t.Logf, nil /* no network monitor */, nil /* no link selector */, new(tsdial.Dialer), nil /* no control knobs */) return New(t.Logf,
nil, // no link selector
tsdial.NewDialer(netmon.NewStatic()),
nil, // no control knobs
)
} }
func TestResolveLocal(t *testing.T) { func TestResolveLocal(t *testing.T) {
@ -1009,7 +1014,13 @@ func TestForwardLinkSelection(t *testing.T) {
// routes differently. // routes differently.
specialIP := netaddr.IPv4(1, 2, 3, 4) specialIP := netaddr.IPv4(1, 2, 3, 4)
fwd := newForwarder(t.Logf, nil, linkSelFunc(func(ip netip.Addr) string { netMon, err := netmon.New(logger.WithPrefix(t.Logf, ".... netmon: "))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { netMon.Close() })
fwd := newForwarder(t.Logf, netMon, linkSelFunc(func(ip netip.Addr) string {
if ip == netaddr.IPv4(1, 2, 3, 4) { if ip == netaddr.IPv4(1, 2, 3, 4) {
return "special" return "special"
} }

View File

@ -19,7 +19,6 @@
"time" "time"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/net/netmon"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/cloudenv" "tailscale.com/util/cloudenv"
"tailscale.com/util/singleflight" "tailscale.com/util/singleflight"
@ -90,11 +89,6 @@ type Resolver struct {
// be added to all log messages printed with this logger. // be added to all log messages printed with this logger.
Logf logger.Logf Logf logger.Logf
// NetMon optionally provides a netmon.Monitor to use to get the current
// (cached) network interface.
// If nil, the interface will be looked up dynamically.
NetMon *netmon.Monitor
sf singleflight.Group[string, ipRes] sf singleflight.Group[string, ipRes]
mu sync.Mutex mu sync.Mutex

View File

@ -1657,7 +1657,6 @@ func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeP
Forward: net.DefaultResolver, Forward: net.DefaultResolver,
UseLastGood: true, UseLastGood: true,
Logf: c.logf, Logf: c.logf,
NetMon: c.NetMon,
} }
} }
resolver := c.resolver resolver := c.resolver

View File

@ -24,7 +24,6 @@
"tailscale.com/net/stun/stuntest" "tailscale.com/net/stun/stuntest"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/types/logger"
) )
func TestHairpinSTUN(t *testing.T) { func TestHairpinSTUN(t *testing.T) {
@ -157,14 +156,8 @@ func TestHairpinWait(t *testing.T) {
} }
func newTestClient(t testing.TB) *Client { func newTestClient(t testing.TB) *Client {
netMon, err := netmon.New(logger.WithPrefix(t.Logf, "... netmon: "))
if err != nil {
t.Fatalf("netmon.New: %v", err)
}
t.Cleanup(func() { netMon.Close() })
c := &Client{ c := &Client{
NetMon: netMon, NetMon: netmon.NewStatic(),
Logf: t.Logf, Logf: t.Logf,
} }
return c return c

View File

@ -24,12 +24,15 @@
// to bind, errors will be returned, if one or both protocols can bind no error // to bind, errors will be returned, if one or both protocols can bind no error
// is returned. // is returned.
func (c *Client) Standalone(ctx context.Context, bindAddr string) error { func (c *Client) Standalone(ctx context.Context, bindAddr string) error {
if c.NetMon == nil {
panic("netcheck.Client.NetMon must be set")
}
if bindAddr == "" { if bindAddr == "" {
bindAddr = ":0" bindAddr = ":0"
} }
var errs []error var errs []error
u4, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, nil)).ListenPacket(ctx, "udp4", bindAddr) u4, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, c.NetMon)).ListenPacket(ctx, "udp4", bindAddr)
if err != nil { if err != nil {
c.logf("udp4: %v", err) c.logf("udp4: %v", err)
errs = append(errs, err) errs = append(errs, err)
@ -37,7 +40,7 @@ func (c *Client) Standalone(ctx context.Context, bindAddr string) error {
go readPackets(ctx, c.logf, u4, c.ReceiveSTUNPacket) go readPackets(ctx, c.logf, u4, c.ReceiveSTUNPacket)
} }
u6, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, nil)).ListenPacket(ctx, "udp6", bindAddr) u6, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, c.NetMon)).ListenPacket(ctx, "udp6", bindAddr)
if err != nil { if err != nil {
c.logf("udp6: %v", err) c.logf("udp6: %v", err)
errs = append(errs, err) errs = append(errs, err)

View File

@ -55,6 +55,7 @@ type Monitor struct {
om osMon // nil means not supported on this platform om osMon // nil means not supported on this platform
change chan bool // send false to wake poller, true to also force ChangeDeltas be sent change chan bool // send false to wake poller, true to also force ChangeDeltas be sent
stop chan struct{} // closed on Stop stop chan struct{} // closed on Stop
static bool // static Monitor that doesn't actually monitor
// Things that must be set early, before use, // Things that must be set early, before use,
// and not change at runtime. // and not change at runtime.
@ -139,6 +140,17 @@ func New(logf logger.Logf) (*Monitor, error) {
return m, nil return m, nil
} }
// NewStatic returns a Monitor that's a one-time snapshot of the network state
// but doesn't actually monitor for changes. It should only be used in tests
// and situations like cleanups or short-lived CLI programs.
func NewStatic() *Monitor {
m := &Monitor{static: true}
if st, err := m.interfaceStateUncached(); err == nil {
m.ifState = st
}
return m
}
// InterfaceState returns the latest snapshot of the machine's network // InterfaceState returns the latest snapshot of the machine's network
// interfaces. // interfaces.
// //
@ -168,6 +180,10 @@ func (m *Monitor) SetTailscaleInterfaceName(ifName string) {
// It's the same as interfaces.LikelyHomeRouterIP, but it caches the // It's the same as interfaces.LikelyHomeRouterIP, but it caches the
// result until the monitor detects a network change. // result until the monitor detects a network change.
func (m *Monitor) GatewayAndSelfIP() (gw, myIP netip.Addr, ok bool) { func (m *Monitor) GatewayAndSelfIP() (gw, myIP netip.Addr, ok bool) {
if m.static {
return
}
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
if m.gwValid { if m.gwValid {
@ -190,6 +206,9 @@ func (m *Monitor) GatewayAndSelfIP() (gw, myIP netip.Addr, ok bool) {
// notified (in their own goroutine) when the network state changes. // notified (in their own goroutine) when the network state changes.
// To remove this callback, call unregister (or close the monitor). // To remove this callback, call unregister (or close the monitor).
func (m *Monitor) RegisterChangeCallback(callback ChangeFunc) (unregister func()) { func (m *Monitor) RegisterChangeCallback(callback ChangeFunc) (unregister func()) {
if m.static {
return func() {}
}
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
handle := m.cbs.Add(callback) handle := m.cbs.Add(callback)
@ -210,6 +229,9 @@ func (m *Monitor) RegisterChangeCallback(callback ChangeFunc) (unregister func()
// notified (in their own goroutine) when a Linux ip rule is deleted. // notified (in their own goroutine) when a Linux ip rule is deleted.
// To remove this callback, call unregister (or close the monitor). // To remove this callback, call unregister (or close the monitor).
func (m *Monitor) RegisterRuleDeleteCallback(callback RuleDeleteCallback) (unregister func()) { func (m *Monitor) RegisterRuleDeleteCallback(callback RuleDeleteCallback) (unregister func()) {
if m.static {
return func() {}
}
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
handle := m.ruleDelCB.Add(callback) handle := m.ruleDelCB.Add(callback)
@ -223,6 +245,9 @@ func (m *Monitor) RegisterRuleDeleteCallback(callback RuleDeleteCallback) (unreg
// Start starts the monitor. // Start starts the monitor.
// A monitor can only be started & closed once. // A monitor can only be started & closed once.
func (m *Monitor) Start() { func (m *Monitor) Start() {
if m.static {
return
}
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
if m.started || m.closed { if m.started || m.closed {
@ -244,6 +269,9 @@ func (m *Monitor) Start() {
// Close closes the monitor. // Close closes the monitor.
func (m *Monitor) Close() error { func (m *Monitor) Close() error {
if m.static {
return nil
}
m.mu.Lock() m.mu.Lock()
if m.closed { if m.closed {
m.mu.Unlock() m.mu.Unlock()
@ -275,6 +303,9 @@ func (m *Monitor) Close() error {
// ChangeFunc callbacks will be called within the event coalescing // ChangeFunc callbacks will be called within the event coalescing
// period (under a fraction of a second). // period (under a fraction of a second).
func (m *Monitor) InjectEvent() { func (m *Monitor) InjectEvent() {
if m.static {
return
}
select { select {
case m.change <- true: case m.change <- true:
default: default:
@ -290,6 +321,9 @@ func (m *Monitor) InjectEvent() {
// This is like InjectEvent but only fires ChangeFunc callbacks // This is like InjectEvent but only fires ChangeFunc callbacks
// if the network state differed at all. // if the network state differed at all.
func (m *Monitor) Poll() { func (m *Monitor) Poll() {
if m.static {
return
}
select { select {
case m.change <- false: case m.change <- false:
default: default:

View File

@ -56,8 +56,10 @@ func SetDisableBindConnToInterface(v bool) {
// Listener returns a new net.Listener with its Control hook func // Listener returns a new net.Listener with its Control hook func
// initialized as necessary to run in logical network namespace that // initialized as necessary to run in logical network namespace that
// doesn't route back into Tailscale. // doesn't route back into Tailscale.
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
func Listener(logf logger.Logf, netMon *netmon.Monitor) *net.ListenConfig { func Listener(logf logger.Logf, netMon *netmon.Monitor) *net.ListenConfig {
if netMon == nil {
panic("netns.Listener called with nil netMon")
}
if disabled.Load() { if disabled.Load() {
return new(net.ListenConfig) return new(net.ListenConfig)
} }
@ -68,8 +70,10 @@ func Listener(logf logger.Logf, netMon *netmon.Monitor) *net.ListenConfig {
// hook func initialized as necessary to run in a logical network // hook func initialized as necessary to run in a logical network
// namespace that doesn't route back into Tailscale. It also handles // namespace that doesn't route back into Tailscale. It also handles
// using a SOCKS if configured in the environment with ALL_PROXY. // using a SOCKS if configured in the environment with ALL_PROXY.
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
func NewDialer(logf logger.Logf, netMon *netmon.Monitor) Dialer { func NewDialer(logf logger.Logf, netMon *netmon.Monitor) Dialer {
if netMon == nil {
panic("netns.NewDialer called with nil netMon")
}
return FromDialer(logf, netMon, &net.Dialer{ return FromDialer(logf, netMon, &net.Dialer{
KeepAlive: netknob.PlatformTCPKeepAlive(), KeepAlive: netknob.PlatformTCPKeepAlive(),
}) })
@ -79,8 +83,10 @@ func NewDialer(logf logger.Logf, netMon *netmon.Monitor) Dialer {
// network namespace that doesn't route back into Tailscale. It also // network namespace that doesn't route back into Tailscale. It also
// handles using a SOCKS if configured in the environment with // handles using a SOCKS if configured in the environment with
// ALL_PROXY. // ALL_PROXY.
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
func FromDialer(logf logger.Logf, netMon *netmon.Monitor, d *net.Dialer) Dialer { func FromDialer(logf logger.Logf, netMon *netmon.Monitor, d *net.Dialer) Dialer {
if netMon == nil {
panic("netns.FromDialer called with nil netMon")
}
if disabled.Load() { if disabled.Load() {
return d return d
} }

View File

@ -16,6 +16,7 @@
"tailscale.com/control/controlknobs" "tailscale.com/control/controlknobs"
"tailscale.com/net/netaddr" "tailscale.com/net/netaddr"
"tailscale.com/net/netmon"
"tailscale.com/syncs" "tailscale.com/syncs"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
@ -259,12 +260,13 @@ func (d *TestIGD) handlePCPQuery(pkt []byte, src netip.AddrPort) {
func newTestClient(t *testing.T, igd *TestIGD) *Client { func newTestClient(t *testing.T, igd *TestIGD) *Client {
var c *Client var c *Client
c = NewClient(t.Logf, nil, nil, new(controlknobs.Knobs), func() { c = NewClient(t.Logf, netmon.NewStatic(), nil, new(controlknobs.Knobs), func() {
t.Logf("port map changed") t.Logf("port map changed")
t.Logf("have mapping: %v", c.HaveMapping()) t.Logf("have mapping: %v", c.HaveMapping())
}) })
c.testPxPPort = igd.TestPxPPort() c.testPxPPort = igd.TestPxPPort()
c.testUPnPPort = igd.TestUPnPPort() c.testUPnPPort = igd.TestUPnPPort()
c.netMon = netmon.NewStatic()
c.SetGatewayLookupFunc(testIPAndGateway) c.SetGatewayLookupFunc(testIPAndGateway)
return c return c
} }

View File

@ -198,8 +198,7 @@ func (m *pmpMapping) Release(ctx context.Context) {
// NewClient returns a new portmapping client. // NewClient returns a new portmapping client.
// //
// The netMon parameter is optional; if non-nil it's used to do faster interface // The netMon parameter is required.
// lookups.
// //
// The debug argument allows configuring the behaviour of the portmapper for // The debug argument allows configuring the behaviour of the portmapper for
// debugging; if nil, a sensible set of defaults will be used. // debugging; if nil, a sensible set of defaults will be used.
@ -211,10 +210,13 @@ func (m *pmpMapping) Release(ctx context.Context) {
// whenever the port mapping status has changed. If nil, it doesn't make a // whenever the port mapping status has changed. If nil, it doesn't make a
// callback. // callback.
func NewClient(logf logger.Logf, netMon *netmon.Monitor, debug *DebugKnobs, controlKnobs *controlknobs.Knobs, onChange func()) *Client { func NewClient(logf logger.Logf, netMon *netmon.Monitor, debug *DebugKnobs, controlKnobs *controlknobs.Knobs, onChange func()) *Client {
if netMon == nil {
panic("nil netMon")
}
ret := &Client{ ret := &Client{
logf: logf, logf: logf,
netMon: netMon, netMon: netMon,
ipAndGateway: interfaces.LikelyHomeRouterIP, ipAndGateway: interfaces.LikelyHomeRouterIP, // TODO(bradfitz): move this to netMon
onChange: onChange, onChange: onChange,
controlKnobs: controlKnobs, controlKnobs: controlKnobs,
} }

View File

@ -26,13 +26,27 @@
"tailscale.com/types/netmap" "tailscale.com/types/netmap"
"tailscale.com/util/clientmetric" "tailscale.com/util/clientmetric"
"tailscale.com/util/mak" "tailscale.com/util/mak"
"tailscale.com/util/testenv"
"tailscale.com/version" "tailscale.com/version"
) )
// NewDialer returns a new Dialer that can dial out of tailscaled.
// Its exported fields should be set before use, if any.
func NewDialer(netMon *netmon.Monitor) *Dialer {
if netMon == nil {
panic("NewDialer: netMon is nil")
}
d := &Dialer{}
d.SetNetMon(netMon)
return d
}
// Dialer dials out of tailscaled, while taking care of details while // Dialer dials out of tailscaled, while taking care of details while
// handling the dozens of edge cases depending on the server mode // handling the dozens of edge cases depending on the server mode
// (TUN, netstack), the OS network sandboxing style (macOS/iOS // (TUN, netstack), the OS network sandboxing style (macOS/iOS
// Extension, none), user-selected route acceptance prefs, etc. // Extension, none), user-selected route acceptance prefs, etc.
//
// Before use, SetNetMon should be called with a netmon.Monitor.
type Dialer struct { type Dialer struct {
Logf logger.Logf Logf logger.Logf
// UseNetstackForIP if non-nil is whether NetstackDialTCP (if // UseNetstackForIP if non-nil is whether NetstackDialTCP (if
@ -130,9 +144,14 @@ func (d *Dialer) Close() error {
return nil return nil
} }
// SetNetMon sets d's network monitor to netMon.
// It is a no-op to call SetNetMon with the same netMon as the current one.
func (d *Dialer) SetNetMon(netMon *netmon.Monitor) { func (d *Dialer) SetNetMon(netMon *netmon.Monitor) {
d.mu.Lock() d.mu.Lock()
defer d.mu.Unlock() defer d.mu.Unlock()
if d.netMon == netMon {
return
}
if d.netMonUnregister != nil { if d.netMonUnregister != nil {
go d.netMonUnregister() go d.netMonUnregister()
d.netMonUnregister = nil d.netMonUnregister = nil
@ -141,6 +160,14 @@ func (d *Dialer) SetNetMon(netMon *netmon.Monitor) {
d.netMonUnregister = d.netMon.RegisterChangeCallback(d.linkChanged) d.netMonUnregister = d.netMon.RegisterChangeCallback(d.linkChanged)
} }
// NetMon returns the Dialer's network monitor.
// It returns nil if SetNetMon has not been called.
func (d *Dialer) NetMon() *netmon.Monitor {
d.mu.Lock()
defer d.mu.Unlock()
return d.netMon
}
var ( var (
metricLinkChangeConnClosed = clientmetric.NewCounter("tsdial_linkchange_closes") metricLinkChangeConnClosed = clientmetric.NewCounter("tsdial_linkchange_closes")
metricChangeDeltaNoDefaultRoute = clientmetric.NewCounter("tsdial_changedelta_no_default_route") metricChangeDeltaNoDefaultRoute = clientmetric.NewCounter("tsdial_changedelta_no_default_route")
@ -314,6 +341,13 @@ func (d *Dialer) logf(format string, args ...any) {
// Control and (in the future, as of 2022-04-27) DERPs.. // Control and (in the future, as of 2022-04-27) DERPs..
func (d *Dialer) SystemDial(ctx context.Context, network, addr string) (net.Conn, error) { func (d *Dialer) SystemDial(ctx context.Context, network, addr string) (net.Conn, error) {
d.mu.Lock() d.mu.Lock()
if d.netMon == nil {
d.mu.Unlock()
if testenv.InTest() {
panic("SystemDial requires a netmon.Monitor; call SetNetMon first")
}
return nil, errors.New("SystemDial requires a netmon.Monitor; call SetNetMon first")
}
closed := d.closed closed := d.closed
d.mu.Unlock() d.mu.Unlock()
if closed { if closed {

View File

@ -16,6 +16,7 @@
"tailscale.com/derp" "tailscale.com/derp"
"tailscale.com/derp/derphttp" "tailscale.com/derp/derphttp"
"tailscale.com/net/netmon"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
) )
@ -140,7 +141,7 @@ func TestRunDerpProbeNodePair(t *testing.T) {
} }
}() }()
newClient := func() *derphttp.Client { newClient := func() *derphttp.Client {
c, err := derphttp.NewClient(key.NewNode(), serverURL, t.Logf) c, err := derphttp.NewClient(key.NewNode(), serverURL, t.Logf, netmon.NewStatic())
if err != nil { if err != nil {
t.Fatalf("NewClient: %v", err) t.Fatalf("NewClient: %v", err)
} }

View File

@ -53,7 +53,7 @@ func New(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *he
// CleanUp restores the system network configuration to its original state // CleanUp restores the system network configuration to its original state
// in case the Tailscale daemon terminated without closing the router. // in case the Tailscale daemon terminated without closing the router.
// No other state needs to be instantiated before this runs. // No other state needs to be instantiated before this runs.
func CleanUp(logf logger.Logf, interfaceName string) { func CleanUp(logf logger.Logf, netMon *netmon.Monitor, interfaceName string) {
cleanUp(logf, interfaceName) cleanUp(logf, interfaceName)
} }

View File

@ -193,7 +193,7 @@ type Config struct {
HealthTracker *health.Tracker HealthTracker *health.Tracker
// Dialer is the dialer to use for outbound connections. // Dialer is the dialer to use for outbound connections.
// If nil, a new Dialer is created // If nil, a new Dialer is created.
Dialer *tsdial.Dialer Dialer *tsdial.Dialer
// ControlKnobs is the set of control plane-provied knobs // ControlKnobs is the set of control plane-provied knobs
@ -341,7 +341,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
tunName, _ := conf.Tun.Name() tunName, _ := conf.Tun.Name()
conf.Dialer.SetTUNName(tunName) conf.Dialer.SetTUNName(tunName)
conf.Dialer.SetNetMon(e.netMon) conf.Dialer.SetNetMon(e.netMon)
e.dns = dns.NewManager(logf, conf.DNS, e.netMon, e.health, conf.Dialer, fwdDNSLinkSelector{e, tunName}, conf.ControlKnobs) e.dns = dns.NewManager(logf, conf.DNS, e.health, conf.Dialer, fwdDNSLinkSelector{e, tunName}, conf.ControlKnobs)
// TODO: there's probably a better place for this // TODO: there's probably a better place for this
sockstats.SetNetMon(e.netMon) sockstats.SetNetMon(e.netMon)