diff --git a/cmd/derper/depaware.txt b/cmd/derper/depaware.txt index 7e82305d1..b40ca360a 100644 --- a/cmd/derper/depaware.txt +++ b/cmd/derper/depaware.txt @@ -86,7 +86,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa 💣 tailscale.com/net/interfaces from tailscale.com/net/netns+ tailscale.com/net/netaddr from tailscale.com/ipn+ tailscale.com/net/netknob from tailscale.com/net/netns - tailscale.com/net/netmon from tailscale.com/net/sockstats + tailscale.com/net/netmon from tailscale.com/net/sockstats+ tailscale.com/net/netns from tailscale.com/derp/derphttp tailscale.com/net/netutil from tailscale.com/client/tailscale tailscale.com/net/packet from tailscale.com/wgengine/filter diff --git a/cmd/tailscale/cli/netcheck.go b/cmd/tailscale/cli/netcheck.go index 0c2f7acd1..bac5a0a48 100644 --- a/cmd/tailscale/cli/netcheck.go +++ b/cmd/tailscale/cli/netcheck.go @@ -19,6 +19,7 @@ import ( "tailscale.com/envknob" "tailscale.com/ipn" "tailscale.com/net/netcheck" + "tailscale.com/net/netmon" "tailscale.com/net/portmapper" "tailscale.com/tailcfg" "tailscale.com/types/logger" @@ -45,9 +46,14 @@ var netcheckArgs struct { } func runNetcheck(ctx context.Context, args []string) error { + logf := logger.WithPrefix(log.Printf, "portmap: ") + netMon, err := netmon.New(logf) + if err != nil { + return err + } c := &netcheck.Client{ UDPBindAddr: envknob.String("TS_DEBUG_NETCHECK_UDP_BIND"), - PortMapper: portmapper.NewClient(logger.WithPrefix(log.Printf, "portmap: "), nil, nil), + PortMapper: portmapper.NewClient(logf, netMon, nil, nil), UseDNSCache: false, // always resolve, don't cache } if netcheckArgs.verbose { diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index b146ec567..5b644992e 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -74,7 +74,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli tailscale.com/net/neterror from tailscale.com/net/netcheck+ tailscale.com/net/netknob from tailscale.com/net/netns - tailscale.com/net/netmon from tailscale.com/net/sockstats + tailscale.com/net/netmon from tailscale.com/net/sockstats+ tailscale.com/net/netns from tailscale.com/derp/derphttp+ tailscale.com/net/netutil from tailscale.com/client/tailscale+ tailscale.com/net/packet from tailscale.com/wgengine/filter+ diff --git a/cmd/tailscaled/debug.go b/cmd/tailscaled/debug.go index 8c1ad88b1..b6a95ba49 100644 --- a/cmd/tailscaled/debug.go +++ b/cmd/tailscaled/debug.go @@ -193,8 +193,8 @@ func checkDerp(ctx context.Context, derpRegion string) (err error) { priv1 := key.NewNode() priv2 := key.NewNode() - c1 := derphttp.NewRegionClient(priv1, log.Printf, getRegion) - c2 := derphttp.NewRegionClient(priv2, log.Printf, getRegion) + c1 := derphttp.NewRegionClient(priv1, log.Printf, nil, getRegion) + c2 := derphttp.NewRegionClient(priv2, log.Printf, nil, getRegion) defer func() { if err != nil { c1.Close() diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 3bae560a2..d2c28de99 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -329,7 +329,15 @@ var logPol *logpolicy.Policy var debugMux *http.ServeMux func run() error { - pol := logpolicy.New(logtail.CollectionNode) + var logf logger.Logf = log.Printf + netMon, err := netmon.New(func(format string, args ...any) { + logf(format, args...) + }) + if err != nil { + return fmt.Errorf("netmon.New: %w", err) + } + + pol := logpolicy.New(logtail.CollectionNode, netMon) pol.SetVerbosityLevel(args.verbose) logPol = pol defer func() { @@ -353,7 +361,6 @@ func run() error { return nil } - var logf logger.Logf = log.Printf if envknob.Bool("TS_DEBUG_MEMORY") { logf = logger.RusagePrefixLog(logf) } @@ -379,10 +386,10 @@ func run() error { debugMux = newDebugMux() } - return startIPNServer(context.Background(), logf, pol.PublicID) + return startIPNServer(context.Background(), logf, pol.PublicID, netMon) } -func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID) error { +func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) error { ln, err := safesocket.Listen(args.socketpath) if err != nil { return fmt.Errorf("safesocket.Listen: %v", err) @@ -408,7 +415,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID) } }() - srv := ipnserver.New(logf, logID) + srv := ipnserver.New(logf, logID, netMon) if debugMux != nil { debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus) } @@ -426,7 +433,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID) return } } - lb, err := getLocalBackend(ctx, logf, logID) + lb, err := getLocalBackend(ctx, logf, logID, netMon) if err == nil { logf("got LocalBackend in %v", time.Since(t0).Round(time.Millisecond)) srv.SetLocalBackend(lb) @@ -450,11 +457,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID) return nil } -func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID) (_ *ipnlocal.LocalBackend, retErr error) { - netMon, err := netmon.New(logf) - if err != nil { - return nil, fmt.Errorf("netmon.New: %w", err) - } +func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) (_ *ipnlocal.LocalBackend, retErr error) { if logPol != nil { logPol.Logtail.SetNetMon(netMon) } diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index a654dadf4..077d3f99f 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -45,6 +45,7 @@ import ( "tailscale.com/logpolicy" "tailscale.com/logtail/backoff" "tailscale.com/net/dns" + "tailscale.com/net/netmon" "tailscale.com/net/tstun" "tailscale.com/types/logger" "tailscale.com/types/logid" @@ -291,8 +292,13 @@ func beWindowsSubprocess() bool { } }() + netMon, err := netmon.New(log.Printf) + if err != nil { + log.Printf("Could not create netMon: %v", err) + netMon = nil + } publicLogID, _ := logid.ParsePublicID(logID) - err := startIPNServer(ctx, log.Printf, publicLogID) + err = startIPNServer(ctx, log.Printf, publicLogID, netMon) if err != nil { log.Fatalf("ipnserver: %v", err) } diff --git a/cmd/tsconnect/wasm/wasm_js.go b/cmd/tsconnect/wasm/wasm_js.go index f94b850a2..a1b0fd95e 100644 --- a/cmd/tsconnect/wasm/wasm_js.go +++ b/cmd/tsconnect/wasm/wasm_js.go @@ -123,7 +123,7 @@ func newIPN(jsConfig js.Value) map[string]any { } logid := lpc.PublicID - srv := ipnserver.New(logf, logid) + srv := ipnserver.New(logf, logid, nil /* no netMon */) lb, err := ipnlocal.NewLocalBackend(logf, logid, store, dialer, eng, controlclient.LoginEphemeral) if err != nil { log.Fatalf("ipnlocal.NewLocalBackend: %v", err) diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 85bd7f07b..cf6fd9987 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -211,8 +211,9 @@ func NewDirect(opts Options) (*Direct, error) { dnsCache := &dnscache.Resolver{ Forward: dnscache.Get().Forward, // use default cache's forwarder UseLastGood: true, - LookupIPFallback: dnsfallback.Lookup(opts.Logf), + LookupIPFallback: dnsfallback.MakeLookupFunc(opts.Logf, opts.NetMon), Logf: opts.Logf, + NetMon: opts.NetMon, } tr := http.DefaultTransport.(*http.Transport).Clone() tr.Proxy = tshttpproxy.ProxyFromEnvironment @@ -1508,7 +1509,7 @@ func (c *Direct) getNoiseClient() (*NoiseClient, error) { return nil, err } c.logf("creating new noise client") - nc, err := NewNoiseClient(k, serverNoiseKey, c.serverURL, c.dialer, dp) + nc, err := NewNoiseClient(k, serverNoiseKey, c.serverURL, c.dialer, c.logf, c.netMon, dp) if err != nil { return nil, err } diff --git a/control/controlclient/noise.go b/control/controlclient/noise.go index 826b0c249..61c472a35 100644 --- a/control/controlclient/noise.go +++ b/control/controlclient/noise.go @@ -19,9 +19,11 @@ import ( "golang.org/x/net/http2" "tailscale.com/control/controlbase" "tailscale.com/control/controlhttp" + "tailscale.com/net/netmon" "tailscale.com/net/tsdial" "tailscale.com/tailcfg" "tailscale.com/types/key" + "tailscale.com/types/logger" "tailscale.com/util/mak" "tailscale.com/util/multierr" "tailscale.com/util/singleflight" @@ -167,6 +169,9 @@ type NoiseClient struct { // be nil. dialPlan func() *tailcfg.ControlDialPlan + logf logger.Logf + netMon *netmon.Monitor + // mu only protects the following variables. mu sync.Mutex last *noiseConn // or nil @@ -177,8 +182,9 @@ type NoiseClient struct { // NewNoiseClient returns a new noiseClient for the provided server and machine key. // serverURL is of the form https://: (no trailing slash). // +// netMon may be nil, if non-nil it's used to do faster interface lookups. // dialPlan may be nil -func NewNoiseClient(privKey key.MachinePrivate, serverPubKey key.MachinePublic, serverURL string, dialer *tsdial.Dialer, dialPlan func() *tailcfg.ControlDialPlan) (*NoiseClient, error) { +func NewNoiseClient(privKey key.MachinePrivate, serverPubKey key.MachinePublic, serverURL string, dialer *tsdial.Dialer, logf logger.Logf, netMon *netmon.Monitor, dialPlan func() *tailcfg.ControlDialPlan) (*NoiseClient, error) { u, err := url.Parse(serverURL) if err != nil { return nil, err @@ -207,6 +213,8 @@ func NewNoiseClient(privKey key.MachinePrivate, serverPubKey key.MachinePublic, httpsPort: httpsPort, dialer: dialer, dialPlan: dialPlan, + logf: logf, + netMon: netMon, } // Create the HTTP/2 Transport using a net/http.Transport @@ -366,6 +374,8 @@ func (nc *NoiseClient) dial() (*noiseConn, error) { ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion), Dialer: nc.dialer.SystemDial, DialPlan: dialPlan, + Logf: nc.logf, + NetMon: nc.netMon, }).Dial(ctx) if err != nil { return nil, err diff --git a/control/controlclient/noise_test.go b/control/controlclient/noise_test.go index 049e260fe..11e35f0af 100644 --- a/control/controlclient/noise_test.go +++ b/control/controlclient/noise_test.go @@ -74,7 +74,7 @@ func (tt noiseClientTest) run(t *testing.T) { defer hs.Close() dialer := new(tsdial.Dialer) - nc, err := NewNoiseClient(clientPrivate, serverPrivate.Public(), hs.URL, dialer, nil) + nc, err := NewNoiseClient(clientPrivate, serverPrivate.Public(), hs.URL, dialer, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/control/controlhttp/client.go b/control/controlhttp/client.go index 02b5a7821..d04aac518 100644 --- a/control/controlhttp/client.go +++ b/control/controlhttp/client.go @@ -389,13 +389,15 @@ func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, addr netip.Addr, SingleHostStaticResult: []netip.Addr{addr}, SingleHost: u.Hostname(), Logf: a.Logf, // not a.logf method; we want to propagate nil-ness + NetMon: a.NetMon, } } else { dns = &dnscache.Resolver{ Forward: dnscache.Get().Forward, - LookupIPFallback: dnsfallback.Lookup(a.logf), + LookupIPFallback: dnsfallback.MakeLookupFunc(a.logf, a.NetMon), UseLastGood: true, Logf: a.Logf, // not a.logf method; we want to propagate nil-ness + NetMon: a.NetMon, } } diff --git a/control/controlhttp/constants.go b/control/controlhttp/constants.go index 045eeba7f..a58ee5374 100644 --- a/control/controlhttp/constants.go +++ b/control/controlhttp/constants.go @@ -9,6 +9,7 @@ import ( "time" "tailscale.com/net/dnscache" + "tailscale.com/net/netmon" "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/types/logger" @@ -70,6 +71,8 @@ type Dialer struct { // dropped. Logf logger.Logf + NetMon *netmon.Monitor + // DialPlan, if set, contains instructions from the control server on // how to connect to it. If present, we will try the methods in this // plan before falling back to DNS. diff --git a/derp/derphttp/derphttp_client.go b/derp/derphttp/derphttp_client.go index 349838dfa..2ea86b686 100644 --- a/derp/derphttp/derphttp_client.go +++ b/derp/derphttp/derphttp_client.go @@ -31,6 +31,7 @@ import ( "tailscale.com/derp" "tailscale.com/envknob" "tailscale.com/net/dnscache" + "tailscale.com/net/netmon" "tailscale.com/net/netns" "tailscale.com/net/sockstats" "tailscale.com/net/tlsdial" @@ -55,6 +56,7 @@ type Client struct { privateKey key.NodePrivate logf logger.Logf + netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand dialer func(ctx context.Context, network, addr string) (net.Conn, error) // Either url or getRegion is non-nil: @@ -88,11 +90,13 @@ func (c *Client) String() string { // NewRegionClient returns a new DERP-over-HTTP client. It connects lazily. // To trigger a connection, use Connect. -func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, getRegion func() *tailcfg.DERPRegion) *Client { +// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. +func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, netMon *netmon.Monitor, getRegion func() *tailcfg.DERPRegion) *Client { ctx, cancel := context.WithCancel(context.Background()) c := &Client{ privateKey: privateKey, logf: logf, + netMon: netMon, getRegion: getRegion, ctx: ctx, cancelCtx: cancel, @@ -492,7 +496,7 @@ func (c *Client) dialURL(ctx context.Context) (net.Conn, error) { return c.dialer(ctx, "tcp", net.JoinHostPort(host, urlPort(c.url))) } hostOrIP := host - dialer := netns.NewDialer(c.logf) + dialer := netns.NewDialer(c.logf, c.netMon) if c.DNSCache != nil { ip, _, _, err := c.DNSCache.LookupIP(ctx, host) @@ -587,7 +591,7 @@ func (c *Client) DialRegionTLS(ctx context.Context, reg *tailcfg.DERPRegion) (tl } func (c *Client) dialContext(ctx context.Context, proto, addr string) (net.Conn, error) { - return netns.NewDialer(c.logf).DialContext(ctx, proto, addr) + return netns.NewDialer(c.logf, c.netMon).DialContext(ctx, proto, addr) } // shouldDialProto reports whether an explicitly provided IPv4 or IPv6 diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 532e68d19..d1aaf6b89 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -313,7 +313,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor loginFlags: loginFlags, } - b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID) + b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logID, e.GetNetMon()) if err != nil { log.Printf("error setting up sockstat logger: %v", err) } diff --git a/ipn/ipnserver/proxyconnect.go b/ipn/ipnserver/proxyconnect.go index 8f330add1..5ac7e89db 100644 --- a/ipn/ipnserver/proxyconnect.go +++ b/ipn/ipnserver/proxyconnect.go @@ -37,7 +37,8 @@ func (s *Server) handleProxyConnectConn(w http.ResponseWriter, r *http.Request) return } - back, err := logpolicy.DialContext(ctx, "tcp", hostPort) + dialContext := logpolicy.MakeDialFunc(s.netMon) + back, err := dialContext(ctx, "tcp", hostPort) if err != nil { s.logf("error CONNECT dialing %v: %v", hostPort, err) http.Error(w, "Connect failure", http.StatusBadGateway) diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index 0123d9e9e..3abfa9364 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -24,6 +24,7 @@ import ( "tailscale.com/ipn/ipnauth" "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/localapi" + "tailscale.com/net/netmon" "tailscale.com/types/logger" "tailscale.com/types/logid" "tailscale.com/util/mak" @@ -36,6 +37,7 @@ import ( type Server struct { lb atomic.Pointer[ipnlocal.LocalBackend] logf logger.Logf + netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand backendLogID logid.PublicID // resetOnZero is whether to call bs.Reset on transition from // 1->0 active HTTP requests. That is, this is whether the backend is @@ -197,7 +199,7 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) { defer onDone() if strings.HasPrefix(r.URL.Path, "/localapi/") { - lah := localapi.NewHandler(lb, s.logf, s.backendLogID) + lah := localapi.NewHandler(lb, s.logf, s.netMon, s.backendLogID) lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci) lah.PermitCert = s.connCanFetchCerts(ci) lah.ServeHTTP(w, r) @@ -408,15 +410,18 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit } // New returns a new Server. +// The netMon parameter is optional; if non-nil it's used to do faster interface +// lookups. // // To start it, use the Server.Run method. // // At some point, either before or after Run, the Server's SetLocalBackend // method must also be called before Server can do anything useful. -func New(logf logger.Logf, logID logid.PublicID) *Server { +func New(logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) *Server { return &Server{ backendLogID: logID, logf: logf, + netMon: netMon, resetOnZero: envknob.GOOS() == "windows", } } diff --git a/ipn/localapi/debugderp.go b/ipn/localapi/debugderp.go index f7ae88787..af2527ee8 100644 --- a/ipn/localapi/debugderp.go +++ b/ipn/localapi/debugderp.go @@ -140,7 +140,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) { } checkSTUN4 := func(derpNode *tailcfg.DERPNode) { - u4, err := nettype.MakePacketListenerWithNetIP(netns.Listener(h.logf)).ListenPacket(ctx, "udp4", ":0") + u4, err := nettype.MakePacketListenerWithNetIP(netns.Listener(h.logf, h.netMon)).ListenPacket(ctx, "udp4", ":0") if err != nil { st.Errors = append(st.Errors, fmt.Sprintf("Error creating IPv4 STUN listener: %v", err)) return @@ -249,7 +249,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) { serverPubKeys := make(map[key.NodePublic]bool) for i := 0; i < 5; i++ { func() { - rc := derphttp.NewRegionClient(fakePrivKey, h.logf, func() *tailcfg.DERPRegion { + rc := derphttp.NewRegionClient(fakePrivKey, h.logf, h.netMon, func() *tailcfg.DERPRegion { return &tailcfg.DERPRegion{ RegionID: reg.RegionID, RegionCode: reg.RegionCode, diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index be15cbe08..28371a45d 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -125,8 +125,10 @@ var ( metrics = map[string]*clientmetric.Metric{} ) -func NewHandler(b *ipnlocal.LocalBackend, logf logger.Logf, logID logid.PublicID) *Handler { - return &Handler{b: b, logf: logf, backendLogID: logID} +// NewHandler creates a new LocalAPI HTTP handler. All parameters except netMon +// are required (if non-nil it's used to do faster interface lookups). +func NewHandler(b *ipnlocal.LocalBackend, logf logger.Logf, netMon *netmon.Monitor, logID logid.PublicID) *Handler { + return &Handler{b: b, logf: logf, netMon: netMon, backendLogID: logID} } type Handler struct { @@ -150,6 +152,7 @@ type Handler struct { b *ipnlocal.LocalBackend logf logger.Logf + netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand backendLogID logid.PublicID } @@ -679,7 +682,7 @@ func (h *Handler) serveDebugPortmap(w http.ResponseWriter, r *http.Request) { done := make(chan bool, 1) var c *portmapper.Client - c = portmapper.NewClient(logger.WithPrefix(logf, "portmapper: "), debugKnobs, func() { + c = portmapper.NewClient(logger.WithPrefix(logf, "portmapper: "), h.netMon, debugKnobs, func() { logf("portmapping changed.") logf("have mapping: %v", c.HaveMapping()) diff --git a/log/sockstatlog/logger.go b/log/sockstatlog/logger.go index 683342a08..4f522f17d 100644 --- a/log/sockstatlog/logger.go +++ b/log/sockstatlog/logger.go @@ -20,6 +20,7 @@ import ( "tailscale.com/logpolicy" "tailscale.com/logtail" "tailscale.com/logtail/filch" + "tailscale.com/net/netmon" "tailscale.com/net/sockstats" "tailscale.com/smallzstd" "tailscale.com/types/logger" @@ -92,7 +93,8 @@ func SockstatLogID(logID logid.PublicID) logid.PrivateID { // 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. // Logs will be uploaded to the log server using a new log ID derived from the provided backend logID. -func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID) (*Logger, error) { +// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. +func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) (*Logger, error) { if !sockstats.IsAvailable { return nil, nil } @@ -112,7 +114,7 @@ func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID) (*Logger, logger := &Logger{ logf: logf, filch: filch, - tr: logpolicy.NewLogtailTransport(logtail.DefaultHost), + tr: logpolicy.NewLogtailTransport(logtail.DefaultHost, netMon), } logger.logger = logtail.NewLogger(logtail.Config{ BaseURL: logpolicy.LogURL(), diff --git a/log/sockstatlog/logger_test.go b/log/sockstatlog/logger_test.go index e64a5de8b..1e2d67d97 100644 --- a/log/sockstatlog/logger_test.go +++ b/log/sockstatlog/logger_test.go @@ -23,7 +23,7 @@ func TestResourceCleanup(t *testing.T) { if err != nil { t.Fatal(err) } - lg, err := NewLogger(td, logger.Discard, id.Public()) + lg, err := NewLogger(td, logger.Discard, id.Public(), nil) if err != nil { t.Fatal(err) } diff --git a/logpolicy/logpolicy.go b/logpolicy/logpolicy.go index 84da5f31a..7becc934f 100644 --- a/logpolicy/logpolicy.go +++ b/logpolicy/logpolicy.go @@ -37,6 +37,7 @@ import ( "tailscale.com/net/dnscache" "tailscale.com/net/dnsfallback" "tailscale.com/net/netknob" + "tailscale.com/net/netmon" "tailscale.com/net/netns" "tailscale.com/net/tlsdial" "tailscale.com/net/tshttpproxy" @@ -450,14 +451,15 @@ func tryFixLogStateLocation(dir, cmdname string) { // New returns a new log policy (a logger and its instance ID) for a // given collection name. -func New(collection string) *Policy { - return NewWithConfigPath(collection, "", "") +// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. +func New(collection string, netMon *netmon.Monitor) *Policy { + return NewWithConfigPath(collection, "", "", netMon) } // NewWithConfigPath is identical to New, // but uses the specified directory and command name. // If either is empty, it derives them automatically. -func NewWithConfigPath(collection, dir, cmdName string) *Policy { +func NewWithConfigPath(collection, dir, cmdName string, netMon *netmon.Monitor) *Policy { var lflags int if term.IsTerminal(2) || runtime.GOOS == "windows" { lflags = 0 @@ -554,7 +556,7 @@ func NewWithConfigPath(collection, dir, cmdName string) *Policy { } return w }, - HTTPC: &http.Client{Transport: NewLogtailTransport(logtail.DefaultHost)}, + HTTPC: &http.Client{Transport: NewLogtailTransport(logtail.DefaultHost, netMon)}, } if collection == logtail.CollectionNode { conf.MetricsDelta = clientmetric.EncodeLogTailMetricsDelta @@ -569,7 +571,7 @@ func NewWithConfigPath(collection, dir, cmdName string) *Policy { log.Println("You have enabled a non-default log target. Doing without being told to by Tailscale staff or your network administrator will make getting support difficult.") conf.BaseURL = val u, _ := url.Parse(val) - conf.HTTPC = &http.Client{Transport: NewLogtailTransport(u.Host)} + conf.HTTPC = &http.Client{Transport: NewLogtailTransport(u.Host, netMon)} } filchOptions := filch.Options{ @@ -670,13 +672,22 @@ func (p *Policy) Shutdown(ctx context.Context) error { return nil } -// DialContext is a net.Dialer.DialContext specialized for use by logtail. +// MakeDialFunc creates a net.Dialer.DialContext function specialized for use +// by logtail. // It does the following: // - If DNS lookup fails, consults the bootstrap DNS list of Tailscale hostnames. // - 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. -func DialContext(ctx context.Context, netw, addr string) (net.Conn, error) { - nd := netns.FromDialer(log.Printf, &net.Dialer{ +// +// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. +func MakeDialFunc(netMon *netmon.Monitor) 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) + } +} + +func dialContext(ctx context.Context, netw, addr string, netMon *netmon.Monitor) (net.Conn, error) { + nd := netns.FromDialer(log.Printf, netMon, &net.Dialer{ Timeout: 30 * time.Second, KeepAlive: netknob.PlatformTCPKeepAlive(), }) @@ -711,7 +722,8 @@ func DialContext(ctx context.Context, netw, addr string) (net.Conn, error) { dnsCache := &dnscache.Resolver{ Forward: dnscache.Get().Forward, // use default cache's forwarder UseLastGood: true, - LookupIPFallback: dnsfallback.Lookup(log.Printf), + LookupIPFallback: dnsfallback.MakeLookupFunc(log.Printf, netMon), + NetMon: netMon, } dialer := dnscache.Dialer(nd.DialContext, dnsCache) c, err = dialer(ctx, netw, addr) @@ -723,7 +735,8 @@ func DialContext(ctx context.Context, netw, addr string) (net.Conn, error) { // NewLogtailTransport returns an HTTP Transport particularly suited to uploading // logs to the given host name. See DialContext for details on how it works. -func NewLogtailTransport(host string) http.RoundTripper { +// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. +func NewLogtailTransport(host string, netMon *netmon.Monitor) http.RoundTripper { if inTest() { return noopPretendSuccessTransport{} } @@ -739,7 +752,7 @@ func NewLogtailTransport(host string) http.RoundTripper { tr.DisableCompression = true // Log whenever we dial: - tr.DialContext = DialContext + tr.DialContext = MakeDialFunc(netMon) // We're contacting exactly 1 hostname, so the default's 100 // max idle conns is very high for our needs. Even 2 is diff --git a/net/dns/manager.go b/net/dns/manager.go index e909cbe5d..d1aa73ca6 100644 --- a/net/dns/manager.go +++ b/net/dns/manager.go @@ -64,6 +64,7 @@ type Manager struct { } // 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, netMon *netmon.Monitor, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector) *Manager { if dialer == nil { panic("nil Dialer") diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go index 3373ffcf1..85670e1d6 100644 --- a/net/dns/resolver/forwarder.go +++ b/net/dns/resolver/forwarder.go @@ -380,11 +380,12 @@ func (f *forwarder) getKnownDoHClientForProvider(urlBase string) (c *http.Client if err != nil { return nil, false } - nsDialer := netns.NewDialer(f.logf) + nsDialer := netns.NewDialer(f.logf, f.netMon) dialer := dnscache.Dialer(nsDialer.DialContext, &dnscache.Resolver{ SingleHost: dohURL.Hostname(), SingleHostStaticResult: allIPs, Logf: f.logf, + NetMon: f.netMon, }) c = &http.Client{ Transport: &http.Transport{ diff --git a/net/dns/resolver/macios_ext.go b/net/dns/resolver/macios_ext.go index 895b8714f..e3f979c19 100644 --- a/net/dns/resolver/macios_ext.go +++ b/net/dns/resolver/macios_ext.go @@ -17,8 +17,8 @@ func init() { initListenConfig = initListenConfigNetworkExtension } -func initListenConfigNetworkExtension(nc *net.ListenConfig, mon *netmon.Monitor, tunName string) error { - nif, ok := mon.InterfaceState().Interface[tunName] +func initListenConfigNetworkExtension(nc *net.ListenConfig, netMon *netmon.Monitor, tunName string) error { + nif, ok := netMon.InterfaceState().Interface[tunName] if !ok { return errors.New("utun not found") } diff --git a/net/dns/resolver/tsdns_test.go b/net/dns/resolver/tsdns_test.go index ad3d36c99..8db97f49a 100644 --- a/net/dns/resolver/tsdns_test.go +++ b/net/dns/resolver/tsdns_test.go @@ -997,7 +997,7 @@ func TestMarshalResponseFormatError(t *testing.T) { func TestForwardLinkSelection(t *testing.T) { configCall := make(chan string, 1) - tstest.Replace(t, &initListenConfig, func(nc *net.ListenConfig, mon *netmon.Monitor, tunName string) error { + tstest.Replace(t, &initListenConfig, func(nc *net.ListenConfig, netMon *netmon.Monitor, tunName string) error { select { case configCall <- tunName: return nil diff --git a/net/dnscache/dnscache.go b/net/dnscache/dnscache.go index 5a5978f5f..0983ae875 100644 --- a/net/dnscache/dnscache.go +++ b/net/dnscache/dnscache.go @@ -21,6 +21,7 @@ import ( "time" "tailscale.com/envknob" + "tailscale.com/net/netmon" "tailscale.com/types/logger" "tailscale.com/util/cloudenv" "tailscale.com/util/singleflight" @@ -91,6 +92,11 @@ type Resolver struct { // be added to all log messages printed with this logger. 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] mu sync.Mutex diff --git a/net/dnsfallback/dnsfallback.go b/net/dnsfallback/dnsfallback.go index 9ac22b9dc..5e80c79c9 100644 --- a/net/dnsfallback/dnsfallback.go +++ b/net/dnsfallback/dnsfallback.go @@ -23,6 +23,7 @@ import ( "time" "tailscale.com/atomicfile" + "tailscale.com/net/netmon" "tailscale.com/net/netns" "tailscale.com/net/tlsdial" "tailscale.com/net/tshttpproxy" @@ -31,13 +32,16 @@ import ( "tailscale.com/util/slicesx" ) -func Lookup(logf logger.Logf) func(ctx context.Context, host string) ([]netip.Addr, error) { +// MakeLookupFunc creates a function that can be used to resolve hostnames +// (e.g. as a LookupIPFallback from dnscache.Resolver). +// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. +func MakeLookupFunc(logf logger.Logf, netMon *netmon.Monitor) func(ctx context.Context, host string) ([]netip.Addr, error) { return func(ctx context.Context, host string) ([]netip.Addr, error) { - return lookup(ctx, host, logf) + return lookup(ctx, host, logf, netMon) } } -func lookup(ctx context.Context, host string, logf logger.Logf) ([]netip.Addr, error) { +func lookup(ctx context.Context, host string, logf logger.Logf, netMon *netmon.Monitor) ([]netip.Addr, error) { if ip, err := netip.ParseAddr(host); err == nil && ip.IsValid() { return []netip.Addr{ip}, nil } @@ -85,7 +89,7 @@ func lookup(ctx context.Context, host string, logf logger.Logf) ([]netip.Addr, e logf("trying bootstrapDNS(%q, %q) for %q ...", cand.dnsName, cand.ip, host) ctx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() - dm, err := bootstrapDNSMap(ctx, cand.dnsName, cand.ip, host, logf) + dm, err := bootstrapDNSMap(ctx, cand.dnsName, cand.ip, host, logf, netMon) if err != nil { logf("bootstrapDNS(%q, %q) for %q error: %v", cand.dnsName, cand.ip, host, err) continue @@ -104,8 +108,8 @@ func lookup(ctx context.Context, host string, logf logger.Logf) ([]netip.Addr, e // serverName and serverIP of are, say, "derpN.tailscale.com". // queryName is the name being sought (e.g. "controlplane.tailscale.com"), passed as hint. -func bootstrapDNSMap(ctx context.Context, serverName string, serverIP netip.Addr, queryName string, logf logger.Logf) (dnsMap, error) { - dialer := netns.NewDialer(logf) +func bootstrapDNSMap(ctx context.Context, serverName string, serverIP netip.Addr, queryName string, logf logger.Logf, netMon *netmon.Monitor) (dnsMap, error) { + dialer := netns.NewDialer(logf, netMon) tr := http.DefaultTransport.(*http.Transport).Clone() tr.Proxy = tshttpproxy.ProxyFromEnvironment tr.DialContext = func(ctx context.Context, netw, addr string) (net.Conn, error) { diff --git a/net/netcheck/netcheck.go b/net/netcheck/netcheck.go index 687aa6a8b..a61343ebe 100644 --- a/net/netcheck/netcheck.go +++ b/net/netcheck/netcheck.go @@ -29,6 +29,7 @@ import ( "tailscale.com/net/interfaces" "tailscale.com/net/netaddr" "tailscale.com/net/neterror" + "tailscale.com/net/netmon" "tailscale.com/net/netns" "tailscale.com/net/ping" "tailscale.com/net/portmapper" @@ -158,6 +159,11 @@ type Client struct { // If nil, log.Printf is used. 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 + // TimeNow, if non-nil, is used instead of time.Now. TimeNow func() time.Time @@ -864,22 +870,29 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (_ *Report, return c.finishAndStoreReport(rs, dm), nil } - ifState, err := interfaces.GetState() - if err != nil { - c.logf("[v1] interfaces: %v", err) - return nil, err + var ifState *interfaces.State + if c.NetMon == nil { + directState, err := interfaces.GetState() + if err != nil { + c.logf("[v1] interfaces: %v", err) + return nil, err + } else { + ifState = directState + } + } else { + ifState = c.NetMon.InterfaceState() } // See if IPv6 works at all, or if it's been hard disabled at the // OS level. - v6udp, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf)).ListenPacket(ctx, "udp6", "[::1]:0") + v6udp, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, c.NetMon)).ListenPacket(ctx, "udp6", "[::1]:0") if err == nil { rs.report.OSHasIPv6 = true v6udp.Close() } // Create a UDP4 socket used for sending to our discovered IPv4 address. - rs.pc4Hair, err = nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf)).ListenPacket(ctx, "udp4", ":0") + rs.pc4Hair, err = nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, c.NetMon)).ListenPacket(ctx, "udp4", ":0") if err != nil { c.logf("udp4: %v", err) return nil, err @@ -909,7 +922,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (_ *Report, if f := c.GetSTUNConn4; f != nil { rs.pc4 = f() } else { - u4, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf)).ListenPacket(ctx, "udp4", c.udpBindAddr()) + u4, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, nil)).ListenPacket(ctx, "udp4", c.udpBindAddr()) if err != nil { c.logf("udp4: %v", err) return nil, err @@ -922,7 +935,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (_ *Report, if f := c.GetSTUNConn6; f != nil { rs.pc6 = f() } else { - u6, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf)).ListenPacket(ctx, "udp6", c.udpBindAddr()) + u6, err := nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, nil)).ListenPacket(ctx, "udp6", c.udpBindAddr()) if err != nil { c.logf("udp6: %v", err) } else { @@ -1297,7 +1310,7 @@ func (c *Client) measureAllICMPLatency(ctx context.Context, rs *reportState, nee ctx, done := context.WithTimeout(ctx, icmpProbeTimeout) defer done() - p, err := ping.New(ctx, c.logf) + p, err := ping.New(ctx, c.logf, c.NetMon) if err != nil { return err } @@ -1635,6 +1648,7 @@ func (c *Client) nodeAddr(ctx context.Context, n *tailcfg.DERPNode, proto probeP Forward: net.DefaultResolver, UseLastGood: true, Logf: c.logf, + NetMon: c.NetMon, } } resolver := c.resolver diff --git a/net/netns/netns.go b/net/netns/netns.go index 86aafcac2..2acb15129 100644 --- a/net/netns/netns.go +++ b/net/netns/netns.go @@ -20,6 +20,7 @@ import ( "sync/atomic" "tailscale.com/net/netknob" + "tailscale.com/net/netmon" "tailscale.com/types/logger" ) @@ -55,19 +56,21 @@ func SetDisableBindConnToInterface(v bool) { // Listener returns a new net.Listener with its Control hook func // initialized as necessary to run in logical network namespace that // doesn't route back into Tailscale. -func Listener(logf logger.Logf) *net.ListenConfig { +// 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 { if disabled.Load() { return new(net.ListenConfig) } - return &net.ListenConfig{Control: control(logf)} + return &net.ListenConfig{Control: control(logf, netMon)} } // NewDialer returns a new Dialer using a net.Dialer with its Control // hook func initialized as necessary to run in a logical network // namespace that doesn't route back into Tailscale. It also handles // using a SOCKS if configured in the environment with ALL_PROXY. -func NewDialer(logf logger.Logf) Dialer { - return FromDialer(logf, &net.Dialer{ +// 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 { + return FromDialer(logf, netMon, &net.Dialer{ KeepAlive: netknob.PlatformTCPKeepAlive(), }) } @@ -76,11 +79,12 @@ func NewDialer(logf logger.Logf) Dialer { // network namespace that doesn't route back into Tailscale. It also // handles using a SOCKS if configured in the environment with // ALL_PROXY. -func FromDialer(logf logger.Logf, d *net.Dialer) Dialer { +// 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 { if disabled.Load() { return d } - d.Control = control(logf) + d.Control = control(logf, netMon) if wrapDialer != nil { return wrapDialer(d) } diff --git a/net/netns/netns_android.go b/net/netns/netns_android.go index 218b132e3..162e5c79a 100644 --- a/net/netns/netns_android.go +++ b/net/netns/netns_android.go @@ -10,6 +10,7 @@ import ( "sync" "syscall" + "tailscale.com/net/netmon" "tailscale.com/types/logger" ) @@ -49,7 +50,7 @@ func SetAndroidProtectFunc(f func(fd int) error) { androidProtectFunc = f } -func control(logger.Logf) func(network, address string, c syscall.RawConn) error { +func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error { return controlC } diff --git a/net/netns/netns_darwin.go b/net/netns/netns_darwin.go index c24c51102..7315dff40 100644 --- a/net/netns/netns_darwin.go +++ b/net/netns/netns_darwin.go @@ -19,24 +19,25 @@ import ( "golang.org/x/sys/unix" "tailscale.com/envknob" "tailscale.com/net/interfaces" + "tailscale.com/net/netmon" "tailscale.com/types/logger" ) -func control(logf logger.Logf) func(network, address string, c syscall.RawConn) error { +func control(logf logger.Logf, netMon *netmon.Monitor) func(network, address string, c syscall.RawConn) error { return func(network, address string, c syscall.RawConn) error { - return controlLogf(logf, network, address, c) + return controlLogf(logf, netMon, network, address, c) } } var bindToInterfaceByRouteEnv = envknob.RegisterBool("TS_BIND_TO_INTERFACE_BY_ROUTE") -var errInterfaceIndexInvalid = errors.New("interface index invalid") +var errInterfaceStateInvalid = errors.New("interface state invalid") // controlLogf marks c as necessary to dial in a separate network namespace. // // It's intentionally the same signature as net.Dialer.Control // and net.ListenConfig.Control. -func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) error { +func controlLogf(logf logger.Logf, netMon *netmon.Monitor, network, address string, c syscall.RawConn) error { if isLocalhost(address) { // Don't bind to an interface for localhost connections. return nil @@ -47,7 +48,7 @@ func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) e return nil } - idx, err := getInterfaceIndex(logf, address) + idx, err := getInterfaceIndex(logf, netMon, address) if err != nil { // callee logged return nil @@ -56,20 +57,31 @@ func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) e return bindConnToInterface(c, network, address, idx, logf) } -func getInterfaceIndex(logf logger.Logf, address string) (int, error) { +func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string) (int, error) { // Helper so we can log errors. defaultIdx := func() (int, error) { - idx, err := interfaces.DefaultRouteInterfaceIndex() - if err != nil { - // It's somewhat common for there to be no default gateway route - // (e.g. on a phone with no connectivity), don't log those errors - // since they are expected. - if !errors.Is(err, interfaces.ErrNoGatewayIndexFound) { - logf("[unexpected] netns: DefaultRouteInterfaceIndex: %v", err) + if netMon == nil { + idx, err := interfaces.DefaultRouteInterfaceIndex() + if err != nil { + // It's somewhat common for there to be no default gateway route + // (e.g. on a phone with no connectivity), don't log those errors + // since they are expected. + if !errors.Is(err, interfaces.ErrNoGatewayIndexFound) { + logf("[unexpected] netns: DefaultRouteInterfaceIndex: %v", err) + } + return -1, err } - return -1, err + return idx, nil } - return idx, nil + state := netMon.InterfaceState() + if state == nil { + return -1, errInterfaceStateInvalid + } + + if iface, ok := state.Interface[state.DefaultRouteInterface]; ok { + return iface.Index, nil + } + return -1, errInterfaceStateInvalid } useRoute := bindToInterfaceByRoute.Load() || bindToInterfaceByRouteEnv() diff --git a/net/netns/netns_darwin_test.go b/net/netns/netns_darwin_test.go index e1dc00b42..0fc92f6f3 100644 --- a/net/netns/netns_darwin_test.go +++ b/net/netns/netns_darwin_test.go @@ -34,7 +34,7 @@ func TestGetInterfaceIndex(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - idx, err := getInterfaceIndex(t.Logf, tc.addr) + idx, err := getInterfaceIndex(t.Logf, nil, tc.addr) if err != nil { if tc.err == "" { t.Fatalf("got unexpected error: %v", err) @@ -68,7 +68,7 @@ func TestGetInterfaceIndex(t *testing.T) { t.Fatal(err) } - idx, err := getInterfaceIndex(t.Logf, "100.100.100.100:53") + idx, err := getInterfaceIndex(t.Logf, nil, "100.100.100.100:53") if err != nil { t.Fatal(err) } diff --git a/net/netns/netns_default.go b/net/netns/netns_default.go index 43a573959..94f24d8fa 100644 --- a/net/netns/netns_default.go +++ b/net/netns/netns_default.go @@ -8,10 +8,11 @@ package netns import ( "syscall" + "tailscale.com/net/netmon" "tailscale.com/types/logger" ) -func control(logger.Logf) func(network, address string, c syscall.RawConn) error { +func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error { return controlC } diff --git a/net/netns/netns_linux.go b/net/netns/netns_linux.go index dea6a9e8d..5d09d7d19 100644 --- a/net/netns/netns_linux.go +++ b/net/netns/netns_linux.go @@ -15,6 +15,7 @@ import ( "golang.org/x/sys/unix" "tailscale.com/envknob" "tailscale.com/net/interfaces" + "tailscale.com/net/netmon" "tailscale.com/types/logger" ) @@ -85,7 +86,7 @@ func ignoreErrors() bool { return false } -func control(logger.Logf) func(network, address string, c syscall.RawConn) error { +func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error { return controlC } diff --git a/net/netns/netns_test.go b/net/netns/netns_test.go index d95c04981..82f919b94 100644 --- a/net/netns/netns_test.go +++ b/net/netns/netns_test.go @@ -24,7 +24,7 @@ func TestDial(t *testing.T) { if !*extNetwork { t.Skip("skipping test without --use-external-network") } - d := NewDialer(t.Logf) + d := NewDialer(t.Logf, nil) c, err := d.Dial("tcp", "google.com:80") if err != nil { t.Fatal(err) diff --git a/net/netns/netns_windows.go b/net/netns/netns_windows.go index 147a762ec..8aa1da18d 100644 --- a/net/netns/netns_windows.go +++ b/net/netns/netns_windows.go @@ -12,6 +12,7 @@ import ( "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "tailscale.com/net/interfaces" + "tailscale.com/net/netmon" "tailscale.com/types/logger" ) @@ -26,7 +27,7 @@ func interfaceIndex(iface *winipcfg.IPAdapterAddresses) uint32 { return iface.IfIndex } -func control(logger.Logf) func(network, address string, c syscall.RawConn) error { +func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error { return controlC } diff --git a/net/ping/ping.go b/net/ping/ping.go index f4bd7f0e0..9b9618e0f 100644 --- a/net/ping/ping.go +++ b/net/ping/ping.go @@ -18,6 +18,7 @@ import ( "golang.org/x/net/icmp" "golang.org/x/net/ipv4" + "tailscale.com/net/netmon" "tailscale.com/net/netns" "tailscale.com/types/logger" ) @@ -52,8 +53,9 @@ type Pinger struct { // New creates a new Pinger. The Context provided will be used to create // network listeners, and to set an absolute deadline (if any) on the net.Conn -func New(ctx context.Context, logf logger.Logf) (*Pinger, error) { - p, err := newUnstarted(ctx, logf) +// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. +func New(ctx context.Context, logf logger.Logf, netMon *netmon.Monitor) (*Pinger, error) { + p, err := newUnstarted(ctx, logf, netMon) if err != nil { return nil, err } @@ -72,14 +74,14 @@ func New(ctx context.Context, logf logger.Logf) (*Pinger, error) { return p, nil } -func newUnstarted(ctx context.Context, logf logger.Logf) (*Pinger, error) { +func newUnstarted(ctx context.Context, logf logger.Logf, netMon *netmon.Monitor) (*Pinger, error) { var id [2]byte _, err := rand.Read(id[:]) if err != nil { return nil, err } - conn, err := netns.Listener(logf).ListenPacket(ctx, "ip4:icmp", "0.0.0.0") + conn, err := netns.Listener(logf, netMon).ListenPacket(ctx, "ip4:icmp", "0.0.0.0") if err != nil { return nil, err } diff --git a/net/portmapper/igd_test.go b/net/portmapper/igd_test.go index f48959631..57c0d7aa6 100644 --- a/net/portmapper/igd_test.go +++ b/net/portmapper/igd_test.go @@ -249,7 +249,7 @@ func (d *TestIGD) handlePCPQuery(pkt []byte, src netip.AddrPort) { func newTestClient(t *testing.T, igd *TestIGD) *Client { var c *Client - c = NewClient(t.Logf, nil, func() { + c = NewClient(t.Logf, nil, nil, func() { t.Logf("port map changed") t.Logf("have mapping: %v", c.HaveMapping()) }) diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go index 2d8c06591..2e500b555 100644 --- a/net/portmapper/portmapper.go +++ b/net/portmapper/portmapper.go @@ -21,6 +21,7 @@ import ( "tailscale.com/net/interfaces" "tailscale.com/net/netaddr" "tailscale.com/net/neterror" + "tailscale.com/net/netmon" "tailscale.com/net/netns" "tailscale.com/net/sockstats" "tailscale.com/types/logger" @@ -59,6 +60,7 @@ const trustServiceStillAvailableDuration = 10 * time.Minute // Client is a port mapping client. type Client struct { logf logger.Logf + netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand ipAndGateway func() (gw, ip netip.Addr, ok bool) onChange func() // or nil debug DebugKnobs @@ -153,15 +155,19 @@ func (m *pmpMapping) Release(ctx context.Context) { // NewClient returns a new portmapping client. // +// The netMon parameter is optional; if non-nil it's used to do faster interface +// lookups. +// // The debug argument allows configuring the behaviour of the portmapper for // debugging; if nil, a sensible set of defaults will be used. // // The optional onChange argument specifies a func to run in a new // goroutine whenever the port mapping status has changed. If nil, // it doesn't make a callback. -func NewClient(logf logger.Logf, debug *DebugKnobs, onChange func()) *Client { +func NewClient(logf logger.Logf, netMon *netmon.Monitor, debug *DebugKnobs, onChange func()) *Client { ret := &Client{ logf: logf, + netMon: netMon, ipAndGateway: interfaces.LikelyHomeRouterIP, onChange: onChange, } @@ -271,7 +277,7 @@ func (c *Client) listenPacket(ctx context.Context, network, addr string) (nettyp } return pc.(*net.UDPConn), nil } - pc, err := netns.Listener(c.logf).ListenPacket(ctx, network, addr) + pc, err := netns.Listener(c.logf, c.netMon).ListenPacket(ctx, network, addr) if err != nil { return nil, err } diff --git a/net/portmapper/portmapper_test.go b/net/portmapper/portmapper_test.go index 67c37f8bd..15f2849fc 100644 --- a/net/portmapper/portmapper_test.go +++ b/net/portmapper/portmapper_test.go @@ -16,7 +16,7 @@ func TestCreateOrGetMapping(t *testing.T) { if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v { t.Skip("skipping test without HIT_NETWORK=1") } - c := NewClient(t.Logf, nil, nil) + c := NewClient(t.Logf, nil, nil, nil) defer c.Close() c.SetLocalPort(1234) for i := 0; i < 2; i++ { @@ -32,7 +32,7 @@ func TestClientProbe(t *testing.T) { if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v { t.Skip("skipping test without HIT_NETWORK=1") } - c := NewClient(t.Logf, nil, nil) + c := NewClient(t.Logf, nil, nil, nil) defer c.Close() for i := 0; i < 3; i++ { if i > 0 { @@ -47,7 +47,7 @@ func TestClientProbeThenMap(t *testing.T) { if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v { t.Skip("skipping test without HIT_NETWORK=1") } - c := NewClient(t.Logf, nil, nil) + c := NewClient(t.Logf, nil, nil, nil) defer c.Close() c.SetLocalPort(1234) res, err := c.Probe(context.Background()) diff --git a/net/portmapper/upnp.go b/net/portmapper/upnp.go index 5844d7428..6a525c54f 100644 --- a/net/portmapper/upnp.go +++ b/net/portmapper/upnp.go @@ -237,7 +237,7 @@ func (c *Client) upnpHTTPClientLocked() *http.Client { if c.uPnPHTTPClient == nil { c.uPnPHTTPClient = &http.Client{ Transport: &http.Transport{ - DialContext: netns.NewDialer(c.logf).DialContext, + DialContext: netns.NewDialer(c.logf, c.netMon).DialContext, IdleConnTimeout: 2 * time.Second, // LAN is cheap }, } diff --git a/net/sockstats/sockstats.go b/net/sockstats/sockstats.go index e968ac7bf..b39d60afb 100644 --- a/net/sockstats/sockstats.go +++ b/net/sockstats/sockstats.go @@ -109,8 +109,8 @@ func GetValidation() *ValidationSockStats { // SetNetMon configures the sockstats package to monitor the active // interface, so that per-interface stats can be collected. -func SetNetMon(lm *netmon.Monitor) { - setNetMon(lm) +func SetNetMon(netMon *netmon.Monitor) { + setNetMon(netMon) } // DebugInfo returns a string containing debug information about the tracked diff --git a/net/sockstats/sockstats_noop.go b/net/sockstats/sockstats_noop.go index 46b5ceaef..96723111a 100644 --- a/net/sockstats/sockstats_noop.go +++ b/net/sockstats/sockstats_noop.go @@ -30,7 +30,7 @@ func getValidation() *ValidationSockStats { return nil } -func setNetMon(lm *netmon.Monitor) { +func setNetMon(netMon *netmon.Monitor) { } func debugInfo() string { diff --git a/net/sockstats/sockstats_tsgo.go b/net/sockstats/sockstats_tsgo.go index e2dd05f22..26211958f 100644 --- a/net/sockstats/sockstats_tsgo.go +++ b/net/sockstats/sockstats_tsgo.go @@ -249,13 +249,13 @@ func getValidation() *ValidationSockStats { return r } -func setNetMon(lm *netmon.Monitor) { +func setNetMon(netMon *netmon.Monitor) { sockStats.mu.Lock() defer sockStats.mu.Unlock() // We intentionally populate all known interfaces now, so that we can // increment stats for them without holding mu. - state := lm.InterfaceState() + state := netMon.InterfaceState() for ifName, iface := range state.Interface { sockStats.knownInterfaces[iface.Index] = ifName } @@ -266,7 +266,7 @@ func setNetMon(lm *netmon.Monitor) { sockStats.usedInterfaces[ifIndex] = 1 } - lm.RegisterChangeCallback(func(changed bool, state *interfaces.State) { + netMon.RegisterChangeCallback(func(changed bool, state *interfaces.State) { if changed { if ifName := state.DefaultRouteInterface; ifName != "" { ifIndex := state.Interface[ifName].Index diff --git a/net/tsdial/tsdial.go b/net/tsdial/tsdial.go index bb1dde397..54e91ca13 100644 --- a/net/tsdial/tsdial.go +++ b/net/tsdial/tsdial.go @@ -128,14 +128,14 @@ func (d *Dialer) Close() error { return nil } -func (d *Dialer) SetNetMon(mon *netmon.Monitor) { +func (d *Dialer) SetNetMon(netMon *netmon.Monitor) { d.mu.Lock() defer d.mu.Unlock() if d.netMonUnregister != nil { go d.netMonUnregister() d.netMonUnregister = nil } - d.netMon = mon + d.netMon = netMon d.netMonUnregister = d.netMon.RegisterChangeCallback(d.linkChanged) } @@ -279,7 +279,7 @@ func (d *Dialer) SystemDial(ctx context.Context, network, addr string) (net.Conn if logf == nil { logf = logger.Discard } - d.netnsDialer = netns.NewDialer(logf) + d.netnsDialer = netns.NewDialer(logf, d.netMon) }) c, err := d.netnsDialer.DialContext(ctx, network, addr) if err != nil { diff --git a/prober/derp.go b/prober/derp.go index 21583a9bf..cd267e27e 100644 --- a/prober/derp.go +++ b/prober/derp.go @@ -346,7 +346,7 @@ func newConn(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode) (*de return !strings.Contains(s, "derphttp.Client.Connect: connecting to") }) priv := key.NewNode() - dc := derphttp.NewRegionClient(priv, l, func() *tailcfg.DERPRegion { + dc := derphttp.NewRegionClient(priv, l, nil /* no netMon */, func() *tailcfg.DERPRegion { rid := n.RegionID return &tailcfg.DERPRegion{ RegionID: rid, diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index 6e7db9440..2bea9026b 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -203,7 +203,7 @@ func (s *Server) Loopback() (addr string, proxyCred, localAPICred string, err er // out the CONNECT code from tailscaled/proxy.go that uses // httputil.ReverseProxy and adding auth support. go func() { - lah := localapi.NewHandler(s.lb, s.logf, s.logid) + lah := localapi.NewHandler(s.lb, s.logf, s.netMon, s.logid) lah.PermitWrite = true lah.PermitRead = true lah.RequiredPassword = s.localAPICred @@ -564,7 +564,7 @@ func (s *Server) start() (reterr error) { go s.printAuthURLLoop() // Run the localapi handler, to allow fetching LetsEncrypt certs. - lah := localapi.NewHandler(lb, logf, s.logid) + lah := localapi.NewHandler(lb, logf, s.netMon, s.logid) lah.PermitWrite = true lah.PermitRead = true @@ -620,7 +620,7 @@ func (s *Server) startLogger(closePool *closeOnErrorPool) error { } return w }, - HTTPC: &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost)}, + HTTPC: &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost, s.netMon)}, } s.logtail = logtail.NewLogger(c, s.logf) closePool.addFunc(func() { s.logtail.Shutdown(context.Background()) }) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 5a0a2bf0d..123b2e70b 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -642,7 +642,7 @@ func NewConn(opts Options) (*Conn, error) { c.idleFunc = opts.IdleFunc c.testOnlyPacketListener = opts.TestOnlyPacketListener c.noteRecvActivity = opts.NoteRecvActivity - c.portMapper = portmapper.NewClient(logger.WithPrefix(c.logf, "portmapper: "), nil, c.onPortMapChanged) + c.portMapper = portmapper.NewClient(logger.WithPrefix(c.logf, "portmapper: "), opts.NetMon, nil, c.onPortMapChanged) if opts.NetMon != nil { c.portMapper.SetGatewayLookupFunc(opts.NetMon.GatewayAndSelfIP) } @@ -656,6 +656,7 @@ func NewConn(opts Options) (*Conn, error) { c.donec = c.connCtx.Done() c.netChecker = &netcheck.Client{ Logf: logger.WithPrefix(c.logf, "netcheck: "), + NetMon: c.netMon, GetSTUNConn4: func() netcheck.STUNConn { return &c.pconn4 }, GetSTUNConn6: func() netcheck.STUNConn { return &c.pconn6 }, SkipExternalNetwork: inTest(), @@ -1554,7 +1555,7 @@ func (c *Conn) derpWriteChanOfAddr(addr netip.AddrPort, peer key.NodePublic) cha // Note that derphttp.NewRegionClient does not dial the server // (it doesn't block) so it is safe to do under the c.mu lock. - dc := derphttp.NewRegionClient(c.privateKey, c.logf, func() *tailcfg.DERPRegion { + dc := derphttp.NewRegionClient(c.privateKey, c.logf, c.netMon, func() *tailcfg.DERPRegion { // Warning: it is not legal to acquire // magicsock.Conn.mu from this callback. // It's run from derphttp.Client.connect (via Send, etc) @@ -3251,7 +3252,7 @@ func (c *Conn) listenPacket(network string, port uint16) (nettype.PacketConn, er if c.testOnlyPacketListener != nil { return nettype.MakePacketListenerWithNetIP(c.testOnlyPacketListener).ListenPacket(ctx, network, addr) } - return nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf)).ListenPacket(ctx, network, addr) + return nettype.MakePacketListenerWithNetIP(netns.Listener(c.logf, c.netMon)).ListenPacket(ctx, network, addr) } var debugBindSocket = envknob.RegisterBool("TS_DEBUG_MAGICSOCK_BIND_SOCKET") diff --git a/wgengine/netlog/logger.go b/wgengine/netlog/logger.go index bb3f667a3..a694308e6 100644 --- a/wgengine/netlog/logger.go +++ b/wgengine/netlog/logger.go @@ -19,6 +19,7 @@ import ( "tailscale.com/logpolicy" "tailscale.com/logtail" "tailscale.com/net/connstats" + "tailscale.com/net/netmon" "tailscale.com/net/sockstats" "tailscale.com/net/tsaddr" "tailscale.com/smallzstd" @@ -91,7 +92,8 @@ var testClient *http.Client // is a non-tailscale IP address to contact for that particular tailscale node. // The IP protocol and source port are always zero. // The sock is used to populated the PhysicalTraffic field in Message. -func (nl *Logger) Startup(nodeID tailcfg.StableNodeID, nodeLogID, domainLogID logid.PrivateID, tun, sock Device) error { +// The netMon parameter is optional; if non-nil it's used to do faster interface lookups. +func (nl *Logger) Startup(nodeID tailcfg.StableNodeID, nodeLogID, domainLogID logid.PrivateID, tun, sock Device, netMon *netmon.Monitor) error { nl.mu.Lock() defer nl.mu.Unlock() if nl.logger != nil { @@ -99,7 +101,7 @@ func (nl *Logger) Startup(nodeID tailcfg.StableNodeID, nodeLogID, domainLogID lo } // Startup a log stream to Tailscale's logging service. - httpc := &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost)} + httpc := &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost, netMon)} if testClient != nil { httpc = testClient } diff --git a/wgengine/userspace.go b/wgengine/userspace.go index c7caf1492..1356b36df 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -917,7 +917,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config, nid := cfg.NetworkLogging.NodeID tid := cfg.NetworkLogging.DomainID e.logf("wgengine: Reconfig: starting up network logger (node:%s tailnet:%s)", nid.Public(), tid.Public()) - if err := e.networkLogger.Startup(cfg.NodeID, nid, tid, e.tundev, e.magicConn); err != nil { + if err := e.networkLogger.Startup(cfg.NodeID, nid, tid, e.tundev, e.magicConn, e.netMon); err != nil { e.logf("wgengine: Reconfig: error starting up network logger: %v", err) } e.networkLogger.ReconfigRoutes(routerCfg)