Reverse zones: other views' addresses aren't missing PTRs
A reverse zone's missing PTR count covers only its own view's A and AAAA records. Addresses only other views' records use are counted apart (ptr_other_views, 'Other views' in zone show, a note on the zone page) and filled only when asked: fill-ptr --other-views, zone create --fill --other-views, or the New reverse zone dialog's checkbox. This keeps one view's host names out of another view's reverse zones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d18fe18ccd
commit
6cd3f6ef4e
16 files changed
+216
-48
No files matched your search
@@ -471,8 +471,8 @@ func (x *expander) zoneCreate(req Request) ([]model.Op, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
fill := in.Fill
|
||||
in.Fill = false
|
||||
fill, fillOther := in.Fill, in.FillOtherViews
|
||||
in.Fill, in.FillOtherViews = false, false
|
||||
if fill {
|
||||
if zt != model.ZonePrimary {
|
||||
return nil, errf(KindBadRequest, "fill only applies to primary zones")
|
||||
@@ -484,10 +484,15 @@ func (x *expander) zoneCreate(req Request) ([]model.Op, error) {
|
||||
if in.ViewID != nil {
|
||||
vid = *in.ViewID
|
||||
}
|
||||
fills := PTRFills(x.st, vid, name, moved)
|
||||
fills := PTRFills(x.st, vid, name, moved, fillOther)
|
||||
if len(fills) == 0 {
|
||||
x.warnings = append(x.warnings, fmt.Sprintf("no A or AAAA record of %s has an address in %s, so there was nothing to fill it with", viewOrServer(x.st, vid), dnsname.Display(name)))
|
||||
}
|
||||
if !fillOther {
|
||||
if h := otherViewsHint(x.st, vid, name, moved, len(fills), "to name them from there, fill it with other views (jarl zone fill-ptr ZONE --other-views)"); h != "" {
|
||||
x.warnings = append(x.warnings, h)
|
||||
}
|
||||
}
|
||||
x.warnings = append(x.warnings, fillWarnings(fills)...)
|
||||
for _, f := range fills {
|
||||
in.Records = append(in.Records, RecordInput{Name: model.Ptr(f.Name), Type: model.Ptr("PTR"), Data: model.Ptr(f.Target)})
|
||||
|
||||
@@ -163,8 +163,11 @@ type ZoneInput struct {
|
||||
// step (docs/reverse.md).
|
||||
AutoPTR *bool `json:"auto_ptr,omitempty"`
|
||||
// Fill (create only, reverse zones) adds a PTR record for every
|
||||
// address of the view's A and AAAA records in the zone's range.
|
||||
Fill bool `json:"fill,omitempty"`
|
||||
// address of the view's A and AAAA records in the zone's range;
|
||||
// FillOtherViews also for the addresses only other views' records
|
||||
// use, named from there.
|
||||
Fill bool `json:"fill,omitempty"`
|
||||
FillOtherViews bool `json:"fill_other_views,omitempty"`
|
||||
|
||||
// Create only: name servers for the apex NS records (default: the
|
||||
// configured default name servers), additional records, and a zone to
|
||||
@@ -224,6 +227,13 @@ type LinkInput struct {
|
||||
With []string `json:"with,omitempty"`
|
||||
}
|
||||
|
||||
// FillPTRInput is zone.fill_ptr: OtherViews also fills the addresses only
|
||||
// other views' records use, naming them from there.
|
||||
type FillPTRInput struct {
|
||||
ZoneID int64 `json:"zone_id,omitempty"`
|
||||
OtherViews bool `json:"other_views,omitempty"`
|
||||
}
|
||||
|
||||
// RRSetInput replaces all records of one name and type.
|
||||
type RRSetInput struct {
|
||||
ZoneID int64 `json:"zone_id,omitempty"`
|
||||
|
||||
+35
-10
@@ -87,11 +87,12 @@ type PTRFill struct {
|
||||
// PTRFills returns the PTR records a reverse zone named zoneName in a view
|
||||
// lacks for the addresses of the view's A and AAAA records: one per
|
||||
// address, naming its mail or name server host when it is one, otherwise
|
||||
// a host rather than the domain itself, then the shortest name. An
|
||||
// address no record of the view uses is named from another view's
|
||||
// records (fillAddresses), marked From. existing are the zone's records
|
||||
// (none for a new zone).
|
||||
func PTRFills(st *model.State, viewID int64, zoneName string, existing []*model.Record) []PTRFill {
|
||||
// a host rather than the domain itself, then the shortest name. With
|
||||
// otherViews, an address no record of the view uses is named from another
|
||||
// view's records (fillAddresses), marked From; that is only on request,
|
||||
// since it publishes one view's host names in another. existing are the
|
||||
// zone's records (none for a new zone).
|
||||
func PTRFills(st *model.State, viewID int64, zoneName string, existing []*model.Record, otherViews bool) []PTRFill {
|
||||
prefix, ok := dnsname.PrefixFromReverse(zoneName)
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -105,7 +106,11 @@ func PTRFills(st *model.State, viewID int64, zoneName string, existing []*model.
|
||||
servers := serverNames(st, viewID)
|
||||
byAddr := map[netip.Addr][]addrUse{}
|
||||
var order []netip.Addr
|
||||
for _, u := range fillAddresses(st, viewID) {
|
||||
addrs := forwardAddresses(st, viewID)
|
||||
if otherViews {
|
||||
addrs = fillAddresses(st, viewID)
|
||||
}
|
||||
for _, u := range addrs {
|
||||
if !prefix.Contains(u.addr) {
|
||||
continue
|
||||
}
|
||||
@@ -195,9 +200,23 @@ func displayAll(names []string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// otherViewsHint says how many addresses only other views' records use in
|
||||
// a reverse zone, and how to add them; "" when there are none.
|
||||
func otherViewsHint(st *model.State, viewID int64, zoneName string, existing []*model.Record, own int, how string) string {
|
||||
n := len(PTRFills(st, viewID, zoneName, existing, true)) - own
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
what := "addresses"
|
||||
if n == 1 {
|
||||
what = "address"
|
||||
}
|
||||
return fmt.Sprintf("%d %s in %s only other views' records use; %s", n, what, dnsname.Display(zoneName), how)
|
||||
}
|
||||
|
||||
// zoneFillPTR expands zone.fill_ptr: the PTR records a reverse zone lacks.
|
||||
func (x *expander) zoneFillPTR(req Request) ([]model.Op, error) {
|
||||
var in LinkInput
|
||||
var in FillPTRInput
|
||||
if err := decode(req.Data, &in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -215,9 +234,15 @@ func (x *expander) zoneFillPTR(req Request) ([]model.Op, error) {
|
||||
if _, ok := dnsname.PrefixFromReverse(z.Name); !ok {
|
||||
return nil, errf(KindBadRequest, "zone %s isn't a reverse zone of whole octets or nibbles (like 10.0.10.in-addr.arpa)", zoneDesc(x.st, z))
|
||||
}
|
||||
fills := PTRFills(x.st, z.ViewID, z.Name, x.st.ZoneRecords(z.ID))
|
||||
fills := PTRFills(x.st, z.ViewID, z.Name, x.st.ZoneRecords(z.ID), in.OtherViews)
|
||||
if len(fills) == 0 {
|
||||
return nil, errf(KindBadRequest, "nothing to add: every address of %s's A and AAAA records in %s has a PTR record", viewOrServer(x.st, z.ViewID), zoneDesc(x.st, z))
|
||||
msg := fmt.Sprintf("nothing to add: every address of %s's A and AAAA records in %s has a PTR record", viewOrServer(x.st, z.ViewID), zoneDesc(x.st, z))
|
||||
if !in.OtherViews {
|
||||
if h := otherViewsHint(x.st, z.ViewID, z.Name, x.st.ZoneRecords(z.ID), 0, "to name them from there, fill with other views (--other-views)"); h != "" {
|
||||
msg += "; " + h
|
||||
}
|
||||
}
|
||||
return nil, errf(KindBadRequest, "%s", msg)
|
||||
}
|
||||
x.warnings = append(x.warnings, fillWarnings(fills)...)
|
||||
ids, err := x.ids(len(fills))
|
||||
@@ -415,7 +440,7 @@ func ReverseCandidates(st *model.State, exposures []validate.Exposure, local []n
|
||||
continue
|
||||
}
|
||||
s := ReverseSuggestion{ViewID: sc.id, View: viewName(st, sc.id), Prefix: p.String(), Zone: strings.TrimSuffix(name, "."), Group: group, Hidden: hidden, Why: why, Samples: []string{}}
|
||||
fills := PTRFills(st, sc.id, name, nil)
|
||||
fills := PTRFills(st, sc.id, name, nil, opt.All && opt.View != "")
|
||||
s.Fill = len(fills)
|
||||
for i, f := range fills {
|
||||
if i == 3 {
|
||||
|
||||
@@ -69,11 +69,11 @@ func TestReverseFillAndSuggest(t *testing.T) {
|
||||
env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("fdab:abab:abab::/48")}))
|
||||
st := env.state()
|
||||
z6 := st.FindZone("b.a.b.a.b.a.b.a.b.a.d.f.ip6.arpa.", st.ViewByName("internal").ID)
|
||||
env.submit(env.admin, NewRequest(OpZoneFillPTR, z6.ID, LinkInput{}))
|
||||
env.submit(env.admin, NewRequest(OpZoneFillPTR, z6.ID, FillPTRInput{}))
|
||||
if p := env.ptrs("internal", z6.Name); len(p) != 1 || !strings.HasSuffix(p[0], " nas.example.com.") {
|
||||
t.Errorf("ip6 fill: %q", p)
|
||||
}
|
||||
if e := env.submitErr(env.admin, NewRequest(OpZoneFillPTR, z6.ID, LinkInput{})); !strings.Contains(e.Message, "nothing to add") {
|
||||
if e := env.submitErr(env.admin, NewRequest(OpZoneFillPTR, z6.ID, FillPTRInput{})); !strings.Contains(e.Message, "nothing to add") {
|
||||
t.Errorf("second fill: %v", e)
|
||||
}
|
||||
|
||||
@@ -288,15 +288,38 @@ func TestReverseFromOtherView(t *testing.T) {
|
||||
t.Errorf("suggested unasked: %+v", s)
|
||||
}
|
||||
}
|
||||
res := env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("2600:3c01::f03c:93ff:fe2b:1/128"), View: model.Ptr("internal"), Fill: true}))
|
||||
st := env.state()
|
||||
var zone string
|
||||
for _, z := range st.ZonesInView(st.ViewByName("internal").ID) {
|
||||
if strings.HasSuffix(z.Name, ".ip6.arpa.") {
|
||||
zone = z.Name
|
||||
for _, s := range ReverseCandidates(env.state(), env.e.Exposures(env.state()), nil, ReverseOptions{All: true, View: "internal"}) {
|
||||
if strings.HasPrefix(s.Prefix, "2600:") && (s.Fill != 1 || len(s.Samples) != 1) {
|
||||
t.Errorf("candidate counts the other view's address: %+v", s)
|
||||
}
|
||||
}
|
||||
if p := env.ptrs("internal", zone); len(p) != 1 || !strings.HasSuffix(p[0], " vps.example.com.") {
|
||||
// Filling doesn't take names from other views unasked: it would
|
||||
// publish one view's host names in another. It says how to.
|
||||
res := env.submit(env.admin, NewRequest(OpZoneCreate, 0, ZoneInput{Name: model.Ptr("2600:3c01::f03c:93ff:fe2b:1/128"), View: model.Ptr("internal"), Fill: true}))
|
||||
st := env.state()
|
||||
var z *model.Zone
|
||||
for _, o := range st.ZonesInView(st.ViewByName("internal").ID) {
|
||||
if strings.HasSuffix(o.Name, ".ip6.arpa.") {
|
||||
z = o
|
||||
}
|
||||
}
|
||||
if p := env.ptrs("internal", z.Name); len(p) != 0 {
|
||||
t.Errorf("filled from another view unasked: %q", p)
|
||||
}
|
||||
if !slices.ContainsFunc(res.Warnings, func(w string) bool {
|
||||
return strings.Contains(w, "1 address in") && strings.Contains(w, "--other-views")
|
||||
}) {
|
||||
t.Errorf("warnings %q", res.Warnings)
|
||||
}
|
||||
// Not missing: no record of the zone's own view uses the address.
|
||||
if f := PTRFills(st, z.ViewID, z.Name, st.ZoneRecords(z.ID), false); len(f) != 0 {
|
||||
t.Errorf("missing %+v", f)
|
||||
}
|
||||
if e := env.submitErr(env.admin, NewRequest(OpZoneFillPTR, z.ID, FillPTRInput{})); !strings.Contains(e.Message, "nothing to add") || !strings.Contains(e.Message, "--other-views") {
|
||||
t.Errorf("fill-ptr: %v", e)
|
||||
}
|
||||
res = env.submit(env.admin, NewRequest(OpZoneFillPTR, z.ID, FillPTRInput{OtherViews: true}))
|
||||
if p := env.ptrs("internal", z.Name); len(p) != 1 || !strings.HasSuffix(p[0], " vps.example.com.") {
|
||||
t.Errorf("filled %q", p)
|
||||
}
|
||||
if !slices.ContainsFunc(res.Warnings, func(w string) bool { return strings.Contains(w, "only view external has a record for it") }) {
|
||||
|
||||
Reference in new issue
Block a user