On all four machines and owned by none. The module declares the package and the daemon. It leaves nsswitch.conf and nss-mdns as found — the hosts: line is one list every name source shares, and the host writes blocks, not line members — and opens nothing: the mesh's filter drops inbound UDP 5353 on every machine and `listens` has no local-link scope. avahi_status, _browse, _resolve and _services report both (to-be 42 Phase 1).
166 lines
5.8 KiB
Go
166 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const browse = `+;enp6s0;IPv4;home\032server;_ssh._tcp;local
|
|
+;enp6s0;IPv4;Printer\046Co;_ipp._tcp;local
|
|
=;enp6s0;IPv4;home\032server;_ssh._tcp;local;home-server.local;192.168.1.10;22;
|
|
=;enp6s0;IPv4;Printer\046Co;_ipp._tcp;local;printer.local;192.168.1.20;631;"txtvers=1" "rp=ipp/print"
|
|
+;enp6s0;IPv6;Kitchen;_spotify-connect._tcp;local
|
|
`
|
|
|
|
func TestABrowseIsReadResolvedOnceAndUnescaped(t *testing.T) {
|
|
s := ParseBrowse(browse)
|
|
if len(s) != 3 {
|
|
t.Fatalf("%+v", s)
|
|
}
|
|
by := map[string]Service{}
|
|
for _, x := range s {
|
|
by[x.Name] = x
|
|
}
|
|
ssh := by["home server"]
|
|
if !ssh.Resolved || ssh.Address != "192.168.1.10" || ssh.Port != 22 || ssh.Host != "home-server.local" {
|
|
t.Fatalf("%+v", ssh)
|
|
}
|
|
ipp := by["Printer.Co"]
|
|
if strings.Join(ipp.TXT, ",") != "txtvers=1,rp=ipp/print" {
|
|
t.Fatalf("%+v", ipp)
|
|
}
|
|
if k := by["Kitchen"]; k.Resolved || k.Type != "_spotify-connect._tcp" {
|
|
t.Fatalf("%+v", k)
|
|
}
|
|
if unescape(`caf\195\169`) != "café" || unescape(`a\.b`) != "a.b" {
|
|
t.Fatal("unescape")
|
|
}
|
|
}
|
|
|
|
func TestABrowseThatHearsNothingSaysTheFilterDropsMDNS(t *testing.T) {
|
|
var calls []call
|
|
m := machine(fake(func(c call) Ran {
|
|
switch c.String() {
|
|
case "timeout 5 avahi-browse -p -r -t -a":
|
|
return Ran{Status: 124}
|
|
case "sudo -n nft list ruleset":
|
|
return Ran{Stdout: "table inet mesh {\n chain input {\n type filter hook input priority filter; policy drop;\n tcp dport 22 accept\n }\n}\n"}
|
|
}
|
|
return Ran{Status: 99}
|
|
}, &calls), 1000)
|
|
r, err := m.Browse(5, "")
|
|
if err != nil || r["count"] != 0 || !strings.Contains(r["note"].(string), "drops inbound UDP 5353") {
|
|
t.Fatalf("%v %v", r, err)
|
|
}
|
|
if _, err := m.Browse(5, "ssh; rm"); err == nil {
|
|
t.Fatal("not a service type")
|
|
}
|
|
}
|
|
|
|
func TestTheFilterIsReadForAnAcceptedInboundMDNS(t *testing.T) {
|
|
for rs, want := range map[string]bool{
|
|
"\t\tudp dport 5353 accept\n": true,
|
|
"\t\tiifname \"enp6s0\" udp dport { 53, 5353 } accept\n": true,
|
|
"\t\tudp dport mdns accept\n": true,
|
|
"\t\tudp dport 53 accept\n": false,
|
|
"\t\tudp dport 5353 drop\n": false,
|
|
"\t\tip saddr 10.0.0.0/8 udp dport 15353 accept\n": false,
|
|
} {
|
|
if InboundMDNS(rs) != want {
|
|
t.Errorf("%q: %v", rs, !want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStatusNamesTheSwitchTheFilterAndTheDaemon(t *testing.T) {
|
|
m := machine(fake(func(c call) Ran {
|
|
switch {
|
|
case c.name == "systemctl" && c.args[1] == Daemon:
|
|
return Ran{Stdout: "LoadState=loaded\nActiveState=active\nUnitFileState=enabled\n"}
|
|
case c.name == "systemctl":
|
|
return Ran{Stdout: "ActiveState=inactive\n"}
|
|
case c.String() == "avahi-daemon --version":
|
|
return Ran{Stdout: "avahi-daemon 0.9-rc5\n"}
|
|
case c.String() == "pacman -Q nss-mdns":
|
|
return Ran{Stdout: "nss-mdns 0.15.1-2\n"}
|
|
case c.String() == "sudo -n nft list ruleset":
|
|
return Ran{Stdout: "udp dport 53 accept\n"}
|
|
}
|
|
return Ran{Status: 99}
|
|
}, nil), 1000)
|
|
files := map[string]string{
|
|
DaemonConf: "[server]\nuse-ipv4=yes\n#host-name=foo\nallow-interfaces=enp6s0\n[publish]\npublish-hinfo=no\n",
|
|
NSSwitch: "passwd: files\nhosts: mymachines files dns mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return]\n",
|
|
}
|
|
m.ReadFile = func(p string) ([]byte, error) {
|
|
if s, ok := files[p]; ok {
|
|
return []byte(s), nil
|
|
}
|
|
return nil, errNoFile
|
|
}
|
|
s, err := m.GetStatus()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !s.MDNSWired || s.NSSMDNS != "nss-mdns 0.15.1-2" || s.InboundMDNS == nil || *s.InboundMDNS || s.Version != "avahi-daemon 0.9-rc5" {
|
|
t.Fatalf("%+v", s)
|
|
}
|
|
if s.Config["server"]["allow-interfaces"] != "enp6s0" || s.Config["server"]["host-name"] != "" || s.Daemon["ActiveState"] != "active" {
|
|
t.Fatalf("%+v", s.Config)
|
|
}
|
|
if len(s.Notes) != 1 || !strings.Contains(s.Notes[0], "drops inbound UDP 5353") {
|
|
t.Fatalf("%v", s.Notes)
|
|
}
|
|
if _, wired := HostsLine("hosts: files dns\n"); wired {
|
|
t.Fatal("no mdns on the line")
|
|
}
|
|
}
|
|
|
|
func TestResolveAsksAvahiAndTheNameServiceAndReadsAFailureFromStderr(t *testing.T) {
|
|
m := machine(byLine(map[string]Ran{
|
|
"avahi-resolve -n printer.local": {Stdout: "printer.local\t192.168.1.20\n"},
|
|
"getent hosts printer.local": {Status: 2},
|
|
"avahi-resolve -n nowhere.local": {Stderr: "Failed to resolve host name 'nowhere.local': Timeout reached\n"},
|
|
"getent hosts nowhere.local": {Status: 2},
|
|
"sudo -n nft list ruleset": {Stdout: "udp dport 5353 accept\n"},
|
|
"avahi-resolve -a 192.168.1.20": {Stdout: "192.168.1.20\tprinter.local\n"},
|
|
}, nil), 1000)
|
|
r, err := m.Resolve("printer", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if r["avahi"].(map[string]any)["resolved"] != true || r["name_service"].(map[string]any)["resolved"] != false {
|
|
t.Fatalf("%v", r)
|
|
}
|
|
r, _ = m.Resolve("nowhere.local", "")
|
|
if a := r["avahi"].(map[string]any); a["resolved"] != false || !strings.Contains(a["error"].(string), "Timeout reached") || r["note"] != "nothing was heard on the local network" {
|
|
t.Fatalf("%v", r)
|
|
}
|
|
r, _ = m.Resolve("", "192.168.1.20")
|
|
if r["avahi"].(map[string]any)["answers"].([]string)[0] != "printer.local" {
|
|
t.Fatalf("%v", r)
|
|
}
|
|
for _, bad := range [][2]string{{"", ""}, {"a", "1.2.3.4"}, {"", "not-an-ip"}} {
|
|
if _, err := m.Resolve(bad[0], bad[1]); err == nil {
|
|
t.Errorf("%v accepted", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPublishedServicesAreReadFromTheirFiles(t *testing.T) {
|
|
m := machine(byLine(map[string]Ran{
|
|
"find /etc/avahi/services -mindepth 1 -maxdepth 1 -name *.service -printf %f\n": {Stdout: "ssh.service\n"},
|
|
}, nil), 1000)
|
|
m.ReadFile = func(string) ([]byte, error) {
|
|
return []byte(`<service-group><name replace-wildcards="yes">%h</name><service><type>_ssh._tcp</type><port>22</port></service></service-group>`), nil
|
|
}
|
|
r, err := m.Services()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
p := r["published"].([]Published)
|
|
if len(p) != 1 || p[0].Name != "%h" || p[0].Types[0] != "_ssh._tcp" || p[0].Ports[0] != 22 {
|
|
t.Fatalf("%+v", p)
|
|
}
|
|
}
|