all: avoid repeated default interface lookups

On some platforms (notably macOS and iOS) we look up the default
interface to bind outgoing connections to. This is both duplicated
work and results in logspam when the default interface is not available
(i.e. when a phone has no connectivity, we log an error and thus cause
more things that we will try to upload and fail).

Fixed by passing around a netmon.Monitor to more places, so that we can
use its cached interface state.

Fixes #7850
Updates #7621

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
pull/7933/head
Mihai Parparita 2 years ago committed by Mihai Parparita
parent 7f17e04a5a
commit 7330aa593e

@ -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/interfaces from tailscale.com/net/netns+
tailscale.com/net/netaddr from tailscale.com/ipn+ tailscale.com/net/netaddr from tailscale.com/ipn+
tailscale.com/net/netknob from tailscale.com/net/netns 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/netns from tailscale.com/derp/derphttp
tailscale.com/net/netutil from tailscale.com/client/tailscale tailscale.com/net/netutil from tailscale.com/client/tailscale
tailscale.com/net/packet from tailscale.com/wgengine/filter tailscale.com/net/packet from tailscale.com/wgengine/filter

@ -19,6 +19,7 @@ import (
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/net/netcheck" "tailscale.com/net/netcheck"
"tailscale.com/net/netmon"
"tailscale.com/net/portmapper" "tailscale.com/net/portmapper"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -45,9 +46,14 @@ var netcheckArgs struct {
} }
func runNetcheck(ctx context.Context, args []string) error { 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{ c := &netcheck.Client{
UDPBindAddr: envknob.String("TS_DEBUG_NETCHECK_UDP_BIND"), 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 UseDNSCache: false, // always resolve, don't cache
} }
if netcheckArgs.verbose { if netcheckArgs.verbose {

@ -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/netcheck from tailscale.com/cmd/tailscale/cli
tailscale.com/net/neterror from tailscale.com/net/netcheck+ tailscale.com/net/neterror from tailscale.com/net/netcheck+
tailscale.com/net/netknob from tailscale.com/net/netns 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/netns from tailscale.com/derp/derphttp+
tailscale.com/net/netutil from tailscale.com/client/tailscale+ tailscale.com/net/netutil from tailscale.com/client/tailscale+
tailscale.com/net/packet from tailscale.com/wgengine/filter+ tailscale.com/net/packet from tailscale.com/wgengine/filter+

@ -193,8 +193,8 @@ func checkDerp(ctx context.Context, derpRegion string) (err error) {
priv1 := key.NewNode() priv1 := key.NewNode()
priv2 := key.NewNode() priv2 := key.NewNode()
c1 := derphttp.NewRegionClient(priv1, log.Printf, getRegion) c1 := derphttp.NewRegionClient(priv1, log.Printf, nil, getRegion)
c2 := derphttp.NewRegionClient(priv2, log.Printf, getRegion) c2 := derphttp.NewRegionClient(priv2, log.Printf, nil, getRegion)
defer func() { defer func() {
if err != nil { if err != nil {
c1.Close() c1.Close()

@ -329,7 +329,15 @@ var logPol *logpolicy.Policy
var debugMux *http.ServeMux var debugMux *http.ServeMux
func run() error { 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) pol.SetVerbosityLevel(args.verbose)
logPol = pol logPol = pol
defer func() { defer func() {
@ -353,7 +361,6 @@ func run() error {
return nil return nil
} }
var logf logger.Logf = log.Printf
if envknob.Bool("TS_DEBUG_MEMORY") { if envknob.Bool("TS_DEBUG_MEMORY") {
logf = logger.RusagePrefixLog(logf) logf = logger.RusagePrefixLog(logf)
} }
@ -379,10 +386,10 @@ func run() error {
debugMux = newDebugMux() 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) ln, err := safesocket.Listen(args.socketpath)
if err != nil { if err != nil {
return fmt.Errorf("safesocket.Listen: %v", err) 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 { if debugMux != nil {
debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus) debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus)
} }
@ -426,7 +433,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID)
return return
} }
} }
lb, err := getLocalBackend(ctx, logf, logID) lb, err := getLocalBackend(ctx, logf, logID, netMon)
if err == nil { if err == nil {
logf("got LocalBackend in %v", time.Since(t0).Round(time.Millisecond)) logf("got LocalBackend in %v", time.Since(t0).Round(time.Millisecond))
srv.SetLocalBackend(lb) srv.SetLocalBackend(lb)
@ -450,11 +457,7 @@ func startIPNServer(ctx context.Context, logf logger.Logf, logID logid.PublicID)
return nil return nil
} }
func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID) (_ *ipnlocal.LocalBackend, retErr error) { func getLocalBackend(ctx context.Context, logf logger.Logf, logID logid.PublicID, netMon *netmon.Monitor) (_ *ipnlocal.LocalBackend, retErr error) {
netMon, err := netmon.New(logf)
if err != nil {
return nil, fmt.Errorf("netmon.New: %w", err)
}
if logPol != nil { if logPol != nil {
logPol.Logtail.SetNetMon(netMon) logPol.Logtail.SetNetMon(netMon)
} }

@ -45,6 +45,7 @@ import (
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
"tailscale.com/logtail/backoff" "tailscale.com/logtail/backoff"
"tailscale.com/net/dns" "tailscale.com/net/dns"
"tailscale.com/net/netmon"
"tailscale.com/net/tstun" "tailscale.com/net/tstun"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/logid" "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) publicLogID, _ := logid.ParsePublicID(logID)
err := startIPNServer(ctx, log.Printf, publicLogID) err = startIPNServer(ctx, log.Printf, publicLogID, netMon)
if err != nil { if err != nil {
log.Fatalf("ipnserver: %v", err) log.Fatalf("ipnserver: %v", err)
} }

@ -123,7 +123,7 @@ func newIPN(jsConfig js.Value) map[string]any {
} }
logid := lpc.PublicID 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) lb, err := ipnlocal.NewLocalBackend(logf, logid, store, dialer, eng, controlclient.LoginEphemeral)
if err != nil { if err != nil {
log.Fatalf("ipnlocal.NewLocalBackend: %v", err) log.Fatalf("ipnlocal.NewLocalBackend: %v", err)

@ -211,8 +211,9 @@ 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.Lookup(opts.Logf), LookupIPFallback: dnsfallback.MakeLookupFunc(opts.Logf, opts.NetMon),
Logf: opts.Logf, Logf: opts.Logf,
NetMon: opts.NetMon,
} }
tr := http.DefaultTransport.(*http.Transport).Clone() tr := http.DefaultTransport.(*http.Transport).Clone()
tr.Proxy = tshttpproxy.ProxyFromEnvironment tr.Proxy = tshttpproxy.ProxyFromEnvironment
@ -1508,7 +1509,7 @@ func (c *Direct) getNoiseClient() (*NoiseClient, error) {
return nil, err return nil, err
} }
c.logf("creating new noise client") 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 { if err != nil {
return nil, err return nil, err
} }

@ -19,9 +19,11 @@ import (
"golang.org/x/net/http2" "golang.org/x/net/http2"
"tailscale.com/control/controlbase" "tailscale.com/control/controlbase"
"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"
"tailscale.com/types/logger"
"tailscale.com/util/mak" "tailscale.com/util/mak"
"tailscale.com/util/multierr" "tailscale.com/util/multierr"
"tailscale.com/util/singleflight" "tailscale.com/util/singleflight"
@ -167,6 +169,9 @@ type NoiseClient struct {
// be nil. // be nil.
dialPlan func() *tailcfg.ControlDialPlan dialPlan func() *tailcfg.ControlDialPlan
logf logger.Logf
netMon *netmon.Monitor
// mu only protects the following variables. // mu only protects the following variables.
mu sync.Mutex mu sync.Mutex
last *noiseConn // or nil last *noiseConn // or nil
@ -177,8 +182,9 @@ type NoiseClient struct {
// NewNoiseClient returns a new noiseClient for the provided server and machine key. // NewNoiseClient returns a new noiseClient for the provided server and machine key.
// serverURL is of the form https://<host>:<port> (no trailing slash). // serverURL is of the form https://<host>:<port> (no trailing slash).
// //
// netMon may be nil, if non-nil it's used to do faster interface lookups.
// dialPlan may be nil // 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) u, err := url.Parse(serverURL)
if err != nil { if err != nil {
return nil, err return nil, err
@ -207,6 +213,8 @@ func NewNoiseClient(privKey key.MachinePrivate, serverPubKey key.MachinePublic,
httpsPort: httpsPort, httpsPort: httpsPort,
dialer: dialer, dialer: dialer,
dialPlan: dialPlan, dialPlan: dialPlan,
logf: logf,
netMon: netMon,
} }
// Create the HTTP/2 Transport using a net/http.Transport // Create the HTTP/2 Transport using a net/http.Transport
@ -366,6 +374,8 @@ func (nc *NoiseClient) dial() (*noiseConn, error) {
ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion), ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion),
Dialer: nc.dialer.SystemDial, Dialer: nc.dialer.SystemDial,
DialPlan: dialPlan, DialPlan: dialPlan,
Logf: nc.logf,
NetMon: nc.netMon,
}).Dial(ctx) }).Dial(ctx)
if err != nil { if err != nil {
return nil, err return nil, err

@ -74,7 +74,7 @@ func (tt noiseClientTest) run(t *testing.T) {
defer hs.Close() defer hs.Close()
dialer := new(tsdial.Dialer) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -389,13 +389,15 @@ 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 = &dnscache.Resolver{ dns = &dnscache.Resolver{
Forward: dnscache.Get().Forward, Forward: dnscache.Get().Forward,
LookupIPFallback: dnsfallback.Lookup(a.logf), 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,
} }
} }

@ -9,6 +9,7 @@ import (
"time" "time"
"tailscale.com/net/dnscache" "tailscale.com/net/dnscache"
"tailscale.com/net/netmon"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -70,6 +71,8 @@ type Dialer struct {
// dropped. // dropped.
Logf logger.Logf Logf logger.Logf
NetMon *netmon.Monitor
// DialPlan, if set, contains instructions from the control server on // DialPlan, if set, contains instructions from the control server on
// how to connect to it. If present, we will try the methods in this // how to connect to it. If present, we will try the methods in this
// plan before falling back to DNS. // plan before falling back to DNS.

@ -31,6 +31,7 @@ import (
"tailscale.com/derp" "tailscale.com/derp"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/net/dnscache" "tailscale.com/net/dnscache"
"tailscale.com/net/netmon"
"tailscale.com/net/netns" "tailscale.com/net/netns"
"tailscale.com/net/sockstats" "tailscale.com/net/sockstats"
"tailscale.com/net/tlsdial" "tailscale.com/net/tlsdial"
@ -55,6 +56,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
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:
@ -88,11 +90,13 @@ 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.
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()) ctx, cancel := context.WithCancel(context.Background())
c := &Client{ c := &Client{
privateKey: privateKey, privateKey: privateKey,
logf: logf, logf: logf,
netMon: netMon,
getRegion: getRegion, getRegion: getRegion,
ctx: ctx, ctx: ctx,
cancelCtx: cancel, 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))) return c.dialer(ctx, "tcp", net.JoinHostPort(host, urlPort(c.url)))
} }
hostOrIP := host hostOrIP := host
dialer := netns.NewDialer(c.logf) dialer := netns.NewDialer(c.logf, c.netMon)
if c.DNSCache != nil { if c.DNSCache != nil {
ip, _, _, err := c.DNSCache.LookupIP(ctx, host) 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) { 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 // shouldDialProto reports whether an explicitly provided IPv4 or IPv6

@ -313,7 +313,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, store ipn.StateStor
loginFlags: loginFlags, 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 { if err != nil {
log.Printf("error setting up sockstat logger: %v", err) log.Printf("error setting up sockstat logger: %v", err)
} }

@ -37,7 +37,8 @@ func (s *Server) handleProxyConnectConn(w http.ResponseWriter, r *http.Request)
return return
} }
back, err := logpolicy.DialContext(ctx, "tcp", hostPort) dialContext := logpolicy.MakeDialFunc(s.netMon)
back, err := dialContext(ctx, "tcp", hostPort)
if err != nil { if err != nil {
s.logf("error CONNECT dialing %v: %v", hostPort, err) s.logf("error CONNECT dialing %v: %v", hostPort, err)
http.Error(w, "Connect failure", http.StatusBadGateway) http.Error(w, "Connect failure", http.StatusBadGateway)

@ -24,6 +24,7 @@ import (
"tailscale.com/ipn/ipnauth" "tailscale.com/ipn/ipnauth"
"tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/localapi" "tailscale.com/ipn/localapi"
"tailscale.com/net/netmon"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/logid" "tailscale.com/types/logid"
"tailscale.com/util/mak" "tailscale.com/util/mak"
@ -36,6 +37,7 @@ import (
type Server struct { type Server struct {
lb atomic.Pointer[ipnlocal.LocalBackend] lb atomic.Pointer[ipnlocal.LocalBackend]
logf logger.Logf logf logger.Logf
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand
backendLogID logid.PublicID backendLogID logid.PublicID
// resetOnZero is whether to call bs.Reset on transition from // resetOnZero is whether to call bs.Reset on transition from
// 1->0 active HTTP requests. That is, this is whether the backend is // 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() defer onDone()
if strings.HasPrefix(r.URL.Path, "/localapi/") { 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.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci)
lah.PermitCert = s.connCanFetchCerts(ci) lah.PermitCert = s.connCanFetchCerts(ci)
lah.ServeHTTP(w, r) lah.ServeHTTP(w, r)
@ -408,15 +410,18 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit
} }
// New returns a new Server. // 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. // To start it, use the Server.Run method.
// //
// At some point, either before or after Run, the Server's SetLocalBackend // At some point, either before or after Run, the Server's SetLocalBackend
// method must also be called before Server can do anything useful. // 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{ return &Server{
backendLogID: logID, backendLogID: logID,
logf: logf, logf: logf,
netMon: netMon,
resetOnZero: envknob.GOOS() == "windows", resetOnZero: envknob.GOOS() == "windows",
} }
} }

@ -140,7 +140,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
} }
checkSTUN4 := func(derpNode *tailcfg.DERPNode) { 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 { if err != nil {
st.Errors = append(st.Errors, fmt.Sprintf("Error creating IPv4 STUN listener: %v", err)) st.Errors = append(st.Errors, fmt.Sprintf("Error creating IPv4 STUN listener: %v", err))
return return
@ -249,7 +249,7 @@ func (h *Handler) serveDebugDERPRegion(w http.ResponseWriter, r *http.Request) {
serverPubKeys := make(map[key.NodePublic]bool) serverPubKeys := make(map[key.NodePublic]bool)
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
func() { func() {
rc := derphttp.NewRegionClient(fakePrivKey, h.logf, func() *tailcfg.DERPRegion { rc := derphttp.NewRegionClient(fakePrivKey, h.logf, h.netMon, func() *tailcfg.DERPRegion {
return &tailcfg.DERPRegion{ return &tailcfg.DERPRegion{
RegionID: reg.RegionID, RegionID: reg.RegionID,
RegionCode: reg.RegionCode, RegionCode: reg.RegionCode,

@ -125,8 +125,10 @@ var (
metrics = map[string]*clientmetric.Metric{} metrics = map[string]*clientmetric.Metric{}
) )
func NewHandler(b *ipnlocal.LocalBackend, logf logger.Logf, logID logid.PublicID) *Handler { // NewHandler creates a new LocalAPI HTTP handler. All parameters except netMon
return &Handler{b: b, logf: logf, backendLogID: logID} // 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 { type Handler struct {
@ -150,6 +152,7 @@ type Handler struct {
b *ipnlocal.LocalBackend b *ipnlocal.LocalBackend
logf logger.Logf logf logger.Logf
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand
backendLogID logid.PublicID backendLogID logid.PublicID
} }
@ -679,7 +682,7 @@ func (h *Handler) serveDebugPortmap(w http.ResponseWriter, r *http.Request) {
done := make(chan bool, 1) done := make(chan bool, 1)
var c *portmapper.Client 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("portmapping changed.")
logf("have mapping: %v", c.HaveMapping()) logf("have mapping: %v", c.HaveMapping())

@ -20,6 +20,7 @@ import (
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
"tailscale.com/logtail" "tailscale.com/logtail"
"tailscale.com/logtail/filch" "tailscale.com/logtail/filch"
"tailscale.com/net/netmon"
"tailscale.com/net/sockstats" "tailscale.com/net/sockstats"
"tailscale.com/smallzstd" "tailscale.com/smallzstd"
"tailscale.com/types/logger" "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. // 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.
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 { if !sockstats.IsAvailable {
return nil, nil return nil, nil
} }
@ -112,7 +114,7 @@ func NewLogger(logdir string, logf logger.Logf, logID logid.PublicID) (*Logger,
logger := &Logger{ logger := &Logger{
logf: logf, logf: logf,
filch: filch, filch: filch,
tr: logpolicy.NewLogtailTransport(logtail.DefaultHost), tr: logpolicy.NewLogtailTransport(logtail.DefaultHost, netMon),
} }
logger.logger = logtail.NewLogger(logtail.Config{ logger.logger = logtail.NewLogger(logtail.Config{
BaseURL: logpolicy.LogURL(), BaseURL: logpolicy.LogURL(),

@ -23,7 +23,7 @@ func TestResourceCleanup(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
lg, err := NewLogger(td, logger.Discard, id.Public()) lg, err := NewLogger(td, logger.Discard, id.Public(), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -37,6 +37,7 @@ import (
"tailscale.com/net/dnscache" "tailscale.com/net/dnscache"
"tailscale.com/net/dnsfallback" "tailscale.com/net/dnsfallback"
"tailscale.com/net/netknob" "tailscale.com/net/netknob"
"tailscale.com/net/netmon"
"tailscale.com/net/netns" "tailscale.com/net/netns"
"tailscale.com/net/tlsdial" "tailscale.com/net/tlsdial"
"tailscale.com/net/tshttpproxy" "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 // New returns a new log policy (a logger and its instance ID) for a
// given collection name. // given collection name.
func New(collection string) *Policy { // The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
return NewWithConfigPath(collection, "", "") func New(collection string, netMon *netmon.Monitor) *Policy {
return NewWithConfigPath(collection, "", "", netMon)
} }
// NewWithConfigPath is identical to New, // NewWithConfigPath is identical to New,
// but uses the specified directory and command name. // but uses the specified directory and command name.
// If either is empty, it derives them automatically. // 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 var lflags int
if term.IsTerminal(2) || runtime.GOOS == "windows" { if term.IsTerminal(2) || runtime.GOOS == "windows" {
lflags = 0 lflags = 0
@ -554,7 +556,7 @@ func NewWithConfigPath(collection, dir, cmdName string) *Policy {
} }
return w return w
}, },
HTTPC: &http.Client{Transport: NewLogtailTransport(logtail.DefaultHost)}, HTTPC: &http.Client{Transport: NewLogtailTransport(logtail.DefaultHost, netMon)},
} }
if collection == logtail.CollectionNode { if collection == logtail.CollectionNode {
conf.MetricsDelta = clientmetric.EncodeLogTailMetricsDelta 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.") 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 conf.BaseURL = val
u, _ := url.Parse(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{ filchOptions := filch.Options{
@ -670,13 +672,22 @@ func (p *Policy) Shutdown(ctx context.Context) error {
return nil 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: // It does the following:
// - If DNS lookup fails, consults the bootstrap DNS list of Tailscale hostnames. // - 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, // - 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.
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, Timeout: 30 * time.Second,
KeepAlive: netknob.PlatformTCPKeepAlive(), KeepAlive: netknob.PlatformTCPKeepAlive(),
}) })
@ -711,7 +722,8 @@ func DialContext(ctx context.Context, netw, addr string) (net.Conn, 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.Lookup(log.Printf), LookupIPFallback: dnsfallback.MakeLookupFunc(log.Printf, 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)
@ -723,7 +735,8 @@ func DialContext(ctx context.Context, netw, addr string) (net.Conn, error) {
// 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.
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() { if inTest() {
return noopPretendSuccessTransport{} return noopPretendSuccessTransport{}
} }
@ -739,7 +752,7 @@ func NewLogtailTransport(host string) http.RoundTripper {
tr.DisableCompression = true tr.DisableCompression = true
// Log whenever we dial: // Log whenever we dial:
tr.DialContext = DialContext tr.DialContext = MakeDialFunc(netMon)
// We're contacting exactly 1 hostname, so the default's 100 // We're contacting exactly 1 hostname, so the default's 100
// max idle conns is very high for our needs. Even 2 is // max idle conns is very high for our needs. Even 2 is

@ -64,6 +64,7 @@ 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, netMon *netmon.Monitor, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector) *Manager { func NewManager(logf logger.Logf, oscfg OSConfigurator, netMon *netmon.Monitor, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector) *Manager {
if dialer == nil { if dialer == nil {
panic("nil Dialer") panic("nil Dialer")

@ -380,11 +380,12 @@ func (f *forwarder) getKnownDoHClientForProvider(urlBase string) (c *http.Client
if err != nil { if err != nil {
return nil, false return nil, false
} }
nsDialer := netns.NewDialer(f.logf) nsDialer := netns.NewDialer(f.logf, f.netMon)
dialer := dnscache.Dialer(nsDialer.DialContext, &dnscache.Resolver{ dialer := dnscache.Dialer(nsDialer.DialContext, &dnscache.Resolver{
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{

@ -17,8 +17,8 @@ func init() {
initListenConfig = initListenConfigNetworkExtension initListenConfig = initListenConfigNetworkExtension
} }
func initListenConfigNetworkExtension(nc *net.ListenConfig, mon *netmon.Monitor, tunName string) error { func initListenConfigNetworkExtension(nc *net.ListenConfig, netMon *netmon.Monitor, tunName string) error {
nif, ok := mon.InterfaceState().Interface[tunName] nif, ok := netMon.InterfaceState().Interface[tunName]
if !ok { if !ok {
return errors.New("utun not found") return errors.New("utun not found")
} }

@ -997,7 +997,7 @@ func TestMarshalResponseFormatError(t *testing.T) {
func TestForwardLinkSelection(t *testing.T) { func TestForwardLinkSelection(t *testing.T) {
configCall := make(chan string, 1) 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 { select {
case configCall <- tunName: case configCall <- tunName:
return nil return nil

@ -21,6 +21,7 @@ import (
"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"
@ -91,6 +92,11 @@ 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

@ -23,6 +23,7 @@ import (
"time" "time"
"tailscale.com/atomicfile" "tailscale.com/atomicfile"
"tailscale.com/net/netmon"
"tailscale.com/net/netns" "tailscale.com/net/netns"
"tailscale.com/net/tlsdial" "tailscale.com/net/tlsdial"
"tailscale.com/net/tshttpproxy" "tailscale.com/net/tshttpproxy"
@ -31,13 +32,16 @@ import (
"tailscale.com/util/slicesx" "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 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() { if ip, err := netip.ParseAddr(host); err == nil && ip.IsValid() {
return []netip.Addr{ip}, nil 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) logf("trying bootstrapDNS(%q, %q) for %q ...", cand.dnsName, cand.ip, host)
ctx, cancel := context.WithTimeout(ctx, 3*time.Second) ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel() 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 { if err != nil {
logf("bootstrapDNS(%q, %q) for %q error: %v", cand.dnsName, cand.ip, host, err) logf("bootstrapDNS(%q, %q) for %q error: %v", cand.dnsName, cand.ip, host, err)
continue 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". // serverName and serverIP of are, say, "derpN.tailscale.com".
// queryName is the name being sought (e.g. "controlplane.tailscale.com"), passed as hint. // 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) { func bootstrapDNSMap(ctx context.Context, serverName string, serverIP netip.Addr, queryName string, logf logger.Logf, netMon *netmon.Monitor) (dnsMap, error) {
dialer := netns.NewDialer(logf) dialer := netns.NewDialer(logf, netMon)
tr := http.DefaultTransport.(*http.Transport).Clone() tr := http.DefaultTransport.(*http.Transport).Clone()
tr.Proxy = tshttpproxy.ProxyFromEnvironment tr.Proxy = tshttpproxy.ProxyFromEnvironment
tr.DialContext = func(ctx context.Context, netw, addr string) (net.Conn, error) { tr.DialContext = func(ctx context.Context, netw, addr string) (net.Conn, error) {

@ -29,6 +29,7 @@ import (
"tailscale.com/net/interfaces" "tailscale.com/net/interfaces"
"tailscale.com/net/netaddr" "tailscale.com/net/netaddr"
"tailscale.com/net/neterror" "tailscale.com/net/neterror"
"tailscale.com/net/netmon"
"tailscale.com/net/netns" "tailscale.com/net/netns"
"tailscale.com/net/ping" "tailscale.com/net/ping"
"tailscale.com/net/portmapper" "tailscale.com/net/portmapper"
@ -158,6 +159,11 @@ type Client struct {
// If nil, log.Printf is used. // If nil, log.Printf is used.
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
// TimeNow, if non-nil, is used instead of time.Now. // TimeNow, if non-nil, is used instead of time.Now.
TimeNow func() time.Time 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 return c.finishAndStoreReport(rs, dm), nil
} }
ifState, err := interfaces.GetState() var ifState *interfaces.State
if c.NetMon == nil {
directState, err := interfaces.GetState()
if err != nil { if err != nil {
c.logf("[v1] interfaces: %v", err) c.logf("[v1] interfaces: %v", err)
return nil, 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 // See if IPv6 works at all, or if it's been hard disabled at the
// OS level. // 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 { if err == nil {
rs.report.OSHasIPv6 = true rs.report.OSHasIPv6 = true
v6udp.Close() v6udp.Close()
} }
// Create a UDP4 socket used for sending to our discovered IPv4 address. // 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 { if err != nil {
c.logf("udp4: %v", err) c.logf("udp4: %v", err)
return nil, 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 { if f := c.GetSTUNConn4; f != nil {
rs.pc4 = f() rs.pc4 = f()
} else { } 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 { if err != nil {
c.logf("udp4: %v", err) c.logf("udp4: %v", err)
return nil, 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 { if f := c.GetSTUNConn6; f != nil {
rs.pc6 = f() rs.pc6 = f()
} else { } 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 { if err != nil {
c.logf("udp6: %v", err) c.logf("udp6: %v", err)
} else { } else {
@ -1297,7 +1310,7 @@ func (c *Client) measureAllICMPLatency(ctx context.Context, rs *reportState, nee
ctx, done := context.WithTimeout(ctx, icmpProbeTimeout) ctx, done := context.WithTimeout(ctx, icmpProbeTimeout)
defer done() defer done()
p, err := ping.New(ctx, c.logf) p, err := ping.New(ctx, c.logf, c.NetMon)
if err != nil { if err != nil {
return err return err
} }
@ -1635,6 +1648,7 @@ 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

@ -20,6 +20,7 @@ import (
"sync/atomic" "sync/atomic"
"tailscale.com/net/netknob" "tailscale.com/net/netknob"
"tailscale.com/net/netmon"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
@ -55,19 +56,21 @@ 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.
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() { if disabled.Load() {
return new(net.ListenConfig) 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 // NewDialer returns a new Dialer using a net.Dialer with its Control
// 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.
func NewDialer(logf logger.Logf) Dialer { // The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
return FromDialer(logf, &net.Dialer{ func NewDialer(logf logger.Logf, netMon *netmon.Monitor) Dialer {
return FromDialer(logf, netMon, &net.Dialer{
KeepAlive: netknob.PlatformTCPKeepAlive(), KeepAlive: netknob.PlatformTCPKeepAlive(),
}) })
} }
@ -76,11 +79,12 @@ func NewDialer(logf logger.Logf) 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.
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() { if disabled.Load() {
return d return d
} }
d.Control = control(logf) d.Control = control(logf, netMon)
if wrapDialer != nil { if wrapDialer != nil {
return wrapDialer(d) return wrapDialer(d)
} }

@ -10,6 +10,7 @@ import (
"sync" "sync"
"syscall" "syscall"
"tailscale.com/net/netmon"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
@ -49,7 +50,7 @@ func SetAndroidProtectFunc(f func(fd int) error) {
androidProtectFunc = f 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 return controlC
} }

@ -19,24 +19,25 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/net/interfaces" "tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/types/logger" "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 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 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. // controlLogf marks c as necessary to dial in a separate network namespace.
// //
// It's intentionally the same signature as net.Dialer.Control // It's intentionally the same signature as net.Dialer.Control
// and net.ListenConfig.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) { if isLocalhost(address) {
// Don't bind to an interface for localhost connections. // Don't bind to an interface for localhost connections.
return nil return nil
@ -47,7 +48,7 @@ func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) e
return nil return nil
} }
idx, err := getInterfaceIndex(logf, address) idx, err := getInterfaceIndex(logf, netMon, address)
if err != nil { if err != nil {
// callee logged // callee logged
return nil return nil
@ -56,9 +57,10 @@ func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) e
return bindConnToInterface(c, network, address, idx, logf) 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. // Helper so we can log errors.
defaultIdx := func() (int, error) { defaultIdx := func() (int, error) {
if netMon == nil {
idx, err := interfaces.DefaultRouteInterfaceIndex() idx, err := interfaces.DefaultRouteInterfaceIndex()
if err != nil { if err != nil {
// It's somewhat common for there to be no default gateway route // It's somewhat common for there to be no default gateway route
@ -71,6 +73,16 @@ func getInterfaceIndex(logf logger.Logf, address string) (int, error) {
} }
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() useRoute := bindToInterfaceByRoute.Load() || bindToInterfaceByRouteEnv()
if !useRoute { if !useRoute {

@ -34,7 +34,7 @@ func TestGetInterfaceIndex(t *testing.T) {
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { 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 err != nil {
if tc.err == "" { if tc.err == "" {
t.Fatalf("got unexpected error: %v", err) t.Fatalf("got unexpected error: %v", err)
@ -68,7 +68,7 @@ func TestGetInterfaceIndex(t *testing.T) {
t.Fatal(err) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -8,10 +8,11 @@ package netns
import ( import (
"syscall" "syscall"
"tailscale.com/net/netmon"
"tailscale.com/types/logger" "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 return controlC
} }

@ -15,6 +15,7 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/net/interfaces" "tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
@ -85,7 +86,7 @@ func ignoreErrors() bool {
return false 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 return controlC
} }

@ -24,7 +24,7 @@ func TestDial(t *testing.T) {
if !*extNetwork { if !*extNetwork {
t.Skip("skipping test without --use-external-network") 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") c, err := d.Dial("tcp", "google.com:80")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

@ -12,6 +12,7 @@ import (
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/net/interfaces" "tailscale.com/net/interfaces"
"tailscale.com/net/netmon"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
@ -26,7 +27,7 @@ func interfaceIndex(iface *winipcfg.IPAdapterAddresses) uint32 {
return iface.IfIndex 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 return controlC
} }

@ -18,6 +18,7 @@ import (
"golang.org/x/net/icmp" "golang.org/x/net/icmp"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"tailscale.com/net/netmon"
"tailscale.com/net/netns" "tailscale.com/net/netns"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
@ -52,8 +53,9 @@ type Pinger struct {
// New creates a new Pinger. The Context provided will be used to create // 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 // network listeners, and to set an absolute deadline (if any) on the net.Conn
func New(ctx context.Context, logf logger.Logf) (*Pinger, error) { // The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
p, err := newUnstarted(ctx, logf) func New(ctx context.Context, logf logger.Logf, netMon *netmon.Monitor) (*Pinger, error) {
p, err := newUnstarted(ctx, logf, netMon)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -72,14 +74,14 @@ func New(ctx context.Context, logf logger.Logf) (*Pinger, error) {
return p, nil 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 var id [2]byte
_, err := rand.Read(id[:]) _, err := rand.Read(id[:])
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }

@ -249,7 +249,7 @@ 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, func() { c = NewClient(t.Logf, nil, nil, 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())
}) })

@ -21,6 +21,7 @@ import (
"tailscale.com/net/interfaces" "tailscale.com/net/interfaces"
"tailscale.com/net/netaddr" "tailscale.com/net/netaddr"
"tailscale.com/net/neterror" "tailscale.com/net/neterror"
"tailscale.com/net/netmon"
"tailscale.com/net/netns" "tailscale.com/net/netns"
"tailscale.com/net/sockstats" "tailscale.com/net/sockstats"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -59,6 +60,7 @@ const trustServiceStillAvailableDuration = 10 * time.Minute
// Client is a port mapping client. // Client is a port mapping client.
type Client struct { type Client struct {
logf logger.Logf logf logger.Logf
netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand
ipAndGateway func() (gw, ip netip.Addr, ok bool) ipAndGateway func() (gw, ip netip.Addr, ok bool)
onChange func() // or nil onChange func() // or nil
debug DebugKnobs debug DebugKnobs
@ -153,15 +155,19 @@ 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
// 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.
// //
// The optional onChange argument specifies a func to run in a new // The optional onChange argument specifies a func to run in a new
// goroutine whenever the port mapping status has changed. If nil, // goroutine whenever the port mapping status has changed. If nil,
// it doesn't make a callback. // 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{ ret := &Client{
logf: logf, logf: logf,
netMon: netMon,
ipAndGateway: interfaces.LikelyHomeRouterIP, ipAndGateway: interfaces.LikelyHomeRouterIP,
onChange: onChange, onChange: onChange,
} }
@ -271,7 +277,7 @@ func (c *Client) listenPacket(ctx context.Context, network, addr string) (nettyp
} }
return pc.(*net.UDPConn), nil 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 { if err != nil {
return nil, err return nil, err
} }

@ -16,7 +16,7 @@ func TestCreateOrGetMapping(t *testing.T) {
if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v { if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v {
t.Skip("skipping test without HIT_NETWORK=1") t.Skip("skipping test without HIT_NETWORK=1")
} }
c := NewClient(t.Logf, nil, nil) c := NewClient(t.Logf, nil, nil, nil)
defer c.Close() defer c.Close()
c.SetLocalPort(1234) c.SetLocalPort(1234)
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
@ -32,7 +32,7 @@ func TestClientProbe(t *testing.T) {
if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v { if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v {
t.Skip("skipping test without HIT_NETWORK=1") t.Skip("skipping test without HIT_NETWORK=1")
} }
c := NewClient(t.Logf, nil, nil) c := NewClient(t.Logf, nil, nil, nil)
defer c.Close() defer c.Close()
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
if i > 0 { if i > 0 {
@ -47,7 +47,7 @@ func TestClientProbeThenMap(t *testing.T) {
if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v { if v, _ := strconv.ParseBool(os.Getenv("HIT_NETWORK")); !v {
t.Skip("skipping test without HIT_NETWORK=1") t.Skip("skipping test without HIT_NETWORK=1")
} }
c := NewClient(t.Logf, nil, nil) c := NewClient(t.Logf, nil, nil, nil)
defer c.Close() defer c.Close()
c.SetLocalPort(1234) c.SetLocalPort(1234)
res, err := c.Probe(context.Background()) res, err := c.Probe(context.Background())

@ -237,7 +237,7 @@ func (c *Client) upnpHTTPClientLocked() *http.Client {
if c.uPnPHTTPClient == nil { if c.uPnPHTTPClient == nil {
c.uPnPHTTPClient = &http.Client{ c.uPnPHTTPClient = &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
DialContext: netns.NewDialer(c.logf).DialContext, DialContext: netns.NewDialer(c.logf, c.netMon).DialContext,
IdleConnTimeout: 2 * time.Second, // LAN is cheap IdleConnTimeout: 2 * time.Second, // LAN is cheap
}, },
} }

@ -109,8 +109,8 @@ func GetValidation() *ValidationSockStats {
// SetNetMon configures the sockstats package to monitor the active // SetNetMon configures the sockstats package to monitor the active
// interface, so that per-interface stats can be collected. // interface, so that per-interface stats can be collected.
func SetNetMon(lm *netmon.Monitor) { func SetNetMon(netMon *netmon.Monitor) {
setNetMon(lm) setNetMon(netMon)
} }
// DebugInfo returns a string containing debug information about the tracked // DebugInfo returns a string containing debug information about the tracked

@ -30,7 +30,7 @@ func getValidation() *ValidationSockStats {
return nil return nil
} }
func setNetMon(lm *netmon.Monitor) { func setNetMon(netMon *netmon.Monitor) {
} }
func debugInfo() string { func debugInfo() string {

@ -249,13 +249,13 @@ func getValidation() *ValidationSockStats {
return r return r
} }
func setNetMon(lm *netmon.Monitor) { func setNetMon(netMon *netmon.Monitor) {
sockStats.mu.Lock() sockStats.mu.Lock()
defer sockStats.mu.Unlock() defer sockStats.mu.Unlock()
// We intentionally populate all known interfaces now, so that we can // We intentionally populate all known interfaces now, so that we can
// increment stats for them without holding mu. // increment stats for them without holding mu.
state := lm.InterfaceState() state := netMon.InterfaceState()
for ifName, iface := range state.Interface { for ifName, iface := range state.Interface {
sockStats.knownInterfaces[iface.Index] = ifName sockStats.knownInterfaces[iface.Index] = ifName
} }
@ -266,7 +266,7 @@ func setNetMon(lm *netmon.Monitor) {
sockStats.usedInterfaces[ifIndex] = 1 sockStats.usedInterfaces[ifIndex] = 1
} }
lm.RegisterChangeCallback(func(changed bool, state *interfaces.State) { netMon.RegisterChangeCallback(func(changed bool, state *interfaces.State) {
if changed { if changed {
if ifName := state.DefaultRouteInterface; ifName != "" { if ifName := state.DefaultRouteInterface; ifName != "" {
ifIndex := state.Interface[ifName].Index ifIndex := state.Interface[ifName].Index

@ -128,14 +128,14 @@ func (d *Dialer) Close() error {
return nil return nil
} }
func (d *Dialer) SetNetMon(mon *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.netMonUnregister != nil { if d.netMonUnregister != nil {
go d.netMonUnregister() go d.netMonUnregister()
d.netMonUnregister = nil d.netMonUnregister = nil
} }
d.netMon = mon d.netMon = netMon
d.netMonUnregister = d.netMon.RegisterChangeCallback(d.linkChanged) 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 { if logf == nil {
logf = logger.Discard logf = logger.Discard
} }
d.netnsDialer = netns.NewDialer(logf) d.netnsDialer = netns.NewDialer(logf, d.netMon)
}) })
c, err := d.netnsDialer.DialContext(ctx, network, addr) c, err := d.netnsDialer.DialContext(ctx, network, addr)
if err != nil { if err != nil {

@ -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") return !strings.Contains(s, "derphttp.Client.Connect: connecting to")
}) })
priv := key.NewNode() 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 rid := n.RegionID
return &tailcfg.DERPRegion{ return &tailcfg.DERPRegion{
RegionID: rid, RegionID: rid,

@ -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 // out the CONNECT code from tailscaled/proxy.go that uses
// httputil.ReverseProxy and adding auth support. // httputil.ReverseProxy and adding auth support.
go func() { 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.PermitWrite = true
lah.PermitRead = true lah.PermitRead = true
lah.RequiredPassword = s.localAPICred lah.RequiredPassword = s.localAPICred
@ -564,7 +564,7 @@ func (s *Server) start() (reterr error) {
go s.printAuthURLLoop() go s.printAuthURLLoop()
// Run the localapi handler, to allow fetching LetsEncrypt certs. // 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.PermitWrite = true
lah.PermitRead = true lah.PermitRead = true
@ -620,7 +620,7 @@ func (s *Server) startLogger(closePool *closeOnErrorPool) error {
} }
return w 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) s.logtail = logtail.NewLogger(c, s.logf)
closePool.addFunc(func() { s.logtail.Shutdown(context.Background()) }) closePool.addFunc(func() { s.logtail.Shutdown(context.Background()) })

@ -642,7 +642,7 @@ func NewConn(opts Options) (*Conn, error) {
c.idleFunc = opts.IdleFunc c.idleFunc = opts.IdleFunc
c.testOnlyPacketListener = opts.TestOnlyPacketListener c.testOnlyPacketListener = opts.TestOnlyPacketListener
c.noteRecvActivity = opts.NoteRecvActivity 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 { if opts.NetMon != nil {
c.portMapper.SetGatewayLookupFunc(opts.NetMon.GatewayAndSelfIP) c.portMapper.SetGatewayLookupFunc(opts.NetMon.GatewayAndSelfIP)
} }
@ -656,6 +656,7 @@ func NewConn(opts Options) (*Conn, error) {
c.donec = c.connCtx.Done() c.donec = c.connCtx.Done()
c.netChecker = &netcheck.Client{ c.netChecker = &netcheck.Client{
Logf: logger.WithPrefix(c.logf, "netcheck: "), Logf: logger.WithPrefix(c.logf, "netcheck: "),
NetMon: c.netMon,
GetSTUNConn4: func() netcheck.STUNConn { return &c.pconn4 }, GetSTUNConn4: func() netcheck.STUNConn { return &c.pconn4 },
GetSTUNConn6: func() netcheck.STUNConn { return &c.pconn6 }, GetSTUNConn6: func() netcheck.STUNConn { return &c.pconn6 },
SkipExternalNetwork: inTest(), 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 // Note that derphttp.NewRegionClient does not dial the server
// (it doesn't block) so it is safe to do under the c.mu lock. // (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 // Warning: it is not legal to acquire
// magicsock.Conn.mu from this callback. // magicsock.Conn.mu from this callback.
// It's run from derphttp.Client.connect (via Send, etc) // 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 { if c.testOnlyPacketListener != nil {
return nettype.MakePacketListenerWithNetIP(c.testOnlyPacketListener).ListenPacket(ctx, network, addr) 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") var debugBindSocket = envknob.RegisterBool("TS_DEBUG_MAGICSOCK_BIND_SOCKET")

@ -19,6 +19,7 @@ import (
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
"tailscale.com/logtail" "tailscale.com/logtail"
"tailscale.com/net/connstats" "tailscale.com/net/connstats"
"tailscale.com/net/netmon"
"tailscale.com/net/sockstats" "tailscale.com/net/sockstats"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/smallzstd" "tailscale.com/smallzstd"
@ -91,7 +92,8 @@ var testClient *http.Client
// is a non-tailscale IP address to contact for that particular tailscale node. // is a non-tailscale IP address to contact for that particular tailscale node.
// The IP protocol and source port are always zero. // The IP protocol and source port are always zero.
// The sock is used to populated the PhysicalTraffic field in Message. // 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() nl.mu.Lock()
defer nl.mu.Unlock() defer nl.mu.Unlock()
if nl.logger != nil { 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. // 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 { if testClient != nil {
httpc = testClient httpc = testClient
} }

@ -917,7 +917,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
nid := cfg.NetworkLogging.NodeID nid := cfg.NetworkLogging.NodeID
tid := cfg.NetworkLogging.DomainID tid := cfg.NetworkLogging.DomainID
e.logf("wgengine: Reconfig: starting up network logger (node:%s tailnet:%s)", nid.Public(), tid.Public()) 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.logf("wgengine: Reconfig: error starting up network logger: %v", err)
} }
e.networkLogger.ReconfigRoutes(routerCfg) e.networkLogger.ReconfigRoutes(routerCfg)

Loading…
Cancel
Save