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>
91 lines
2.9 KiB
Go
91 lines
2.9 KiB
Go
package namedconf
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRangeElements(t *testing.T) {
|
|
e, err := ParseAMElement("!10.0.10.150 - 10.0.10.160;")
|
|
if err != nil || e.Kind != AMRange || !e.Negated || e.String() != "!10.0.10.150-10.0.10.160" {
|
|
t.Fatalf("negated range = %+v, %v", e, err)
|
|
}
|
|
if e, err := ParseAMElement("2001:db8::100-2001:db8::1ff"); err != nil || e.Kind != AMRange {
|
|
t.Errorf("IPv6 range = %+v, %v", e, err)
|
|
}
|
|
// ACL names with dashes are still names.
|
|
if e, err := ParseAMElement("branch-office"); err != nil || e.Kind != AMName {
|
|
t.Errorf("name = %+v, %v", e, err)
|
|
}
|
|
for _, bad := range []string{"10.0.10.199-10.0.10.101", "10.0.10.101-199", "10.0.0.1-2001:db8::1"} {
|
|
if _, err := ParseAMElement(bad); err == nil {
|
|
t.Errorf("%s accepted", bad)
|
|
}
|
|
}
|
|
if err := CheckACLName("10.0.0.1-10.0.0.9"); err == nil {
|
|
t.Error("an ACL name that looks like a range was accepted")
|
|
}
|
|
var got []string
|
|
for _, x := range e.Expand() {
|
|
got = append(got, x.String())
|
|
}
|
|
if strings.Join(got, " ") != "!10.0.10.150/31 !10.0.10.152/29 !10.0.10.160" {
|
|
t.Errorf("expand = %v", got)
|
|
}
|
|
}
|
|
|
|
func TestExpandRanges(t *testing.T) {
|
|
st, err := ParseStatement(`acl "lan" { !10.0.10.150-10.0.10.160; 10.0.10.101-10.0.10.199; key xfer; branch-office; { 10.0.0.1-10.0.0.2; }; }`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ExpandRanges(st)
|
|
want := `acl "lan" {
|
|
// !10.0.10.150-10.0.10.160
|
|
!10.0.10.150/31;
|
|
!10.0.10.152/29;
|
|
!10.0.10.160;
|
|
// 10.0.10.101-10.0.10.199
|
|
10.0.10.101;
|
|
10.0.10.102/31;
|
|
10.0.10.104/29;
|
|
10.0.10.112/28;
|
|
10.0.10.128/26;
|
|
10.0.10.192/29;
|
|
key xfer;
|
|
branch-office;
|
|
{ /* 10.0.0.1-10.0.0.2 */ 10.0.0.1; 10.0.0.2; };
|
|
};
|
|
`
|
|
if got := FormatStmt(st, 0); got != want {
|
|
t.Errorf("got:\n%s\nwant:\n%s", got, want)
|
|
}
|
|
// Short lists stay on one line, with the range as a /* */ comment;
|
|
// both forms parse again.
|
|
st, _ = ParseOptionValue("allow-query", "{ 2001:db8::100-2001:db8::1ff; localhost; }")
|
|
ExpandRanges(st)
|
|
got := FormatStmt(st, 0)
|
|
if got != "allow-query { /* 2001:db8::100-2001:db8::1ff */ 2001:db8::100/120; localhost; };\n" {
|
|
t.Errorf("inline = %q", got)
|
|
}
|
|
if _, err := ParseStatement(strings.TrimSuffix(strings.TrimSpace(got), ";")); err != nil {
|
|
t.Errorf("output doesn't parse: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestShadowedRanges(t *testing.T) {
|
|
elems, _ := ParseAMListString("10.0.10.0/24; 10.0.10.101-10.0.10.199; 10.0.10.150-10.0.11.5; 10.0.11.1")
|
|
w := Shadowed(elems)
|
|
if len(w) != 2 || !strings.Contains(w[0], `"10.0.10.101-10.0.10.199" never matches`) || !strings.Contains(w[1], `"10.0.11.1" never matches: "10.0.10.150-10.0.11.5"`) {
|
|
t.Errorf("shadowed = %q", w)
|
|
}
|
|
elems, _ = ParseAMListString("10.0.10.101-10.0.10.199; 10.0.10.120")
|
|
if w := Shadowed(elems); len(w) != 1 || !strings.Contains(w[0], `"10.0.10.120" never matches`) {
|
|
t.Errorf("shadowed by a range = %q", w)
|
|
}
|
|
elems, _ = ParseAMListString("0.0.0.0-255.255.255.255")
|
|
if ContainsAny(elems) {
|
|
t.Error("ContainsAny should only see expanded elements") // callers expand first
|
|
}
|
|
}
|