Files

135 lines
3.1 KiB
Go
Raw Permalink Normal View History

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
}