12
votes

I have a program in go which accepts URLs from clients and gets them using the net/http package. Before doing further processing, I would like to check if the URL maps to private (non-routable / RFC1918 networks) address space.

The straight-forward way would be to perform an explicit DNS request and check the address for the known private ranges. After that, perform the HTTP GET request for the URL.

Is there a better way to accomplish this? Preferably integrating with http.Client so it can be performed as a part of the GET request.

4
What is "private network space"? The RFC1918 networks? - Martin Tournoij
I ran into exactly this and found: no there is no better alternative. See github.com/mhausenblas/clump/blob/master/main.go#L83 - Michael Hausenblas
Sorry, yes, the RFC1918 networks. I updated the question to state this. - StefanOS
What exactly do you want to do? Do you just want to log if the host is in a private network, or is there something else you need to do before making the request? @MichaelHausenblas: FYI a net.IPNet has a Contains method - JimB
For one use case I want to abort the GET request for RFC1918 networks. For another case I would just log the IP address and whether it's private or public. - StefanOS

4 Answers

28
votes

You might also want to include checks for loopback (IPv4 or IPv6) and/or IPv6 link-local or unique-local addresses. Here is an example with a list of RFC1918 address plus these others and a simple check against them as isPrivateIP(ip net.IP):

var privateIPBlocks []*net.IPNet

func init() {
    for _, cidr := range []string{
        "127.0.0.0/8",    // IPv4 loopback
        "10.0.0.0/8",     // RFC1918
        "172.16.0.0/12",  // RFC1918
        "192.168.0.0/16", // RFC1918
        "169.254.0.0/16", // RFC3927 link-local
        "::1/128",        // IPv6 loopback
        "fe80::/10",      // IPv6 link-local
        "fc00::/7",       // IPv6 unique local addr
    } {
        _, block, err := net.ParseCIDR(cidr)
        if err != nil {
            panic(fmt.Errorf("parse error on %q: %v", cidr, err))
        }
        privateIPBlocks = append(privateIPBlocks, block)
    }
}

func isPrivateIP(ip net.IP) bool {
    if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
        return true
    }

    for _, block := range privateIPBlocks {
        if block.Contains(ip) {
            return true
        }
    }
    return false
  }
5
votes

That should be easier to do with Go 1.17 (Q4 2021, 5 years later), as reported by Go 101:

See commit c73fccc and CL 272668:

net: add IP.IsPrivate()

Add IsPrivate() helper to check if an IP is private according to RFC 1918 & RFC 4193

That fixes golang issue 29146 raised by Aaran McGuire:

The net package seems to have many helpers to report what an IP is. e.g:

  • IsLoopback()
  • IsMulticast()
  • IsInterfaceLocalMulticast()

However there are no helpers to report if a IP address is in the private ranges (RFC 1918 & RFC 4193).

3
votes
package main

import (
    "fmt"
    "net"
)

func main() {
    fmt.Println(privateIPCheck("1.1.1.1"))  // False since this is not a private IP
    fmt.Println(privateIPCheck("10.8.0.1")) // True: Since this is a private ip.
}

// Check if a ip is private.
func privateIPCheck(ip string) bool {
    ipAddress := net.ParseIP(ip)
    return ipAddress.IsPrivate()
}

This requires Go 1.17.

2
votes

It seems there's no better way to accomplish than the one I described. Combining code from @MichaelHausenblas with the suggestion from @JimB, my code ended up kind of like this.

func privateIP(ip string) (bool, error) {
    var err error
    private := false
    IP := net.ParseIP(ip)
    if IP == nil {
        err = errors.New("Invalid IP")
    } else {
        _, private24BitBlock, _ := net.ParseCIDR("10.0.0.0/8")
        _, private20BitBlock, _ := net.ParseCIDR("172.16.0.0/12")
        _, private16BitBlock, _ := net.ParseCIDR("192.168.0.0/16")
        private = private24BitBlock.Contains(IP) || private20BitBlock.Contains(IP) || private16BitBlock.Contains(IP)
    }
    return private, err
}