@ -39,7 +39,6 @@ import (
"tailscale.com/net/dnscache"
"tailscale.com/net/dnscache"
"tailscale.com/net/dnsfallback"
"tailscale.com/net/dnsfallback"
"tailscale.com/net/netmon"
"tailscale.com/net/netmon"
"tailscale.com/net/netutil"
"tailscale.com/net/netx"
"tailscale.com/net/netx"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tsdial"
"tailscale.com/net/tsdial"
@ -64,30 +63,29 @@ import (
// Direct is the client that connects to a tailcontrol server for a node.
// Direct is the client that connects to a tailcontrol server for a node.
type Direct struct {
type Direct struct {
httpc * http . Client // HTTP client used to do TLS requests to control (just https://controlplane.tailscale.com/key?v=123)
httpc * http . Client // HTTP client used to do TLS requests to control (just https://controlplane.tailscale.com/key?v=123)
interceptedDial * atomic . Bool // if non-nil, pointer to bool whether ScreenTime intercepted our dial
interceptedDial * atomic . Bool // if non-nil, pointer to bool whether ScreenTime intercepted our dial
dialer * tsdial . Dialer
dialer * tsdial . Dialer
dnsCache * dnscache . Resolver
dnsCache * dnscache . Resolver
controlKnobs * controlknobs . Knobs // always non-nil
controlKnobs * controlknobs . Knobs // always non-nil
serverURL string // URL of the tailcontrol server
serverURL string // URL of the tailcontrol server
clock tstime . Clock
clock tstime . Clock
logf logger . Logf
logf logger . Logf
netMon * netmon . Monitor // non-nil
netMon * netmon . Monitor // non-nil
health * health . Tracker
health * health . Tracker
busClient * eventbus . Client
busClient * eventbus . Client
clientVersionPub * eventbus . Publisher [ tailcfg . ClientVersion ]
clientVersionPub * eventbus . Publisher [ tailcfg . ClientVersion ]
autoUpdatePub * eventbus . Publisher [ AutoUpdate ]
autoUpdatePub * eventbus . Publisher [ AutoUpdate ]
controlTimePub * eventbus . Publisher [ ControlTime ]
controlTimePub * eventbus . Publisher [ ControlTime ]
getMachinePrivKey func ( ) ( key . MachinePrivate , error )
getMachinePrivKey func ( ) ( key . MachinePrivate , error )
debugFlags [ ] string
debugFlags [ ] string
skipIPForwardingCheck bool
pinger Pinger
pinger Pinger
popBrowser func ( url string ) // or nil
popBrowser func ( url string ) // or nil
polc policyclient . Client // always non-nil
polc policyclient . Client // always non-nil
c2nHandler http . Handler // or nil
c2nHandler http . Handler // or nil
panicOnUse bool // if true, panic if client is used (for testing)
panicOnUse bool // if true, panic if client is used (for testing)
closedCtx context . Context // alive until Direct.Close is called
closedCtx context . Context // alive until Direct.Close is called
closeCtx context . CancelFunc // cancels closedCtx
closeCtx context . CancelFunc // cancels closedCtx
dialPlan ControlDialPlanner // can be nil
dialPlan ControlDialPlanner // can be nil
@ -95,6 +93,7 @@ type Direct struct {
serverLegacyKey key . MachinePublic // original ("legacy") nacl crypto_box-based public key; only used for signRegisterRequest on Windows now
serverLegacyKey key . MachinePublic // original ("legacy") nacl crypto_box-based public key; only used for signRegisterRequest on Windows now
serverNoiseKey key . MachinePublic
serverNoiseKey key . MachinePublic
discoPubKey key . DiscoPublic // protected by mu; can be updated via [SetDiscoPublicKey]
discoPubKey key . DiscoPublic // protected by mu; can be updated via [SetDiscoPublicKey]
ipForwardBroken bool // protected by mu; can be updated via [SetIPForwardingBroken]
sfGroup singleflight . Group [ struct { } , * ts2021 . Client ] // protects noiseClient creation.
sfGroup singleflight . Group [ struct { } , * ts2021 . Client ] // protects noiseClient creation.
noiseClient * ts2021 . Client // also protected by mu
noiseClient * ts2021 . Client // also protected by mu
@ -159,11 +158,6 @@ type Options struct {
// If nil, no status updates are reported.
// If nil, no status updates are reported.
Observer Observer
Observer Observer
// SkipIPForwardingCheck declares that the host's IP
// forwarding works and should not be double-checked by the
// controlclient package.
SkipIPForwardingCheck bool
// Pinger optionally specifies the Pinger to use to satisfy
// Pinger optionally specifies the Pinger to use to satisfy
// MapResponse.PingRequest queries from the control plane.
// MapResponse.PingRequest queries from the control plane.
// If nil, PingRequest queries are not answered.
// If nil, PingRequest queries are not answered.
@ -307,26 +301,25 @@ func NewDirect(opts Options) (*Direct, error) {
}
}
c := & Direct {
c := & Direct {
httpc : httpc ,
httpc : httpc ,
interceptedDial : interceptedDial ,
interceptedDial : interceptedDial ,
controlKnobs : opts . ControlKnobs ,
controlKnobs : opts . ControlKnobs ,
getMachinePrivKey : opts . GetMachinePrivateKey ,
getMachinePrivKey : opts . GetMachinePrivateKey ,
serverURL : opts . ServerURL ,
serverURL : opts . ServerURL ,
clock : opts . Clock ,
clock : opts . Clock ,
logf : opts . Logf ,
logf : opts . Logf ,
persist : opts . Persist . View ( ) ,
persist : opts . Persist . View ( ) ,
authKey : opts . AuthKey ,
authKey : opts . AuthKey ,
debugFlags : opts . DebugFlags ,
debugFlags : opts . DebugFlags ,
netMon : netMon ,
netMon : netMon ,
health : opts . HealthTracker ,
health : opts . HealthTracker ,
skipIPForwardingCheck : opts . SkipIPForwardingCheck ,
pinger : opts . Pinger ,
pinger : opts . Pinger ,
polc : cmp . Or ( opts . PolicyClient , policyclient . Client ( policyclient . NoPolicyClient { } ) ) ,
polc : cmp . Or ( opts . PolicyClient , policyclient . Client ( policyclient . NoPolicyClient { } ) ) ,
popBrowser : opts . PopBrowserURL ,
popBrowser : opts . PopBrowserURL ,
c2nHandler : opts . C2NHandler ,
c2nHandler : opts . C2NHandler ,
dialer : opts . Dialer ,
dialer : opts . Dialer ,
dnsCache : dnsCache ,
dnsCache : dnsCache ,
dialPlan : opts . DialPlan ,
dialPlan : opts . DialPlan ,
}
}
c . discoPubKey = opts . DiscoPublicKey
c . discoPubKey = opts . DiscoPublicKey
c . closedCtx , c . closeCtx = context . WithCancel ( context . Background ( ) )
c . closedCtx , c . closeCtx = context . WithCancel ( context . Background ( ) )
@ -861,6 +854,18 @@ func (c *Direct) SetDiscoPublicKey(key key.DiscoPublic) {
c . discoPubKey = key
c . discoPubKey = key
}
}
// SetIPForwardingBroken updates the IP forwarding broken state.
// It reports whether the value changed.
func ( c * Direct ) SetIPForwardingBroken ( v bool ) bool {
c . mu . Lock ( )
defer c . mu . Unlock ( )
if c . ipForwardBroken == v {
return false
}
c . ipForwardBroken = v
return true
}
// ClientID returns the controlClientID of the controlClient.
// ClientID returns the controlClientID of the controlClient.
func ( c * Direct ) ClientID ( ) int64 {
func ( c * Direct ) ClientID ( ) int64 {
return c . controlClientID
return c . controlClientID
@ -991,10 +996,6 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap
}
}
var extraDebugFlags [ ] string
var extraDebugFlags [ ] string
if buildfeatures . HasAdvertiseRoutes && hi != nil && c . netMon != nil && ! c . skipIPForwardingCheck &&
ipForwardingBroken ( hi . RoutableIPs , c . netMon . InterfaceState ( ) ) {
extraDebugFlags = append ( extraDebugFlags , "warn-ip-forwarding-off" )
}
if c . health . RouterHealth ( ) != nil {
if c . health . RouterHealth ( ) != nil {
extraDebugFlags = append ( extraDebugFlags , "warn-router-unhealthy" )
extraDebugFlags = append ( extraDebugFlags , "warn-router-unhealthy" )
}
}
@ -1413,24 +1414,6 @@ func initDevKnob() devKnobs {
var clock tstime . Clock = tstime . StdClock { }
var clock tstime . Clock = tstime . StdClock { }
// ipForwardingBroken reports whether the system's IP forwarding is disabled
// and will definitely not work for the routes provided.
//
// It should not return false positives.
//
// TODO(bradfitz): Change controlclient.Options.SkipIPForwardingCheck into a
// func([]netip.Prefix) error signature instead.
func ipForwardingBroken ( routes [ ] netip . Prefix , state * netmon . State ) bool {
warn , err := netutil . CheckIPForwarding ( routes , state )
if err != nil {
// Oh well, we tried. This is just for debugging.
// We don't want false positives.
// TODO: maybe we want a different warning for inability to check?
return false
}
return warn != nil
}
// isUniquePingRequest reports whether pr contains a new PingRequest.URL
// isUniquePingRequest reports whether pr contains a new PingRequest.URL
// not already handled, noting its value when returning true.
// not already handled, noting its value when returning true.
func ( c * Direct ) isUniquePingRequest ( pr * tailcfg . PingRequest ) bool {
func ( c * Direct ) isUniquePingRequest ( pr * tailcfg . PingRequest ) bool {