This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user