package dnsname import ( "net/netip" "strings" "testing" ) func TestOwner(t *testing.T) { zone := "example.com." cases := []struct{ in, want string }{ {"", "example.com."}, {"@", "example.com."}, {"www", "www.example.com."}, {"WWW", "www.example.com."}, {"www.example.com", "www.example.com."}, {"www.example.com.", "www.example.com."}, {"example.com", "example.com."}, {"a.b", "a.b.example.com."}, {"other.org.", "other.org."}, {"_acme-challenge.www", "_acme-challenge.www.example.com."}, {"bücher", "xn--bcher-kva.example.com."}, } for _, c := range cases { got, err := Owner(c.in, zone) if err != nil || got != c.want { t.Errorf("Owner(%q) = %q, %v; want %q", c.in, got, err, c.want) } } // Not fooled by a zone name that's a suffix of a label. got, _ := Owner("myexample.com", zone) if got != "myexample.com.example.com." { t.Errorf("Owner(myexample.com) = %q", got) } } func TestRelative(t *testing.T) { cases := []struct{ name, zone, want string }{ {"example.com.", "example.com.", "@"}, {"www.example.com.", "example.com.", "www"}, {"a.b.example.com.", "example.com.", "a.b"}, {"other.org.", "example.com.", "other.org."}, {"x.", ".", "x."}, } for _, c := range cases { if got := Relative(c.name, c.zone); got != c.want { t.Errorf("Relative(%q,%q) = %q want %q", c.name, c.zone, got, c.want) } } } func TestCheckOwner(t *testing.T) { ok := []string{".", "example.com.", "*.example.com.", "_dmarc.example.com.", "0/26.2.0.192.in-addr.arpa.", "a-b.c."} for _, n := range ok { if err := CheckOwner(n); err != nil { t.Errorf("CheckOwner(%q) = %v", n, err) } } bad := []string{"a..b.", "x.*.example.com.", "foo*.example.com.", "Upper.example.com.", "sp ace.com.", "example.com"} for _, n := range bad { if err := CheckOwner(n); err == nil { t.Errorf("CheckOwner(%q) succeeded, want error", n) } } long := "" for i := 0; i < 64; i++ { long += "a" } if CheckOwner(long+".com.") == nil { t.Error("64 octet label accepted") } if CheckZoneName("*.example.com.") == nil { t.Error("wildcard zone accepted") } } func TestIsHostname(t *testing.T) { cases := []struct { name string wildcard bool want bool }{ {"www.example.com.", false, true}, {"1host.example.com.", false, true}, {"foo_bar.example.com.", false, false}, {"-x.example.com.", false, false}, {"x-.example.com.", false, false}, {"*.example.com.", true, true}, {"*.example.com.", false, false}, {".", false, false}, {"a.b.", false, true}, } for _, c := range cases { if got := IsHostname(c.name, c.wildcard); got != c.want { t.Errorf("IsHostname(%q,%v) = %v", c.name, c.wildcard, got) } } } func TestMailbox(t *testing.T) { mb, err := MailboxFromEmail("first.last@example.com") if err != nil || mb != `first\.last.example.com.` { t.Fatalf("MailboxFromEmail = %q %v", mb, err) } if !IsMailbox(mb) { t.Error("IsMailbox false for escaped local part") } if got := EmailFromMailbox(mb); got != "first.last@example.com" { t.Errorf("EmailFromMailbox = %q", got) } if mb, _ := MailboxFromEmail("hostmaster.example.com."); mb != "hostmaster.example.com." { t.Errorf("domain form changed: %q", mb) } if IsMailbox("hostmaster.bad_domain.com.") { t.Error("bad domain part accepted") } } func TestReverse(t *testing.T) { a := netip.MustParseAddr("192.0.2.10") if got := ReverseName(a); got != "10.2.0.192.in-addr.arpa." { t.Errorf("ReverseName v4 = %q", got) } a6 := netip.MustParseAddr("2001:db8::1") r6 := ReverseName(a6) if r6 != "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa." { t.Errorf("ReverseName v6 = %q", r6) } for _, name := range []string{"10.2.0.192.in-addr.arpa.", r6} { back, ok := AddrFromReverse(name) if !ok { t.Errorf("AddrFromReverse(%q) failed", name) continue } if ReverseName(back) != name { t.Errorf("round trip %q -> %v", name, back) } } z, err := ReverseZone(netip.MustParsePrefix("192.168.1.0/24")) if err != nil || z != "1.168.192.in-addr.arpa." { t.Errorf("ReverseZone /24 = %q %v", z, err) } z, err = ReverseZone(netip.MustParsePrefix("10.0.0.0/8")) if err != nil || z != "10.in-addr.arpa." { t.Errorf("ReverseZone /8 = %q %v", z, err) } if _, err := ReverseZone(netip.MustParsePrefix("192.168.1.0/26")); err == nil { t.Error("ReverseZone /26 accepted") } z, err = ReverseZone(netip.MustParsePrefix("2001:db8::/32")) if err != nil || z != "8.b.d.0.1.0.0.2.ip6.arpa." { t.Errorf("ReverseZone v6 = %q %v", z, err) } if !IsReverse("1.168.192.in-addr.arpa.") || IsReverse("example.com.") { t.Error("IsReverse") } } func TestMisc(t *testing.T) { if Parent("www.example.com.") != "example.com." || Parent("com.") != "." || Parent(".") != "" { t.Error("Parent") } if CountLabels(".") != 0 || CountLabels("a.b.c.") != 3 { t.Error("CountLabels") } if BestZone("a.lab.example.com.", []string{"example.com.", "lab.example.com.", "other.com."}) != "lab.example.com." { t.Error("BestZone") } if FileSafe("0/26.2.0.192.in-addr.arpa.") != "0%2f26.2.0.192.in-addr.arpa" || FileSafe(".") != "root" { t.Errorf("FileSafe = %q", FileSafe("0/26.2.0.192.in-addr.arpa.")) } if !IsSubdomain("a.example.com.", "example.com.") || IsSubdomain("aexample.com.", "example.com.") || !IsSubdomain("x.", ".") { t.Error("IsSubdomain") } } func TestOwnerAddressInReverseZone(t *testing.T) { for _, tc := range []struct{ in, zone, want string }{ {"10.10.0.20", "0.10.10.in-addr.arpa.", "20.0.10.10.in-addr.arpa."}, {"20", "0.10.10.in-addr.arpa.", "20.0.10.10.in-addr.arpa."}, {"2001:db8::1", "8.b.d.0.1.0.0.2.ip6.arpa.", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."}, {"10.10.0.20", "adbraun.lol.", "10.10.0.20.adbraun.lol."}, // not special in forward zones } { if got, err := Owner(tc.in, tc.zone); err != nil || got != tc.want { t.Errorf("Owner(%q, %q) = %q, %v; want %q", tc.in, tc.zone, got, err, tc.want) } } } func TestReverseZones(t *testing.T) { cases := []struct { prefix string want string // zones joined by spaces, or "error" }{ {"10.0.10.0/24", "10.0.10.in-addr.arpa."}, {"10.0.10.100/32", "100.10.0.10.in-addr.arpa."}, {"10.0.8.0/22", "8.0.10.in-addr.arpa. 9.0.10.in-addr.arpa. 10.0.10.in-addr.arpa. 11.0.10.in-addr.arpa."}, {"10.0.9.0/22", "8.0.10.in-addr.arpa. 9.0.10.in-addr.arpa. 10.0.10.in-addr.arpa. 11.0.10.in-addr.arpa."}, {"172.16.0.0/12", "16.172.in-addr.arpa. 17.172.in-addr.arpa. 18.172.in-addr.arpa. 19.172.in-addr.arpa. 20.172.in-addr.arpa. 21.172.in-addr.arpa. 22.172.in-addr.arpa. 23.172.in-addr.arpa. 24.172.in-addr.arpa. 25.172.in-addr.arpa. 26.172.in-addr.arpa. 27.172.in-addr.arpa. 28.172.in-addr.arpa. 29.172.in-addr.arpa. 30.172.in-addr.arpa. 31.172.in-addr.arpa."}, {"10.0.0.0/17", "error"}, // 128 zones {"10.0.10.64/26", "error"}, {"10.0.0.0/7", "error"}, {"2001:db8::/47", "0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa. 1.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."}, {"2001:db8::1/128", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."}, } for _, c := range cases { zones, err := ReverseZones(netip.MustParsePrefix(c.prefix)) got := strings.Join(zones, " ") if err != nil { got = "error" } if got != c.want { t.Errorf("ReverseZones(%s) = %s (%v), want %s", c.prefix, got, err, c.want) } } if _, err := ReverseZone(netip.MustParsePrefix("10.0.8.0/22")); err == nil || !strings.Contains(err.Error(), "spans 4 reverse zones") { t.Errorf("ReverseZone /22: %v", err) } }