Julien's dev blog

HTTP with Go: IP blocklisting middleware

Implementing HTTP IP-based blocklisting with Golang.

Last updated on: 2024-12-09

Parse IP blocklist from config or other:

blockedIPNets := make([]*net.IPNet, 0, len(conf.IPBlocklist))
for _, v := range conf.IPBlocklist {
    _, ipNet, err := net.ParseCIDR(v)
    if err != nil {
        return fmt.Errorf("parse CIDR: %w", err)
    }
    blockedIPNets = append(blockedIPNets, ipNet)
}
Go code

Define middleware type alias (optional):

type Middleware = func(http.Handler) http.Handler

Implement IP blocklist middleware:

func NewIPBlockMiddleware(trueIPHeader string, blocklist []*net.IPNet) Middleware {
    return func(h http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Get request's IP address.
            remoteAddr := r.RemoteAddr
            if trueIPHeader != "" {
                remoteAddr = r.Header.Get(trueIPHeader)
            }
            host, _, _ := net.SplitHostPort(remoteAddr)
            if host == "" {
                host = remoteAddr
            }
            ipAddr := net.ParseIP(host)

            // Check if address is blocked.
            if ipAddr != nil {
                for _, blocked := range blocklist {
                    if blocked.Contains(ipAddr) {
                        http.Error(w, "Forbidden", http.StatusForbidden)
                        return
                    }
                }
            }
            h.ServeHTTP(w, r)
        })
    }
}
Go code

Use the middleware:

httpHandler := NewIPBlockMiddleware("", []string{"1.2.3.4/32"})(httpHandler)
Go code

Notes

If you want the blocklist to be dynamic (for ex: when the source is a database), you can easily adapt the middleware to use a database query or local cache instead.

If performance is critical, you may look into more efficient tree-like data structures for checking if an IP address is within a given set.