Address ranges in address match lists (IPv4 and IPv6)
ACLs, views' match lists, options with address match lists and blocking client groups take ranges such as 10.0.10.101-10.0.10.199 or 2001:db8::100-2001:db8::1ff (negated, or with spaces around the dash, too). Jarl stores the range as written and writes the fewest prefixes that cover exactly that range to named.conf, with the range as a comment. New package internal/iprange; namedconf gets an AMRange element (a range used to parse as an ACL name), ExpandRanges and statement comments. Mistakes are refused when entered, validation sees what ranges cover, and IPv6 ranges in the IPv4-mapped space get a warning. The web UI shows what a range becomes while typing (GET /catalog/ranges). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
49b1c872c9
commit
e2e5007f94
32 files changed
+905
-31
No files matched your search
@@ -4,6 +4,7 @@ import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"jarl/internal/iprange"
|
||||
"jarl/internal/model"
|
||||
"jarl/internal/namedconf"
|
||||
)
|
||||
@@ -162,6 +163,9 @@ func (c *checker) checkMember(g *model.ClientGroup, m string, seen map[string]bo
|
||||
if _, err := netip.ParsePrefix(m); err == nil {
|
||||
return
|
||||
}
|
||||
if _, ok, err := iprange.Parse(m); ok && err == nil {
|
||||
return
|
||||
}
|
||||
acl := c.s.ACLByName(m)
|
||||
if acl == nil {
|
||||
c.errf("client_group_member_invalid", "clientgroup", g.ID, "client group %s: %q is not an address, a prefix or an ACL", g.Name, m)
|
||||
|
||||
@@ -126,6 +126,7 @@ func (c *checker) checkACLs() {
|
||||
c.warnf("acl_empty", "acl", a.ID, "%s is empty and matches nothing", label)
|
||||
}
|
||||
c.checkRefs(elems, label, "acl", a.ID)
|
||||
c.checkRanges(elems, label, "acl", a.ID)
|
||||
for _, w := range namedconf.Shadowed(elems) {
|
||||
c.warnf("acl_shadowed", "acl", a.ID, "%s: %s", label, w)
|
||||
}
|
||||
@@ -179,6 +180,7 @@ func (c *checker) checkViews() {
|
||||
c.errf("view_acl_invalid", "view", v.ID, "%s: %s element %q: %v", label, field, s, err)
|
||||
})
|
||||
c.checkRefs(elems, label+" "+field, "view", v.ID)
|
||||
c.checkRanges(elems, label+" "+field, "view", v.ID)
|
||||
for _, w := range namedconf.Shadowed(elems) {
|
||||
c.warnf("acl_shadowed", "view", v.ID, "%s %s: %s", label, field, w)
|
||||
}
|
||||
@@ -237,11 +239,24 @@ func (c *checker) flatten(list []string, depth int) []namedconf.AMElement {
|
||||
continue
|
||||
}
|
||||
}
|
||||
out = append(out, e)
|
||||
out = append(out, e.Expand()...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// checkRanges warns about IPv6 ranges in the IPv4-mapped space, which may
|
||||
// match IPv4 clients too.
|
||||
func (c *checker) checkRanges(elems []namedconf.AMElement, where, objType string, id int64) {
|
||||
for _, e := range elems {
|
||||
if e.Kind == namedconf.AMRange && e.Range.TouchesMapped4() {
|
||||
c.warnf("range_mapped4", objType, id, "%s: range %s reaches into ::ffff:0:0/96, where named can see IPv4 clients as IPv6 addresses, so it may match IPv4 clients too; for IPv4 clients, write an IPv4 range", where, e.Range)
|
||||
}
|
||||
if e.Kind == namedconf.AMNested {
|
||||
c.checkRanges(e.Nested, where, objType, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// optionScope maps an option to its catalog scope.
|
||||
func (c *checker) optionScope(o *model.Option) (string, string, bool) {
|
||||
switch o.Scope {
|
||||
|
||||
Reference in new issue
Block a user