fe80::/10 in the IPv6 antispoof list (or your prefixes) dropped the ISP's router adverts and DHCPv6 replies in the early chain, before the ICMPv6 and DHCPv6 client built-ins could accept them: the router lost its IPv6 default route. Validation now refuses it (antispoof_link_local), the settings page says so, and the importer leaves it out with a note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
123 lines
3.5 KiB
Go
123 lines
3.5 KiB
Go
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"net/netip"
|
|
"strings"
|
|
)
|
|
|
|
// LinkLocal6 is IPv6's link-local prefix.
|
|
var LinkLocal6 = netip.MustParsePrefix("fe80::/10")
|
|
|
|
// Address is a parsed address entry: one address, a prefix or a range.
|
|
type Address struct {
|
|
Prefix netip.Prefix // a single address is a /32 or /128
|
|
First, End netip.Addr // a range (First valid only for ranges)
|
|
}
|
|
|
|
// IsV6 reports whether the entry is IPv6.
|
|
func (a Address) IsV6() bool {
|
|
if a.First.IsValid() {
|
|
return a.First.Is6()
|
|
}
|
|
return a.Prefix.Addr().Is6()
|
|
}
|
|
|
|
// String writes the entry as nftables and Housecarl write it:
|
|
// "10.0.0.5", "10.0.0.0/24", "10.0.0.1-10.0.0.9".
|
|
func (a Address) String() string {
|
|
if a.First.IsValid() {
|
|
return a.First.String() + "-" + a.End.String()
|
|
}
|
|
if a.Prefix.IsSingleIP() {
|
|
return a.Prefix.Addr().String()
|
|
}
|
|
return a.Prefix.String()
|
|
}
|
|
|
|
// Contains reports whether the entry holds an address.
|
|
func (a Address) Contains(x netip.Addr) bool {
|
|
x = x.Unmap()
|
|
if a.First.IsValid() {
|
|
return x.Is4() == a.First.Is4() && !x.Less(a.First) && !a.End.Less(x)
|
|
}
|
|
return a.Prefix.Contains(x)
|
|
}
|
|
|
|
// Overlaps reports whether the entry and a prefix share an address.
|
|
func (a Address) Overlaps(p netip.Prefix) bool {
|
|
if !a.First.IsValid() {
|
|
return a.Prefix.Overlaps(p)
|
|
}
|
|
if a.First.Is4() != p.Addr().Is4() {
|
|
return false
|
|
}
|
|
// The prefix's first and last addresses against the range.
|
|
first := p.Masked().Addr()
|
|
last := first
|
|
b := last.AsSlice()
|
|
for i := p.Bits(); i < len(b)*8; i++ {
|
|
b[i/8] |= 0x80 >> (i % 8)
|
|
}
|
|
last, _ = netip.AddrFromSlice(b)
|
|
return !last.Less(a.First) && !a.End.Less(first)
|
|
}
|
|
|
|
// ParseAddress reads an address, a prefix (host bits must be zero) or a
|
|
// range, IPv4 or IPv6.
|
|
func ParseAddress(s string) (Address, error) {
|
|
s = strings.TrimSpace(s)
|
|
if a, b, ok := strings.Cut(s, "-"); ok {
|
|
first, err1 := netip.ParseAddr(strings.TrimSpace(a))
|
|
end, err2 := netip.ParseAddr(strings.TrimSpace(b))
|
|
if err1 != nil || err2 != nil || first.Is4() != end.Is4() {
|
|
return Address{}, fmt.Errorf("%q is not an address range (write it as 10.0.0.1-10.0.0.9)", s)
|
|
}
|
|
first, end = first.Unmap(), end.Unmap()
|
|
if end.Less(first) {
|
|
return Address{}, fmt.Errorf("the range %q ends before it starts", s)
|
|
}
|
|
if first == end {
|
|
return Address{Prefix: netip.PrefixFrom(first, first.BitLen())}, nil
|
|
}
|
|
return Address{First: first, End: end}, nil
|
|
}
|
|
if strings.Contains(s, "/") {
|
|
p, err := netip.ParsePrefix(s)
|
|
if err != nil {
|
|
return Address{}, fmt.Errorf("%q is not an address or prefix", s)
|
|
}
|
|
if p.Addr().Is4In6() {
|
|
// Unmapping would leave an IPv4 address with IPv6 bits
|
|
// (::ffff:0:0/96 as 0.0.0.0/96).
|
|
return Address{}, fmt.Errorf("%q is an IPv4-mapped prefix, which can't be used: write IPv4 prefixes as IPv4 (10.0.0.0/8)", s)
|
|
}
|
|
if p.Masked() != p {
|
|
return Address{}, fmt.Errorf("%q has host bits set; the prefix is %s", s, p.Masked())
|
|
}
|
|
return Address{Prefix: p}, nil
|
|
}
|
|
a, err := netip.ParseAddr(s)
|
|
if err != nil || a.Zone() != "" {
|
|
return Address{}, fmt.Errorf("%q is not an address, prefix, range or set name", s)
|
|
}
|
|
a = a.Unmap()
|
|
return Address{Prefix: netip.PrefixFrom(a, a.BitLen())}, nil
|
|
}
|
|
|
|
// IsLiteral reports whether a source/destination reference is an
|
|
// address, prefix or range rather than a set name.
|
|
func IsLiteral(ref string) bool {
|
|
_, err := ParseAddress(ref)
|
|
return err == nil
|
|
}
|
|
|
|
// NormalizeAddress rewrites an address entry in its canonical form.
|
|
func NormalizeAddress(s string) (string, error) {
|
|
a, err := ParseAddress(s)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return a.String(), nil
|
|
}
|