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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1X-Forwarded-For is client-controllable, so you can only trust hops that come from proxies you actually operate.
- 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.
- 3Parsing CIDR ranges once at startup and panicking on bad input keeps the hot path fast and guarantees valid config.
Related explainers
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
java
public static Map<String, String> parseCookieHeader(String header) { Map<String, String> cookies = new LinkedHashMap<>(); if (header == null || header.isBlank()) { return cookies;
Parsing an HTTP Cookie header in Java
string-parsing
http
url-decoding
Intermediate
6 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
go
package middleware import ( "net/http"
Per-plan export limits in Gin middleware
middleware
rate-limiting
authorization
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/safely-extracting-the-real-client-ip-in-go-explained-go-b372/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.