Judge the machine's own networking beside what its modules run (hq ADR 0241)
A VPN client rewrote the laptop's resolver file and every mesh name failed while each module read healthy: nothing asked the machine. The engine now looks every 30 s at the resolver file the uplink holder declared (naming the program that rewrote it), the names through each listed resolver (NXDOMAIN for a mesh name's IPv6 address is a finding, issue 262), the tunnel's handshake with the hub, the bus and the default route; a part is unhealthy on its second failing look, and the statement carries it.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Naming the program that rewrote the resolver file (ADR 0241 rule 2). **A guess, said as one**: the
|
||||
// mesh cannot see who wrote a file after the fact, only what the file, its link and the machine show. In
|
||||
// order of how much each says:
|
||||
//
|
||||
// 1. what the file says of itself — every program that writes it puts its name in a comment;
|
||||
// 2. a backup the writer left beside it — named for the writer, or changed when the file was;
|
||||
// 3. a program known to write the file, running now.
|
||||
//
|
||||
// Nothing found is said as nothing found, never as a name.
|
||||
|
||||
// signs are the words a writer leaves in the file's comments, and its name.
|
||||
var signs = []struct{ word, name string }{
|
||||
{"forti", "FortiClient"},
|
||||
{"openfortivpn", "openfortivpn"},
|
||||
{"networkmanager", "NetworkManager"},
|
||||
{"systemd-resolved", "systemd-resolved"},
|
||||
{"resolvconf", "resolvconf"},
|
||||
{"dhcpcd", "dhcpcd"},
|
||||
{"dhclient", "dhclient"},
|
||||
{"netconfig", "netconfig"},
|
||||
{"openvpn", "OpenVPN"},
|
||||
{"openconnect", "OpenConnect"},
|
||||
{"vpnc", "vpnc"},
|
||||
{"tailscale", "Tailscale"},
|
||||
{"connman", "ConnMan"},
|
||||
}
|
||||
|
||||
// writers are the programs known to rewrite the file, as they run, and their name. The VPN clients
|
||||
// first: they are what rewrites a file the machine's network manager was already told to keep off.
|
||||
var writers = []struct{ comm, name string }{
|
||||
{"fortivpn", "FortiClient"},
|
||||
{"forticlient", "FortiClient"},
|
||||
{"fctsched", "FortiClient"},
|
||||
{"openfortivpn", "openfortivpn"},
|
||||
{"openvpn", "OpenVPN"},
|
||||
{"openconnect", "OpenConnect"},
|
||||
{"vpnc", "vpnc"},
|
||||
{"charon", "strongSwan"},
|
||||
{"tailscaled", "Tailscale"},
|
||||
{"dhclient", "dhclient"},
|
||||
{"resolvconf", "resolvconf"},
|
||||
}
|
||||
|
||||
// writerOf names who rewrote the file, and why that name, from the file's own words, a backup changed
|
||||
// beside it, or a writer running.
|
||||
func (j *Judge) writerOf(content string, changed time.Time) (string, string) {
|
||||
for _, line := range strings.Split(content, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if !strings.HasPrefix(line, "#") && !strings.HasPrefix(line, ";") {
|
||||
continue
|
||||
}
|
||||
lower := strings.ToLower(line)
|
||||
for _, s := range signs {
|
||||
if strings.Contains(lower, s.word) {
|
||||
return s.name, "its own header names " + s.name
|
||||
}
|
||||
}
|
||||
}
|
||||
// A backup the writer kept beside it.
|
||||
backup := ""
|
||||
matches, _ := filepath.Glob(j.m.ResolvPath + "*")
|
||||
for _, m := range matches {
|
||||
if m == j.m.ResolvPath {
|
||||
continue
|
||||
}
|
||||
info, err := os.Stat(m)
|
||||
if err != nil || info.IsDir() {
|
||||
continue
|
||||
}
|
||||
// A backup that names its writer says so whenever it was made: a client that renames the file
|
||||
// aside (FortiClient does, on connect) leaves the backup with the old file's time, not its own.
|
||||
lower := strings.ToLower(filepath.Base(m))
|
||||
for _, s := range signs {
|
||||
if strings.Contains(lower, s.word) {
|
||||
return s.name, "it left " + m + " beside it"
|
||||
}
|
||||
}
|
||||
if d := info.ModTime().Sub(changed); d >= -time.Minute && d <= time.Minute {
|
||||
backup = m
|
||||
}
|
||||
}
|
||||
why := "no program it could be is known"
|
||||
if backup != "" {
|
||||
why = backup + " was changed when it was: whoever wrote it kept a copy there"
|
||||
}
|
||||
runningNow := map[string]bool{}
|
||||
for _, name := range j.m.Running() {
|
||||
runningNow[strings.ToLower(name)] = true
|
||||
}
|
||||
for _, w := range writers {
|
||||
if runningNow[w.comm] {
|
||||
return w.name + "?", w.comm + " is running; " + why
|
||||
}
|
||||
}
|
||||
return "", why
|
||||
}
|
||||
|
||||
// writerOfLink names the program a link points the file at.
|
||||
func writerOfLink(target string) string {
|
||||
lower := strings.ToLower(target)
|
||||
switch {
|
||||
case strings.Contains(lower, "systemd/resolve"):
|
||||
return "systemd-resolved"
|
||||
case strings.Contains(lower, "resolvconf"):
|
||||
return "resolvconf"
|
||||
case strings.Contains(lower, "networkmanager"):
|
||||
return "NetworkManager"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user