net/netmon: make ChangeFunc's signature take new ChangeDelta, not bool

Updates #9040

Change-Id: Ia43752064a1a6ecefc8802b58d6eaa0b71cf1f84
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2023-08-23 10:05:21 -07:00
committed by Brad Fitzpatrick
parent 78f087aa02
commit 9089efea06
8 changed files with 87 additions and 53 deletions
+44 -12
View File
@@ -71,9 +71,37 @@ type Monitor struct {
}
// ChangeFunc is a callback function registered with Monitor that's called when the
// network changed. The changed parameter is whether the network changed
// enough for State to have changed since the last callback.
type ChangeFunc func(changed bool, state *interfaces.State)
// network changed.
type ChangeFunc func(*ChangeDelta)
// ChangeDelta describes the difference between two network states.
type ChangeDelta struct {
// Old is the old interface state, if known.
// It's nil if the old state is unknown.
// Do not mutate it.
Old *interfaces.State
// New is the new network state.
// It is always non-nil.
// Do not mutate it.
New *interfaces.State
// Major is our legacy boolean of whether the network changed in some major
// way.
//
// Deprecated: do not remove. As of 2023-08-23 we're in a renewed effort to
// remove it and ask specific qustions of ChangeDelta instead. Look at Old
// and New (or add methods to ChangeDelta) instead of using Major.
Major bool
// TimeJumped is whether there was a big jump in wall time since the last
// time we checked. This is a hint that a mobile sleeping device might have
// come out of sleep.
TimeJumped bool
// TODO(bradfitz): add some lazy cached fields here as needed with methods
// on *ChangeDelta to let callers ask specific questions
}
// New instantiates and starts a monitoring instance.
// The returned monitor is inactive until it's started by the Start method.
@@ -299,29 +327,33 @@ func (m *Monitor) debounce() {
} else {
m.mu.Lock()
oldState := m.ifState
changed := !curState.EqualFiltered(oldState, m.isInterestingInterface, interfaces.UseInterestingIPs)
if changed {
delta := &ChangeDelta{
Old: m.ifState,
New: curState,
}
delta.Major = !delta.New.EqualFiltered(delta.Old, m.isInterestingInterface, interfaces.UseInterestingIPs)
if delta.Major {
m.gwValid = false
m.ifState = curState
if s1, s2 := oldState.String(), curState.String(); s1 == s2 {
if s1, s2 := delta.Old.String(), delta.New.String(); s1 == s2 {
m.logf("[unexpected] network state changed, but stringification didn't: %v", s1)
m.logf("[unexpected] old: %s", jsonSummary(oldState))
m.logf("[unexpected] new: %s", jsonSummary(curState))
m.logf("[unexpected] old: %s", jsonSummary(delta.Old))
m.logf("[unexpected] new: %s", jsonSummary(delta.New))
}
}
// See if we have a queued or new time jump signal.
if shouldMonitorTimeJump && m.checkWallTimeAdvanceLocked() {
m.resetTimeJumpedLocked()
if !changed {
delta.TimeJumped = true
if !delta.Major {
// Only log if it wasn't an interesting change.
m.logf("time jumped (probably wake from sleep); synthesizing major change event")
changed = true
delta.Major = true
}
}
for _, cb := range m.cbs {
go cb(changed, m.ifState)
go cb(delta)
}
m.mu.Unlock()
}
+3 -5
View File
@@ -8,8 +8,6 @@ import (
"sync/atomic"
"testing"
"time"
"tailscale.com/net/interfaces"
)
func TestMonitorStartClose(t *testing.T) {
@@ -40,7 +38,7 @@ func TestMonitorInjectEvent(t *testing.T) {
}
defer mon.Close()
got := make(chan bool, 1)
mon.RegisterChangeCallback(func(changed bool, state *interfaces.State) {
mon.RegisterChangeCallback(func(*ChangeDelta) {
select {
case got <- true:
default:
@@ -101,9 +99,9 @@ func TestMonitorMode(t *testing.T) {
done = t.C
}
n := 0
mon.RegisterChangeCallback(func(changed bool, st *interfaces.State) {
mon.RegisterChangeCallback(func(d *ChangeDelta) {
n++
t.Logf("cb: changed=%v, ifSt=%v", changed, st)
t.Logf("cb: changed=%v, ifSt=%v", d.Major, d.New)
})
mon.Start()
<-done
+23 -19
View File
@@ -266,25 +266,29 @@ func setNetMon(netMon *netmon.Monitor) {
sockStats.usedInterfaces[ifIndex] = 1
}
netMon.RegisterChangeCallback(func(changed bool, state *interfaces.State) {
if changed {
if ifName := state.DefaultRouteInterface; ifName != "" {
ifIndex := state.Interface[ifName].Index
sockStats.mu.Lock()
defer sockStats.mu.Unlock()
// Ignore changes to unknown interfaces -- it would require
// updating the tx/rxBytesByInterface maps and thus
// additional locking for every read/write. Most of the time
// the set of interfaces is static.
if _, ok := sockStats.knownInterfaces[ifIndex]; ok {
sockStats.currentInterface.Store(uint32(ifIndex))
sockStats.usedInterfaces[ifIndex] = 1
sockStats.currentInterfaceCellular.Store(isLikelyCellularInterface(ifName))
} else {
sockStats.currentInterface.Store(0)
sockStats.currentInterfaceCellular.Store(false)
}
}
netMon.RegisterChangeCallback(func(delta *netmon.ChangeDelta) {
if !delta.Major {
return
}
state := delta.New
ifName := state.DefaultRouteInterface
if ifName == "" {
return
}
ifIndex := state.Interface[ifName].Index
sockStats.mu.Lock()
defer sockStats.mu.Unlock()
// Ignore changes to unknown interfaces -- it would require
// updating the tx/rxBytesByInterface maps and thus
// additional locking for every read/write. Most of the time
// the set of interfaces is static.
if _, ok := sockStats.knownInterfaces[ifIndex]; ok {
sockStats.currentInterface.Store(uint32(ifIndex))
sockStats.usedInterfaces[ifIndex] = 1
sockStats.currentInterfaceCellular.Store(isLikelyCellularInterface(ifName))
} else {
sockStats.currentInterface.Store(0)
sockStats.currentInterfaceCellular.Store(false)
}
})
}
+2 -3
View File
@@ -18,7 +18,6 @@ import (
"time"
"tailscale.com/net/dnscache"
"tailscale.com/net/interfaces"
"tailscale.com/net/netknob"
"tailscale.com/net/netmon"
"tailscale.com/net/netns"
@@ -139,8 +138,8 @@ func (d *Dialer) SetNetMon(netMon *netmon.Monitor) {
d.netMonUnregister = d.netMon.RegisterChangeCallback(d.linkChanged)
}
func (d *Dialer) linkChanged(major bool, state *interfaces.State) {
if !major {
func (d *Dialer) linkChanged(delta *netmon.ChangeDelta) {
if !delta.Major {
return
}
d.mu.Lock()