@ -87,7 +87,10 @@ type ChangeFunc func(*ChangeDelta)
// ChangeDelta describes the difference between two network states.
//
// Use NewChangeDelta to construct one and compute the cached fields.
// Use NewChangeDelta to construct a delta and compute the cached fields.
//
// TODO (barnstar): make new and old (and netmon.State) private once all consumers are updated
// to use the accessor methods.
type ChangeDelta struct {
// Old is the old interface state, if known.
// It's nil if the old state is unknown.
@ -108,6 +111,8 @@ type ChangeDelta struct {
// platforms know this or set it. Copied from netmon.Monitor.tsIfName.
TailscaleIfaceName string
DefaultRouteInterface string
// Computed Fields
DefaultInterfaceChanged bool // whether default route interface changed
@ -116,16 +121,15 @@ type ChangeDelta struct {
InterfaceIPsChanged bool // whether any interface IPs changed in a meaningful way
AvailableProtocolsChanged bool // whether we have seen a change in available IPv4/IPv6
DefaultInterfaceMaybeViable bool // whether the default interface is potentially viable (has usable IPs, is up and is not the tunnel itself)
IsInitialState bool // whether this is the initial state (old == nil)
// RebindLikelyRequired combines the various fields above to report whether this change likely requires us
// to rebind sockets. This is a very conservative estimate and covers a number of
// cases where a rebind is not strictly necessary. Consumers of the ChangeDelta should
// use this as a hint only. If in doubt, rebind .
// to rebind sockets. This is a very conservative estimate and covers a number of cases where a rebind
// may not be strictly necessary. Consumers of the ChangeDelta should consider checking the individual fields
// above or the state of their sockets .
RebindLikelyRequired bool
}
var skipRebindIfNoDefaultRouteChange = true
// NewChangeDelta builds a ChangeDelta and eagerly computes the cached fields.
func NewChangeDelta ( old , new * State , timeJumped bool , tsIfName string ) ChangeDelta {
cd := ChangeDelta {
@ -142,6 +146,7 @@ func NewChangeDelta(old, new *State, timeJumped bool, tsIfName string) ChangeDel
cd . IsLessExpensive = false
cd . HasPACOrProxyConfigChanged = true
cd . InterfaceIPsChanged = true
cd . IsInitialState = true
} else {
cd . AvailableProtocolsChanged = cd . Old . HaveV4 != cd . New . HaveV4 || cd . Old . HaveV6 != cd . New . HaveV6
cd . DefaultInterfaceChanged = cd . Old . DefaultRouteInterface != cd . New . DefaultRouteInterface
@ -152,11 +157,13 @@ func NewChangeDelta(old, new *State, timeJumped bool, tsIfName string) ChangeDel
// If the default route interface is populated, but it's not up this event signifies that we're in
// the process of tearing it down. Rebinds are going to fail so it's flappy to try.
defIfName := new . DefaultRouteInterface
defIf := new . Interface [ defIfName ]
cd . DefaultRouteInterface = new . DefaultRouteInterface
defIf := new . Interface [ cd . DefaultRouteInterface ]
cd . DefaultInterfaceMaybeViable = true
// The default interface is not viable if is down or is the Tailscale interface itself.
if ! defIf . IsUp ( ) || defIfNam e == tsIfName {
if ! defIf . IsUp ( ) || cd. DefaultRouteInterfac e == tsIfName {
cd . DefaultInterfaceMaybeViable = false
}
@ -181,6 +188,50 @@ func NewChangeDelta(old, new *State, timeJumped bool, tsIfName string) ChangeDel
return cd
}
// StateDesc returns a description of the old and new states for logging
func ( cd * ChangeDelta ) StateDesc ( ) string {
return fmt . Sprintf ( "old: %v new: %v" , cd . Old , cd . New )
}
// HasPAC reports whether the new state has a PAC configured.
func ( cd * ChangeDelta ) HasPAC ( ) bool {
if cd . New == nil {
return false
}
return cd . New . PAC != ""
}
// InterfaceIPAppeared reports whether the given IP address exists on any interface
// in the old state, but not in the new state.
func ( cd * ChangeDelta ) InterfaceIPDisppeared ( ip netip . Addr ) bool {
if cd . Old == nil {
return false
}
if cd . New == nil && cd . Old . HasIP ( ip ) {
return true
}
return cd . New . HasIP ( ip ) && ! cd . Old . HasIP ( ip )
}
// InterfaceIPDisappeared reports whether the given IP address existed on any interface
// in the old state, but not in the new state.
func ( cd * ChangeDelta ) InterfaceIPDisappeared ( ip netip . Addr ) bool {
return ! cd . New . HasIP ( ip ) && cd . Old . HasIP ( ip )
}
// AnyInterfaceUp reports whether any interfaces are up in the new state.
func ( cd * ChangeDelta ) AnyInterfaceUp ( ) bool {
if cd . New == nil {
return false
}
for _ , ifi := range cd . New . Interface {
if ifi . IsUp ( ) {
return true
}
}
return false
}
// isInterestingIntefaceChange reports whether any interfaces have changed in a meaninful way.
// This excludes interfaces that are not interesting per IsInterestingInterface and
// filters out changes to interface IPs that that are uninteresting (e.g. link-local addresses).