go 58 lines · 7 steps

Safely extracting the real client IP in Go

Walk the X-Forwarded-For chain from right to left, trusting only known proxy ranges, to find the true client address.

Explained by highlit
1package httputil
2 
3import (
4 "net"
5 "net/http"
6 "strings"
7)
8 
9var trustedProxies = []*net.IPNet{
10 mustCIDR("10.0.0.0/8"),
11 mustCIDR("172.16.0.0/12"),
12 mustCIDR("192.168.0.0/16"),
13 mustCIDR("127.0.0.0/8"),
14 mustCIDR("::1/128"),
15 mustCIDR("fc00::/7"),
16}
17 
18func mustCIDR(s string) *net.IPNet {
19 _, n, err := net.ParseCIDR(s)
20 if err != nil {
21 panic(err)
22 }
23 return n
24}
25 
26func isTrusted(ip net.IP) bool {
27 for _, n := range trustedProxies {
28 if n.Contains(ip) {
29 return true
30 }
31 }
32 return false
33}
34 
35func ClientIP(r *http.Request) string {
36 remote, _, err := net.SplitHostPort(r.RemoteAddr)
37 if err != nil {
38 remote = r.RemoteAddr
39 }
40 
41 xff := r.Header.Get("X-Forwarded-For")
42 if xff == "" || !isTrusted(net.ParseIP(remote)) {
43 return remote
44 }
45 
46 parts := strings.Split(xff, ",")
47 for i := len(parts) - 1; i >= 0; i-- {
48 candidate := strings.TrimSpace(parts[i])
49 ip := net.ParseIP(candidate)
50 if ip == nil {
51 continue
52 }
53 if !isTrusted(ip) {
54 return candidate
55 }
56 }
57 return remote
58}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1X-Forwarded-For is client-controllable, so you can only trust hops that come from proxies you actually operate.
  2. 2Walking the forwarding chain from right to left lets you peel off trusted proxies until you hit the first address you can't vouch for.
  3. 3Parsing CIDR ranges once at startup and panicking on bad input keeps the hot path fast and guarantees valid config.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Safely extracting the real client IP in Go — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code