Files
netbird/proxy/internal/roundtrip/dialguard.go
T
Brad Ison f400f4bee8 [proxy] Optionally refuse private addresses on direct-upstream dials (#7913)
Direct-upstream targets are dialled on the proxy host's network stack,
outside the embedded client's LAN blocking. A proxy that serves
untrusted accounts lets them reach the host's loopback, its LAN or
cluster, and the cloud metadata service through such a target.

NB_PROXY_DIRECT_UPSTREAM_BLOCK_PRIVATE adds a dialer control that
refuses addresses that are not globally reachable. It checks each
socket's resolved address just before connect, so hostnames and DNS
rebinding are covered, and IPv4 embedded in IPv6 addresses is checked
as IPv4. Refused dials are served as a 502. The setting defaults to
off for private and self-hosted proxies; an unparsable value turns it
on.
2026-10-02 12:02:06 +02:00

91 lines
3.4 KiB
Go

package roundtrip
import (
"context"
"errors"
"net/netip"
"syscall"
)
// ErrDirectUpstreamBlocked is returned when a direct-upstream dial targets
// an address that is not globally reachable while
// NB_PROXY_DIRECT_UPSTREAM_BLOCK_PRIVATE is set.
var ErrDirectUpstreamBlocked = errors.New("direct upstream address is not allowed")
// blockedUpstreamPrefixes are the ranges that reach the proxy host, its
// cluster or its cloud provider rather than the public internet. NAT64
// and 6to4 addresses are matched by the IPv4 address they embed.
var blockedUpstreamPrefixes = []netip.Prefix{
// IPv4
netip.MustParsePrefix("0.0.0.0/8"), // "this network", including 0.0.0.0
netip.MustParsePrefix("10.0.0.0/8"), // RFC1918
netip.MustParsePrefix("100.64.0.0/10"), // CGNAT
netip.MustParsePrefix("127.0.0.0/8"), // loopback
netip.MustParsePrefix("169.254.0.0/16"), // link-local, cloud metadata services
netip.MustParsePrefix("172.16.0.0/12"), // RFC1918
netip.MustParsePrefix("192.0.0.0/24"), // IETF protocol assignments
netip.MustParsePrefix("192.0.2.0/24"), // documentation
netip.MustParsePrefix("192.88.99.0/24"), // 6to4 relay anycast (deprecated)
netip.MustParsePrefix("192.168.0.0/16"), // RFC1918
netip.MustParsePrefix("198.18.0.0/15"), // benchmarking
netip.MustParsePrefix("198.51.100.0/24"), // documentation
netip.MustParsePrefix("203.0.113.0/24"), // documentation
netip.MustParsePrefix("224.0.0.0/4"), // multicast
netip.MustParsePrefix("240.0.0.0/4"), // reserved, including broadcast
// IPv6
netip.MustParsePrefix("::/96"), // unspecified, loopback, IPv4-compatible
netip.MustParsePrefix("64:ff9b:1::/48"), // local-use NAT64
netip.MustParsePrefix("100::/64"), // discard-only
netip.MustParsePrefix("2001::/32"), // Teredo
netip.MustParsePrefix("2001:2::/48"), // benchmarking
netip.MustParsePrefix("2001:db8::/32"), // documentation
netip.MustParsePrefix("3fff::/20"), // documentation
netip.MustParsePrefix("5f00::/16"), // SRv6 SIDs
netip.MustParsePrefix("fc00::/7"), // unique local, including AWS IMDS fd00:ec2::254
netip.MustParsePrefix("fe80::/10"), // link-local
netip.MustParsePrefix("fec0::/10"), // site-local (deprecated)
netip.MustParsePrefix("ff00::/8"), // multicast
}
var (
nat64Prefix = netip.MustParsePrefix("64:ff9b::/96")
sixToFour = netip.MustParsePrefix("2002::/16")
)
// isBlockedUpstreamAddr reports whether a guarded direct-upstream dial
// must refuse addr.
func isBlockedUpstreamAddr(addr netip.Addr) bool {
addr = addr.Unmap().WithZone("")
if !addr.IsValid() {
return true
}
if nat64Prefix.Contains(addr) {
b := addr.As16()
return isBlockedUpstreamAddr(netip.AddrFrom4([4]byte(b[12:16])))
}
if sixToFour.Contains(addr) {
b := addr.As16()
return isBlockedUpstreamAddr(netip.AddrFrom4([4]byte(b[2:6])))
}
for _, p := range blockedUpstreamPrefixes {
if p.Contains(addr) {
return true
}
}
return false
}
// guardUpstreamDial is a net.Dialer ControlContext that refuses blocked
// addresses. It sees the resolved address of each socket just before
// connect, so DNS rebinding cannot swap the target after the check.
func guardUpstreamDial(_ context.Context, _, address string, _ syscall.RawConn) error {
ap, err := netip.ParseAddrPort(address)
if err != nil || isBlockedUpstreamAddr(ap.Addr()) {
return ErrDirectUpstreamBlocked
}
return nil
}