package netxlite // // Bogon // // This file helps us to decide if an IPAddr is a bogon as well as // a way to create a resolver that fails when resolving bogons. // import ( "context" "net" "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/runtimex" ) // MaybeWrapWithBogonResolver wraps the given resolver with a BogonResolver // iff the provided boolean flag is true. Otherwise, this factory just returns // the provided resolver to the caller without any wrapping. // // The returned resolver returns a wrapped ErrDNSBogon if there's a bogon error. // // BUG: This resolver currently only implements LookupHost. All the other // lookup methods will always return ErrNoDNSTransport. func MaybeWrapWithBogonResolver(enabled bool, reso model.Resolver) model.Resolver { if enabled { reso = &bogonResolver{Resolver: reso} } return reso } // bogonResolver is a bogon aware resolver. When a bogon is encountered in // a reply, this resolver will return ErrDNSBogon. type bogonResolver struct { Resolver model.Resolver } var _ model.Resolver = &bogonResolver{} // LookupHost implements Resolver.LookupHost func (r *bogonResolver) LookupHost(ctx context.Context, hostname string) ([]string, error) { addrs, err := r.Resolver.LookupHost(ctx, hostname) if err != nil { return nil, err // not our responsibility to wrap this error } for _, addr := range addrs { if IsBogon(addr) { // wrap ErrDNSBogon as documented return nil, NewErrWrapper(ClassifyResolverError, ResolveOperation, ErrDNSBogon) } } return addrs, nil } // LookupHTTPS implements Resolver.LookupHTTPS func (r *bogonResolver) LookupHTTPS(ctx context.Context, hostname string) (*model.HTTPSSvc, error) { // TODO(bassosimone): decide whether we want to implement this method or not return nil, ErrNoDNSTransport } // LookupNS implements Resolver.LookupNS func (r *bogonResolver) LookupNS(ctx context.Context, hostname string) ([]*net.NS, error) { // TODO(bassosimone): decide whether we want to implement this method or not return nil, ErrNoDNSTransport } // Network implements Resolver.Network func (r *bogonResolver) Network() string { return r.Resolver.Network() } // Address implements Resolver.Address func (r *bogonResolver) Address() string { return r.Resolver.Address() } // CloseIdleConnections implements Resolver.CloseIdleConnections func (r *bogonResolver) CloseIdleConnections() { r.Resolver.CloseIdleConnections() } // IsBogon returns whether an IP address is bogon. Passing to this // function a non-IP address causes it to return true. func IsBogon(address string) bool { ip := net.ParseIP(address) return ip == nil || isBogon(address, ip) } // IsLoopback returns whether an IP address is loopback. Passing to this // function a non-IP address causes it to return true. func IsLoopback(address string) bool { ip := net.ParseIP(address) return ip == nil || ip.IsLoopback() } var ( bogons4 []*net.IPNet bogons6 []*net.IPNet ) func expandBogons(cidrs []string) (out []*net.IPNet) { for _, cidr := range cidrs { _, block, err := net.ParseCIDR(cidr) runtimex.PanicOnError(err, "net.ParseCIDR failed") out = append(out, block) } return } func init() { bogons4 = append(bogons4, expandBogons([]string{ // // List extracted from https://ipinfo.io/bogon // "0.0.0.0/8", // "This" network "10.0.0.0/8", // Private-use networks "100.64.0.0/10", // Carrier-grade NAT "127.0.0.0/8", // Loopback "127.0.53.53/32", // Name collision occurrence "169.254.0.0/16", // Link local "172.16.0.0/12", // Private-use networks "192.0.0.0/24", // IETF protocol assignments "192.0.2.0/24", // TEST-NET-1 "192.168.0.0/16", // Private-use networks "198.18.0.0/15", // Network interconnect device benchmark testing "198.51.100.0/24", // TEST-NET-2 "203.0.113.0/24", // TEST-NET-3 "224.0.0.0/4", // Multicast "240.0.0.0/4", // Reserved for future use "255.255.255.255/32", // Limited broadcast })...) bogons6 = append(bogons6, expandBogons([]string{ // // List extracted from https://ipinfo.io/bogon // "::/128", // Node-scope unicast unspecified address "::1/128", // Node-scope unicast loopback address "::ffff:0:0/96", // IPv4-mapped addresses "::/96", // IPv4-compatible addresses "100::/64", // Remotely triggered black hole addresses "2001:10::/28", // Overlay routable cryptographic hash identifiers (ORCHID) "2001:db8::/32", // Documentation prefix "fc00::/7", // Unique local addresses (ULA) "fe80::/10", // Link-local unicast "fec0::/10", // Site-local unicast (deprecated) "ff00::/8", // Multicast (Note: ff0e:/16 is global scope and may appear on the global internet.) "2002::/24", // 6to4 bogon (0.0.0.0/8) "2002:a00::/24", // 6to4 bogon (10.0.0.0/8) "2002:7f00::/24", // 6to4 bogon (127.0.0.0/8) "2002:a9fe::/32", // 6to4 bogon (169.254.0.0/16) "2002:ac10::/28", // 6to4 bogon (172.16.0.0/12) "2002:c000::/40", // 6to4 bogon (192.0.0.0/24) "2002:c000:200::/40", // 6to4 bogon (192.0.2.0/24) "2002:c0a8::/32", // 6to4 bogon (192.168.0.0/16) "2002:c612::/31", // 6to4 bogon (198.18.0.0/15) "2002:c633:6400::/40", // 6to4 bogon (198.51.100.0/24) "2002:cb00:7100::/40", // 6to4 bogon (203.0.113.0/24) "2002:e000::/20", // 6to4 bogon (224.0.0.0/4) "2002:f000::/20", // 6to4 bogon (240.0.0.0/4) "2002:ffff:ffff::/48", // 6to4 bogon (255.255.255.255/32) "2001::/40", // Teredo bogon (0.0.0.0/8) "2001:0:a00::/40", // Teredo bogon (10.0.0.0/8) "2001:0:7f00::/40", // Teredo bogon (127.0.0.0/8) "2001:0:a9fe::/48", // Teredo bogon (169.254.0.0/16) "2001:0:ac10::/44", // Teredo bogon (172.16.0.0/12) "2001:0:c000::/56", // Teredo bogon (192.0.0.0/24) "2001:0:c000:200::/56", // Teredo bogon (192.0.2.0/24) "2001:0:c0a8::/48", // Teredo bogon (192.168.0.0/16) "2001:0:c612::/47", // Teredo bogon (198.18.0.0/15) "2001:0:c633:6400::/56", // Teredo bogon (198.51.100.0/24) "2001:0:cb00:7100::/56", // Teredo bogon (203.0.113.0/24) "2001:0:e000::/36", // Teredo bogon (224.0.0.0/4) "2001:0:f000::/36", // Teredo bogon (240.0.0.0/4) "2001:0:ffff:ffff::/64", // Teredo bogon (255.255.255.255/32) })...) } // isBogon implements IsBogon func isBogon(address string, ip net.IP) bool { // TODO(bassosimone): the following check is probably redundant given that these // three checks are already included into the list of bogons. if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() { return true } var bogons []*net.IPNet if isIPv6(address) { bogons = bogons6 } else { bogons = bogons4 } for _, block := range bogons { if block.Contains(ip) { return true } } return false }