package netguard import ( "context" "fmt" "net" "testing" "time" ) func TestIsPublic(t *testing.T) { public := []string{ "8.8.8.8", "1.1.1.1", "2001:4860:4860::8888", // NAT64 and 6to4 wrapping a PUBLIC embedded IPv4 address a real // public destination on IPv6-only networks — allowed by policy. "64:ff9b::8.8.8.8", "2002:808:808::", } blocked := []string{ "127.0.0.1", "::1", // loopback "10.1.2.3", "172.16.0.1", "192.168.1.1", "fc00::1", "fd12::1", "fec0::1", // RFC1918 + ULA + site-local "169.254.169.254", "fe80::1", // link-local / cloud metadata "100.64.0.1", "100.127.255.254", // RFC 6598 CGNAT "198.18.0.1", "198.19.255.255", // benchmark "192.0.0.1", // IETF protocol assignments "192.0.2.1", "198.51.100.1", "203.0.113.1", "2001:db8::1", "3fff::1", // documentation space "0.1.2.3", "240.0.0.1", "255.255.255.255", // this-network / reserved / broadcast "0.0.0.0", "::", "224.0.0.1", "ff02::1", // unspecified / multicast "::ffff:10.0.0.1", "::ffff:127.0.0.1", // IPv4-mapped private/loopback "64:ff9b::7f00:1", "64:ff9b::a00:1", "64:ff9b::a9fe:a9fe", // NAT64-embedded loopback / private / metadata "64:ff9b:1::8.8.8.8", // local-use NAT64 is never public "2002:7f00:1::", "2002:a00:1::", "2002:a9fe:a9fe::", // 6to4-embedded loopback / private / metadata "2001:0:0:0:0:0:7f00:1", "2001:0:4136:e378:8000:63bf:3fff:fdd2", // Teredo "::127.0.0.1", "::10.0.0.1", "::ffff:0:7f00:1", "::ffff:0:10.0.0.1", // IPv4-compatible / translated "100::1", "2001:2::1", "2001:10::1", "2001:20::1", "5f00::1", "3ffe::1", // discard, benchmark, ORCHID, SRv6, 6bone } for _, s := range public { if !IsPublic(net.ParseIP(s)) { t.Errorf("IsPublic(%s) = false, want true", s) } } for _, s := range blocked { if IsPublic(net.ParseIP(s)) { t.Errorf("IsPublic(%s) = true, want false", s) } } } func TestAllowPrivateEnv(t *testing.T) { if AllowPrivate() { t.Fatal("AllowPrivate must default to false") } t.Setenv("INTEGRATIONS_ALLOW_PRIVATE", "1") if !AllowPrivate() { t.Fatal("AllowPrivate must honor INTEGRATIONS_ALLOW_PRIVATE=1") } } func TestDialContextBlocksLoopbackByDefault(t *testing.T) { if _, e := DialContext(context.Background(), "tcp", "127.0.0.1:5432"); e == nil { t.Fatal("loopback destination accepted") } } func TestDialContextPortsRejectsUnusualPorts(t *testing.T) { // Port policy is checked before DNS, so this never dials. if _, e := DialContextPorts(context.Background(), "tcp", "8.8.8.8:22", 80, 443); e == nil { t.Fatal("SSH port accepted for a URL preview") } if _, e := DialContextPorts(context.Background(), "tcp", "8.8.8.8:not-a-port", 80, 443); e == nil { t.Fatal("non-numeric port accepted") } } // The NAT64 well-known prefix must not smuggle a non-public IPv4 destination // past the guard (review follow-up: comprehensive special-range coverage). func TestDialContextBlocksNAT64WrappedLoopback(t *testing.T) { if _, e := DialContext(context.Background(), "tcp", "[64:ff9b::7f00:1]:80"); e == nil { t.Fatal("NAT64-wrapped loopback destination accepted") } if _, e := DialContext(context.Background(), "tcp", "[64:ff9b::a9fe:a9fe]:80"); e == nil { t.Fatal("NAT64-wrapped metadata destination accepted") } } // Bounded fallback among already-validated addresses (review follow-up): a // host that resolves to several addresses connects even when the first // record is unreachable. "localhost" resolves to ::1 and/or 127.0.0.1 // depending on the resolver; the listener binds only IPv4 loopback, so on // dual-stack machines the ::1 attempt must fail over to 127.0.0.1. func TestDialContextFallsBackAmongValidatedAddresses(t *testing.T) { t.Setenv("INTEGRATIONS_ALLOW_PRIVATE", "1") ln, e := net.Listen("tcp4", "127.0.0.1:0") if e != nil { t.Skipf("no IPv4 loopback listener possible: %v", e) } defer ln.Close() go func() { for { c, e := ln.Accept() if e != nil { return } c.Close() } }() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() conn, e := DialContext(ctx, "tcp", fmt.Sprintf("localhost:%d", ln.Addr().(*net.TCPAddr).Port)) if e != nil { t.Fatalf("fallback dial to localhost failed: %v", e) } conn.Close() }