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.
120 lines
3.7 KiB
Go
120 lines
3.7 KiB
Go
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 ""
|
|
}
|