mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-10-12 01:58:46 +02:00
168 lines
5.6 KiB
Go
168 lines
5.6 KiB
Go
package cli
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/Control-D-Inc/ctrld"
|
|
"tailscale.com/net/netmon"
|
|
)
|
|
|
|
// networkEventsNowFn is the clock of the network events. Tests replace it.
|
|
var networkEventsNowFn = time.Now
|
|
|
|
const (
|
|
hostWokeMessage = "Host woke"
|
|
|
|
// noiseDeltaMessage names the deltas that no part of the daemon acts on.
|
|
noiseDeltaMessage = "Network delta noise"
|
|
|
|
// interfaceChangedMessage names one interface that a delta changed.
|
|
interfaceChangedMessage = "Network interface changed"
|
|
|
|
// networkTransitionMessage names the outcome of one delta.
|
|
networkTransitionMessage = "Network transition"
|
|
|
|
// transitionOutcomeIgnored names a delta that left every usable interface
|
|
// as it was.
|
|
transitionOutcomeIgnored = "ignored"
|
|
|
|
// transitionOutcomeSnapshotSuperseded names a delta whose snapshot netmon
|
|
// already replaced. A newer callback reports the same interfaces.
|
|
transitionOutcomeSnapshotSuperseded = "snapshot_superseded"
|
|
)
|
|
|
|
// noteHostWoke reports one wake to the wake reporter. The caller passes the
|
|
// network of the wake, because the state that ctrld holds still describes the
|
|
// network before the sleep. netmon reports a time jump and measures no gap, so
|
|
// its report waits for the detector, and the journal gets one event per wake.
|
|
func (p *prog) noteHostWoke(source string, gap time.Duration, state *netmon.State) {
|
|
now := networkEventsNowFn()
|
|
// A resume moves the resolvers of the host, so every wake source starts the
|
|
// fast poll.
|
|
if p.dnsConfig != nil {
|
|
p.dnsConfig.noteActivity(now)
|
|
}
|
|
p.wake.note(now, wakeReport{source: source, gap: gap, state: state}, logHostWoke)
|
|
}
|
|
|
|
// logHostWoke writes the Host woke journal event of one report.
|
|
func logHostWoke(report wakeReport) {
|
|
gapKnown := report.gap > 0
|
|
event := journal(mainLog.Load().Info()).Str("source", report.source).Bool("gap_known", gapKnown)
|
|
if gapKnown {
|
|
event.Int64("gap_ms", report.gap.Milliseconds())
|
|
}
|
|
event.Str("default_route", defaultRouteOf(report.state)).
|
|
Int("interfaces_up", upInterfaceCount(report.state)).
|
|
Msg(hostWokeMessage)
|
|
}
|
|
|
|
// noteNoiseDelta records one delta of a noise storm. AirDrop and the virtual
|
|
// adapters of a container host change every few seconds, so the journal keeps
|
|
// one line per window while the debug stream keeps them all.
|
|
func (p *prog) noteNoiseDelta(changes []interfaceChange) {
|
|
names := changedInterfaceNames(changes)
|
|
mainLog.Load().Debug().Strs("interfaces", names).Msg(noiseDeltaMessage)
|
|
if summary, emit := p.networkNoise.add(names, networkEventsNowFn()); emit {
|
|
logNoiseSummary(summary)
|
|
}
|
|
}
|
|
|
|
// flushNoiseSummary closes the open noise window, because the delta that the
|
|
// daemon acts on ends the storm.
|
|
func (p *prog) flushNoiseSummary() {
|
|
if summary, emit := p.networkNoise.flush(networkEventsNowFn()); emit {
|
|
logNoiseSummary(summary)
|
|
}
|
|
}
|
|
|
|
// logNoiseSummary writes one journal line for a window of noise deltas.
|
|
func logNoiseSummary(summary noiseSummary) {
|
|
journal(mainLog.Load().Info()).
|
|
Int("count", summary.Count).
|
|
Strs("interfaces", summary.Interfaces).
|
|
Str("first_at", summary.First.Format(time.RFC3339)).
|
|
Str("last_at", summary.Last.Format(time.RFC3339)).
|
|
Msg(noiseDeltaMessage)
|
|
}
|
|
|
|
// logInterfaceChanges puts the interfaces of one transition in the journal.
|
|
// Support reads back the port that lost its address at the time of an outage.
|
|
// An AirDrop or a container adapter changes every few seconds, so the
|
|
// transition names it and the journal keeps the interfaces that carry the
|
|
// traffic of the host.
|
|
func logInterfaceChanges(transitionID uint64, changes []interfaceChange) {
|
|
for _, change := range changes {
|
|
if noiseClass(change.Class) {
|
|
continue
|
|
}
|
|
journal(mainLog.Load().Info()).
|
|
Uint64("transition_id", transitionID).
|
|
Str("interface", change.Name).
|
|
Str("action", change.Action).
|
|
Str("class", change.Class).
|
|
Str("hardware_port", change.HardwarePort).
|
|
Str("service", change.Service).
|
|
Strs("ips_before", change.IPsBefore).
|
|
Strs("ips_after", change.IPsAfter).
|
|
Str("flags", change.Flags).
|
|
Int("mtu", change.MTU).
|
|
Bool("is_default_route", change.IsDefaultRoute).
|
|
Msg(interfaceChangedMessage)
|
|
}
|
|
}
|
|
|
|
// networkTransitionEvent picks the level of one transition line. An ignored
|
|
// delta and a superseded snapshot arrive many times per minute, so the journal
|
|
// keeps the outcomes that can change the resolver state only.
|
|
func networkTransitionEvent(outcome string) *ctrld.LogEvent {
|
|
if outcome == transitionOutcomeIgnored || outcome == transitionOutcomeSnapshotSuperseded {
|
|
return mainLog.Load().Debug()
|
|
}
|
|
return journal(mainLog.Load().Info())
|
|
}
|
|
|
|
// changedInterfaceNames names the interfaces of one delta, in the order of the
|
|
// diff.
|
|
func changedInterfaceNames(changes []interfaceChange) []string {
|
|
names := make([]string, 0, len(changes))
|
|
for _, change := range changes {
|
|
names = append(names, change.Name)
|
|
}
|
|
return names
|
|
}
|
|
|
|
// hasAddOrRemove reports a delta that changed the adapter set of the host. The
|
|
// platform names of an adapter come from a cache, and that cache holds nothing
|
|
// for an adapter that just appeared.
|
|
func hasAddOrRemove(changes []interfaceChange) bool {
|
|
for _, change := range changes {
|
|
if change.Action == "added" || change.Action == "removed" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func defaultRouteOf(state *netmon.State) string {
|
|
if state == nil {
|
|
return ""
|
|
}
|
|
return state.DefaultRouteInterface
|
|
}
|
|
|
|
// upInterfaceCount counts the interfaces with the up flag. A netmon entry
|
|
// without an embedded interface counts as down.
|
|
func upInterfaceCount(state *netmon.State) int {
|
|
if state == nil {
|
|
return 0
|
|
}
|
|
up := 0
|
|
for _, iface := range state.Interface {
|
|
if iface.Interface != nil && iface.IsUp() {
|
|
up++
|
|
}
|
|
}
|
|
return up
|
|
}
|