Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IPv6 support #15

Merged
merged 4 commits into from
Jan 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 32 additions & 18 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand All @@ -66,7 +85,7 @@ func (c *config) isIPForbidden(ip net.IP) bool {
}
}

if ip.Equal(net.IPv4bcast) || !ip.IsGlobalUnicast() {
if !ip.IsGlobalUnicast() {
return true
}

Expand Down Expand Up @@ -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
Expand All @@ -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)
}
Expand All @@ -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")
}
}
}
Expand Down
15 changes: 13 additions & 2 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down