diff --git a/client.go b/client.go index 5b65321..3d2ed4c 100644 --- a/client.go +++ b/client.go @@ -32,19 +32,38 @@ func mustParseCIDR(addr string) *net.IPNet { } func init() { + forbiddenIPs := []string{ + //IPv4 + "10.0.0.0/8", // private class A + "172.16.0.0/12", // private class B + "192.168.0.0/16", // private class C + "192.0.2.0/24", // test net 1 + "192.88.99.0/24", // 6to4 relay + + // ipv6 + // block everything except 2000::/3 according to rfc2373#section-2.4 + "0000::/3", // 0000-0010 + "4000::/2", // 0100-1000 + "8000::/1", // 1000-1111 + //v6 special ranges inside 2000::/3 + "2001::/32", // Teredo tunneling + "2001:10::/28", // Deprecated (previously ORCHID) + "2001:20::/28", // ORCHIDv2 + "2001:db8::/32", // Addresses used in documentation and example source code + "2002::/16", // 6to4 + } + defaultConfig = config{ - ForbiddenIPNets: []*net.IPNet{ - mustParseCIDR("10.0.0.0/8"), // private class A - mustParseCIDR("172.16.0.0/12"), // private class B - mustParseCIDR("192.168.0.0/16"), // private class C - mustParseCIDR("192.0.2.0/24"), // test net 1 - mustParseCIDR("192.88.99.0/24"), // 6to4 relay - }, + ForbiddenIPNets: make([]*net.IPNet, len(forbiddenIPs)), ForbiddenHosts: []*regexp.Regexp{ regexp.MustCompile(`(?i)^localhost$`), regexp.MustCompile(`(?i)\s+`), }, } + for n, i := range forbiddenIPs { + defaultConfig.ForbiddenIPNets[n] = mustParseCIDR(i) + } + DefaultClient, _, _ = NewClient() } @@ -66,7 +85,7 @@ func (c *config) isIPForbidden(ip net.IP) bool { } } - if ip.Equal(net.IPv4bcast) || !ip.IsGlobalUnicast() { + if !ip.IsGlobalUnicast() { return true } @@ -121,7 +140,7 @@ func safeAddr(ctx context.Context, resolver *net.Resolver, hostport string, opts ip := net.ParseIP(host) if ip != nil { - if ip.IsUnspecified() || (ip.To4() != nil && c.isIPForbidden(ip)) { + if ip.IsUnspecified() || c.isIPForbidden(ip) { return "", fmt.Errorf("bad ip is detected: %v", ip) } return net.JoinHostPort(ip.String(), port), nil @@ -141,10 +160,6 @@ func safeAddr(ctx context.Context, resolver *net.Resolver, hostport string, opts } safeAddrs := make([]net.IPAddr, 0, len(addrs)) for _, addr := range addrs { - // only support IPv4 address - if addr.IP.To4() == nil { - continue - } if c.isIPForbidden(addr.IP) { return "", fmt.Errorf("bad ip is detected: %v", addr.IP) } @@ -156,21 +171,20 @@ func safeAddr(ctx context.Context, resolver *net.Resolver, hostport string, opts return net.JoinHostPort(safeAddrs[0].IP.String(), port), nil } -// NewDialer returns a dialer function which only accepts IPv4 connections. +// NewDialer returns a dialer function which only accepts connections to secure hosts. // // This is used to create a new paranoid http.Client, -// because I'm not sure about a paranoid behavior for IPv6 connections :( func NewDialer(dialer *net.Dialer, opts ...Option) func(ctx context.Context, network, addr string) (net.Conn, error) { return func(ctx context.Context, network, hostport string) (net.Conn, error) { switch network { - case "tcp", "tcp4": + case "tcp", "tcp4", "tcp6": addr, err := safeAddr(ctx, dialer.Resolver, hostport, opts...) if err != nil { return nil, err } - return dialer.DialContext(ctx, "tcp4", addr) + return dialer.DialContext(ctx, network, addr) default: - return nil, errors.New("does not support any networks except tcp4") + return nil, errors.New("does not support any networks except tcp") } } } diff --git a/client_test.go b/client_test.go index a7ce139..3e8a897 100644 --- a/client_test.go +++ b/client_test.go @@ -23,6 +23,9 @@ func TestRequest(t *testing.T) { if _, err := DefaultClient.Get("http://[::]"); err == nil { t.Errorf("The request for IPv6 unspecified address should be fail") } + if _, err := DefaultClient.Get("http://[fd00::1234]"); err == nil { + t.Errorf("The request for IPv6 ULA should fail") + } } func TestIsHostForbidden(t *testing.T) { @@ -52,15 +55,20 @@ func TestIsHostForbidden(t *testing.T) { func TestIsIpForbidden(t *testing.T) { badIPs := []string{ - "0.0.0.0", // Unspecified - "127.0.0.0", "127.255.255.255", // Loopback + "0.0.0.0", "::", // Unspecified + "127.0.0.0", "127.255.255.255", "::1", // Loopback "10.0.0.0", "10.255.255.255", // Private A "172.16.0.0", "172.31.255.255", // Private B "192.168.0.0", "192.168.255.255", // Private C + "fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", // Private v6 "192.0.2.0", "192.0.2.255", // Test-Net "192.88.99.0", "192.88.99.255", // 6to4 relay "224.0.0.0", "239.255.255.255", // Multicast + "ff00::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", //Multicast 6 "169.254.0.0", "169.254.255.255", // Link local + "fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff", // Link Local v6 + "::ffff:0:255.255.255.255", "::ffff:255.255.255.255", //v4 to v6 mapping + "2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff", //v6 documentation } for _, ip := range badIPs { @@ -78,6 +86,9 @@ func TestIsIpForbidden(t *testing.T) { "192.88.98.255", "192.88.100.0", "223.255.255.255", "240.0.0.0", "169.253.255.255", "169.255.0.0", + "2000::1", "3fff::1", + // real examples + "2606:4700:4700::1111", "2001:4860:4860::8888", } for _, ip := range notBadIPs {