Reverse zone suggestions: yours and public, dismiss, per-view switch; child zones take their records
Suggestions split into networks that are yours (private IPv4 /24, ULA /48, an IPv6 /48 this server has an address in), which Create all creates, and public addresses (/32 or /128) to create only if they are yours or dismiss. A view can turn suggestions off, which also keeps it out of choosing a reverse zone's view (database version 13). A zone created inside another in the same view takes the parent's records under it; a delegation and its glue stay, a linked parent keeps them with a warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aa4b004a14
commit
b24c730b97
21 files changed
+543
-107
No files matched your search
@@ -1,6 +1,7 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -40,16 +41,16 @@ func TestReverseFillAndSuggest(t *testing.T) {
|
||||
recIn("internal", "*.dev", "A", "10.0.10.99"),
|
||||
recIn("internal", "lo", "A", "127.0.0.1"))
|
||||
|
||||
sugg := ReverseSuggestions(env.state(), env.e.Exposures(env.state()))
|
||||
sugg := ReverseSuggestions(env.state(), env.e.Exposures(env.state()), nil)
|
||||
var got []string
|
||||
for _, s := range sugg {
|
||||
got = append(got, s.View+" "+s.Prefix+" "+s.Zone)
|
||||
got = append(got, s.Group+" "+s.View+" "+s.Prefix+" "+s.Zone)
|
||||
}
|
||||
want(t, "suggestions", got,
|
||||
"internal 10.0.10.0/24 10.0.10.in-addr.arpa",
|
||||
"internal 192.0.2.10/32 10.2.0.192.in-addr.arpa",
|
||||
"internal 192.0.2.25/32 25.2.0.192.in-addr.arpa",
|
||||
"internal fdab:abab:abab::/48 b.a.b.a.b.a.b.a.b.a.d.f.ip6.arpa")
|
||||
"yours internal 10.0.10.0/24 10.0.10.in-addr.arpa",
|
||||
"yours internal fdab:abab:abab::/48 b.a.b.a.b.a.b.a.b.a.d.f.ip6.arpa",
|
||||
"public internal 192.0.2.10/32 10.2.0.192.in-addr.arpa",
|
||||
"public internal 192.0.2.25/32 25.2.0.192.in-addr.arpa")
|
||||
if sugg[0].Fill != 1 || sugg[0].Samples[0] != "10.0.10.20 files.example.com" {
|
||||
t.Errorf("10.0.10.0/24: %+v", sugg[0])
|
||||
}
|
||||
@@ -77,7 +78,7 @@ func TestReverseFillAndSuggest(t *testing.T) {
|
||||
}
|
||||
|
||||
got = got[:0]
|
||||
for _, s := range ReverseSuggestions(env.state(), env.e.Exposures(env.state())) {
|
||||
for _, s := range ReverseSuggestions(env.state(), env.e.Exposures(env.state()), nil) {
|
||||
got = append(got, s.Prefix)
|
||||
}
|
||||
want(t, "suggestions left", got, "192.0.2.10/32")
|
||||
@@ -136,3 +137,101 @@ func TestAutoPTR(t *testing.T) {
|
||||
env.submit(env.admin, recIn("internal", "quiet", "A", "10.0.10.50"))
|
||||
want(t, "off", env.ptrs("internal", rz))
|
||||
}
|
||||
|
||||
func suggested(env *testEnv, local ...netip.Addr) []string {
|
||||
var out []string
|
||||
for _, s := range ReverseSuggestions(env.state(), env.e.Exposures(env.state()), local) {
|
||||
out = append(out, s.Group+" "+s.View+" "+s.Prefix)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Public IPv6: the /48 only when this server has an address in it, else
|
||||
// the address alone, as a public suggestion. Dismissed networks and views
|
||||
// with suggestions off get none.
|
||||
func TestReverseSuggestionGroups(t *testing.T) {
|
||||
env := linkedEnv(t)
|
||||
env.submit(env.admin,
|
||||
recIn("internal", "home", "AAAA", "2a02:8013:7f3d:10::5"),
|
||||
recIn("internal", "gh", "AAAA", "2606:50c0:8000::153"))
|
||||
want(t, "no local addresses", suggested(env),
|
||||
"public internal 192.0.2.10/32", "public internal 192.0.2.25/32",
|
||||
"public internal 2606:50c0:8000::153/128", "public internal 2a02:8013:7f3d:10::5/128")
|
||||
want(t, "this server is in 2a02:8013:7f3d::/48", suggested(env, netip.MustParseAddr("2a02:8013:7f3d:10:1266:6aff:fef9:f5b1")),
|
||||
"yours internal 2a02:8013:7f3d::/48",
|
||||
"public internal 192.0.2.10/32", "public internal 192.0.2.25/32", "public internal 2606:50c0:8000::153/128")
|
||||
|
||||
env.submit(env.admin, NewRequest(OpReverseDismiss, 0, DismissInput{View: "internal", Prefix: "2606:50c0:8000::153/128"}))
|
||||
if got := suggested(env); slices.Contains(got, "public internal 2606:50c0:8000::153/128") {
|
||||
t.Errorf("dismissed still suggested: %q", got)
|
||||
}
|
||||
if e := env.submitErr(env.admin, NewRequest(OpReverseUndismiss, 0, DismissInput{View: "internal", Prefix: "192.0.2.10/32"})); e.Kind != KindConflict {
|
||||
t.Errorf("undismiss of one not dismissed: %v", e)
|
||||
}
|
||||
env.submit(env.admin, NewRequest(OpReverseUndismiss, 0, DismissInput{View: "internal", Prefix: "2606:50c0:8000::153/128"}))
|
||||
if got := suggested(env); !slices.Contains(got, "public internal 2606:50c0:8000::153/128") {
|
||||
t.Errorf("undismissed not suggested: %q", got)
|
||||
}
|
||||
|
||||
// A second private view using the same address (a VPS's view): with
|
||||
// its suggestions off it gets none, and doesn't count when choosing
|
||||
// the view of a new reverse zone.
|
||||
env.submit(env.admin, NewRequest(OpViewCreate, 0, ViewInput{Name: model.Ptr("vps"), MatchClients: &[]string{"198.51.100.7"}}))
|
||||
env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("example.com"), View: model.Ptr("vps"), LinkWith: "internal"}))
|
||||
if got := suggested(env); !slices.Contains(got, "public vps 192.0.2.25/32") {
|
||||
t.Errorf("vps view: %q", got)
|
||||
}
|
||||
if e := env.submitErr(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("192.0.2.25/32")})); !strings.Contains(e.Message, "views internal and vps") {
|
||||
t.Errorf("two views: %v", e)
|
||||
}
|
||||
vps := env.state().ViewByName("vps")
|
||||
env.submit(env.admin, NewRequest(OpViewUpdate, vps.ID, ViewInput{ReverseSuggestions: model.Ptr(false)}))
|
||||
if got := suggested(env); slices.ContainsFunc(got, func(s string) bool { return strings.Contains(s, " vps ") }) {
|
||||
t.Errorf("vps view with suggestions off: %q", got)
|
||||
}
|
||||
env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("192.0.2.25/32"), Fill: true}))
|
||||
want(t, "went to internal", env.ptrs("internal", "25.2.0.192.in-addr.arpa."), "25.2.0.192.in-addr.arpa. mail.example.com.")
|
||||
}
|
||||
|
||||
// A zone created inside another takes the parent's records under it.
|
||||
func TestChildZoneTakesRecords(t *testing.T) {
|
||||
env := linkedEnv(t)
|
||||
env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("10.0.0.0/8"), View: model.Ptr("internal")}))
|
||||
ptr := func(name, data string) Request {
|
||||
return NewRequest(OpRecordCreate, 0, RecordInput{Zone: "10.in-addr.arpa", View: "internal", Name: &name, Type: model.Ptr("PTR"), Data: &data})
|
||||
}
|
||||
env.submit(env.admin, ptr("20.10.0.10.in-addr.arpa.", "printer.example.com."), ptr("5.20.0.10.in-addr.arpa.", "other.example.com."))
|
||||
env.submit(env.admin, recIn("internal", "nas", "A", "10.0.10.30"))
|
||||
res := env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("10.0.10.0/24"), View: model.Ptr("internal"), Fill: true}))
|
||||
want(t, "/24", env.ptrs("internal", "10.0.10.in-addr.arpa."),
|
||||
"20.10.0.10.in-addr.arpa. printer.example.com.", "30.10.0.10.in-addr.arpa. nas.example.com.")
|
||||
want(t, "/8 keeps the rest", env.ptrs("internal", "10.in-addr.arpa."), "5.20.0.10.in-addr.arpa. other.example.com.")
|
||||
for _, p := range res.Problems {
|
||||
if p.Code == "shadowed_by_zone" {
|
||||
t.Errorf("still hidden records: %v", p)
|
||||
}
|
||||
}
|
||||
|
||||
// A forward child: its delegation and glue stay in the parent.
|
||||
env.submit(env.admin,
|
||||
recIn("internal", "lab", "NS", "ns.lab.example.com."),
|
||||
recIn("internal", "ns.lab", "A", "10.0.30.1"),
|
||||
recIn("internal", "www.lab", "A", "10.0.30.2"))
|
||||
env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("lab.example.com"), View: model.Ptr("internal")}))
|
||||
st := env.state()
|
||||
lab := st.FindZone("lab.example.com.", st.ViewByName("internal").ID)
|
||||
var labRecs []string
|
||||
for _, r := range st.ZoneRecords(lab.ID) {
|
||||
if r.Type != "NS" {
|
||||
labRecs = append(labRecs, r.Name+" "+r.Type)
|
||||
}
|
||||
}
|
||||
want(t, "lab", labRecs, "www.lab.example.com. A")
|
||||
v := env.view("internal") // without NS records
|
||||
if !slices.Contains(v, "ns.lab A 10.0.30.1 L") || slices.ContainsFunc(v, func(s string) bool { return strings.HasPrefix(s, "www.lab ") }) {
|
||||
t.Errorf("parent after: %q", v)
|
||||
}
|
||||
if env.recordID("internal", "lab", "NS") == 0 {
|
||||
t.Error("the delegation left the parent")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user