package resolver import ( "fmt" "net/netip" "strings" ) // ACL decides which clients may use recursion. // // It is deliberately closed by default: an empty allow list denies everyone. // Denies are evaluated before allows, so a narrow exclusion can be carved out // of a broad allowance. type ACL struct { allow []netip.Prefix deny []netip.Prefix } // NewACL compiles allow and deny lists. Entries may be CIDR blocks or bare // addresses; an invalid entry is reported rather than silently dropped, // because a typo in an ACL is a security-relevant mistake. func NewACL(allow, deny []string) (*ACL, error) { a := &ACL{} var err error if a.allow, err = parsePrefixes(allow, "allowed"); err != nil { return nil, err } if a.deny, err = parsePrefixes(deny, "denied"); err != nil { return nil, err } return a, nil } func parsePrefixes(in []string, label string) ([]netip.Prefix, error) { var out []netip.Prefix for _, s := range in { s = strings.TrimSpace(s) if s == "" { continue } if strings.Contains(s, "/") { p, err := netip.ParsePrefix(s) if err != nil { return nil, fmt.Errorf("%s network %q is not a valid CIDR block", label, s) } out = append(out, p.Masked()) continue } addr, err := netip.ParseAddr(s) if err != nil { return nil, fmt.Errorf("%s network %q is not a valid IP address or CIDR block", label, s) } addr = addr.Unmap() out = append(out, netip.PrefixFrom(addr, addr.BitLen())) } return out, nil } // Allowed reports whether addr may use recursion. func (a *ACL) Allowed(addr netip.Addr) bool { if a == nil { return false } ip := addr.Unmap() for _, p := range a.deny { if p.Addr().Is4() == ip.Is4() && p.Contains(ip) { return false } } for _, p := range a.allow { if p.Addr().Is4() == ip.Is4() && p.Contains(ip) { return true } } return false } // Describe renders the ACL for the settings page. func (a *ACL) Describe() (allow, deny []string) { if a == nil { return nil, nil } for _, p := range a.allow { allow = append(allow, p.String()) } for _, p := range a.deny { deny = append(deny, p.String()) } return allow, deny } // Prefix sets for non-security lists (rate-limit exemptions, query-log ignore // lists) live in package netutil. This package keeps only the recursion ACL, // whose constructor deliberately reports errors instead of skipping entries.