Files
vibedns/internal/netutil/prefix.go
T
2026-08-16 21:18:45 -05:00

121 lines
2.9 KiB
Go

// Package netutil holds small address helpers shared by the DNS server, the
// rate limiter and the HTTP layer.
package netutil
import (
"net"
"net/netip"
"strings"
)
// PrefixSet answers "is this address in one of these networks?".
//
// It backs rate-limit exemptions, query-log ignore lists and trusted-proxy
// configuration. Invalid entries are skipped rather than rejected: these lists
// are conveniences, and the security-critical recursion ACL uses a stricter
// constructor that reports errors.
type PrefixSet struct {
prefixes []netip.Prefix
}
// NewPrefixSet compiles a list of CIDR blocks or bare addresses.
func NewPrefixSet(in []string) *PrefixSet {
s := &PrefixSet{}
for _, v := range in {
if p, ok := ParsePrefix(v); ok {
s.prefixes = append(s.prefixes, p)
}
}
return s
}
// ParsePrefix accepts a CIDR block or a bare address, returning a masked
// prefix. A bare address becomes a host route.
func ParsePrefix(v string) (netip.Prefix, bool) {
v = strings.TrimSpace(v)
if v == "" {
return netip.Prefix{}, false
}
if strings.Contains(v, "/") {
p, err := netip.ParsePrefix(v)
if err != nil {
return netip.Prefix{}, false
}
return p.Masked(), true
}
addr, err := netip.ParseAddr(v)
if err != nil {
return netip.Prefix{}, false
}
a := addr.Unmap()
return netip.PrefixFrom(a, a.BitLen()), true
}
// Contains reports whether addr falls inside the set.
func (s *PrefixSet) Contains(addr netip.Addr) bool {
if s == nil || len(s.prefixes) == 0 {
return false
}
ip := addr.Unmap()
for _, p := range s.prefixes {
if p.Addr().Is4() == ip.Is4() && p.Contains(ip) {
return true
}
}
return false
}
// Empty reports whether the set has no usable entries.
func (s *PrefixSet) Empty() bool { return s == nil || len(s.prefixes) == 0 }
// Len reports how many prefixes the set holds.
func (s *PrefixSet) Len() int {
if s == nil {
return 0
}
return len(s.prefixes)
}
// AddrFromNetAddr extracts the IP from a net.Addr, which is how both the DNS
// listeners and net/http hand us the client address.
func AddrFromNetAddr(a net.Addr) netip.Addr {
switch v := a.(type) {
case *net.UDPAddr:
if addr, ok := netip.AddrFromSlice(v.IP); ok {
return addr.Unmap()
}
case *net.TCPAddr:
if addr, ok := netip.AddrFromSlice(v.IP); ok {
return addr.Unmap()
}
}
if a == nil {
return netip.Addr{}
}
host, _, err := net.SplitHostPort(a.String())
if err != nil {
host = a.String()
}
addr, err := netip.ParseAddr(host)
if err != nil {
return netip.Addr{}
}
return addr.Unmap()
}
// AddrFromHostPort parses a "host:port" or bare-host string into an address.
func AddrFromHostPort(s string) (netip.Addr, bool) {
s = strings.TrimSpace(s)
if s == "" {
return netip.Addr{}, false
}
if host, _, err := net.SplitHostPort(s); err == nil {
s = host
}
addr, err := netip.ParseAddr(strings.Trim(s, "[]"))
if err != nil {
return netip.Addr{}, false
}
return addr.Unmap(), true
}