package netutil import ( "context" "fmt" "net" "net/url" "strings" ) func isPrivateIP(ip net.IP) bool { privateRanges := []struct { network *net.IPNet }{ {mustParseCIDR("10.0.0.0/8")}, {mustParseCIDR("172.16.0.0/12")}, {mustParseCIDR("192.168.0.0/16")}, {mustParseCIDR("127.0.0.0/8")}, {mustParseCIDR("169.254.0.0/16")}, {mustParseCIDR("0.0.0.0/8")}, {mustParseCIDR("100.64.0.0/10")}, {mustParseCIDR("198.18.0.0/15")}, {mustParseCIDR("::1/128")}, {mustParseCIDR("fc00::/7")}, {mustParseCIDR("fe80::/10")}, {mustParseCIDR("::ffff:127.0.0.0/104")}, } for _, r := range privateRanges { if r.network.Contains(ip) { return true } } return false } func mustParseCIDR(s string) *net.IPNet { _, n, err := net.ParseCIDR(s) if err != nil { panic(err) } return n } type SafeDialer struct { Dialer net.Dialer } func (d *SafeDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { host, port, err := net.SplitHostPort(addr) if err != nil { return nil, fmt.Errorf("netutil: invalid address %q: %w", addr, err) } resolver := net.Resolver{} ips, err := resolver.LookupIPAddr(ctx, host) if err != nil { return nil, fmt.Errorf("netutil: DNS lookup failed for %q: %w", host, err) } for _, ipAddr := range ips { if isPrivateIP(ipAddr.IP) { return nil, fmt.Errorf("netutil: resolved IP %s of %q is a private/reserved address", ipAddr.IP, host) } } var targetIPs []string for _, ipAddr := range ips { targetIPs = append(targetIPs, ipAddr.IP.String()) } var lastErr error for _, ipStr := range targetIPs { conn, err := d.Dialer.DialContext(ctx, network, net.JoinHostPort(ipStr, port)) if err != nil { lastErr = err continue } remoteAddr, ok := conn.RemoteAddr().(*net.TCPAddr) if ok && isPrivateIP(remoteAddr.IP) { conn.Close() return nil, fmt.Errorf("netutil: connected IP %s is a private/reserved address (DNS rebinding detected)", remoteAddr.IP) } return conn, nil } if lastErr != nil { return nil, lastErr } return nil, fmt.Errorf("netutil: no reachable IP for %q", host) } func ValidateURL(rawURL string) error { u, err := url.Parse(rawURL) if err != nil { return fmt.Errorf("netutil: invalid URL: %w", err) } scheme := strings.ToLower(u.Scheme) if scheme != "http" && scheme != "https" { return fmt.Errorf("netutil: scheme %q is not allowed, only http and https are permitted", u.Scheme) } if u.Host == "" { return fmt.Errorf("netutil: URL missing host") } hostname := u.Hostname() if hostname == "" { return fmt.Errorf("netutil: URL missing hostname") } if ip := net.ParseIP(hostname); ip != nil { if isPrivateIP(ip) { return fmt.Errorf("netutil: host IP %s is a private/reserved address", ip) } } return nil } func CheckResolvedHost(hostname string) error { ips, err := net.LookupIP(hostname) if err != nil { return fmt.Errorf("netutil: DNS lookup failed for %q: %w", hostname, err) } for _, ip := range ips { if isPrivateIP(ip) { return fmt.Errorf("netutil: resolved IP %s of %q is a private/reserved address", ip, hostname) } } return nil }