Help us shape the future of Go! Take the 2026 Go Developer Survey

realip

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 20, 2026 License: MIT Imports: 8 Imported by: 0

README

FastHTTP - RealIP

Go Reference

Go package that can be used to get client's real public IP from Fast HTTP request, which usually useful for logging HTTP server.

This is fork from realip for Fast HTTP with some imporvements.

Soft fork: this repository is a soft fork of ferluci/fasthttp-realip and continues its development under the module path github.com/akdilsiz/fasthttp-realip. The public API stays compatible with the original (FromRequest is unchanged); new work such as the trusted proxy Resolver is also offered upstream (ferluci/fasthttp-realip#6). To migrate, replace the import path; the package name is still realip.

Feature
  • Follows the rule of X-Real-IP
  • Follows the rule of X-Forwarded-For
  • Exclude local or private address
  • Resolver: trusted proxy aware resolution that clients cannot spoof (X-Forwarded-For, RFC 7239 Forwarded or a single value header such as X-Real-IP / CF-Connecting-IP)

How It Works

Security: FromRequest below trusts headers that any client can set. Use it for display or logging only. For access control, rate limiting or audit records use Resolver, see Security.

It looks for specific headers in the request and falls back to some defaults if they do not exist.

The user ip is determined by the following order:

  1. X-Client-IP
  2. X-Original-Forwarded-For
  3. X-Forwarded-For (Header may return multiple IP addresses in the format: "client IP, proxy 1 IP, proxy 2 IP", so we take the the first one.)
  4. CF-Connecting-IP (Cloudflare)
  5. Fastly-Client-Ip (Fastly CDN and Firebase hosting header when forwared to a cloud function)
  6. True-Client-Ip (Akamai and Cloudflare)
  7. X-Real-IP (Nginx proxy/FastCGI)
  8. X-Forwarded, Forwarded-For and Forwarded (Variations of #2)
  9. ctx.RemoteAddr().String()

Install

go get -u github.com/akdilsiz/fasthttp-realip

Example

package main

import (
    "log"
    "github.com/valyala/fasthttp"
    realip "github.com/akdilsiz/fasthttp-realip"
)

func main() {
    if err := fasthttp.ListenAndServe(":8080", realipHandler); err != nil {
        log.Fatalf("Error in ListenAndServe: %s", err)
    }
}

func realipHandler(ctx *fasthttp.RequestCtx) {
    clientIP := realip.FromRequest(ctx)
    log.Println("GET / from", clientIP)
}


Trusted Proxy Resolver

Resolver returns the client IP while trusting only the proxies you list. Build it once at startup and share it; it is safe for concurrent use.

package main

import (
    "log"

    realip "github.com/akdilsiz/fasthttp-realip"
    "github.com/valyala/fasthttp"
)

func main() {
    // nginx / load balancer in 10.0.0.0/8 appends to X-Forwarded-For.
    resolver, err := realip.NewResolver(realip.Options{
        TrustedProxies: []string{"10.0.0.0/8", "2001:db8:ffff::/48"},
        Header:         realip.HeaderXForwardedFor, // the default
    })
    if err != nil {
        log.Fatal(err) // invalid CIDR, /0 block or unsafe header name
    }

    handler := func(ctx *fasthttp.RequestCtx) {
        log.Println("GET / from", resolver.FromRequest(ctx))
    }

    log.Fatal(fasthttp.ListenAndServe(":8080", handler))
}

For net/http servers use resolver.FromHTTPRequest(r); it applies the same rules, taking the peer from r.RemoteAddr.

Options.Header selects the one header your edge proxy writes:

Header Format Typical proxy
X-Forwarded-For (default) comma list, ip, ip:port, [ipv6]:port nginx $proxy_add_x_forwarded_for, most load balancers
Forwarded RFC 7239, for=192.0.2.1, for="[2001:db8::1]:443" proxies that emit RFC 7239
any other name, e.g. X-Real-IP, CF-Connecting-IP exactly one IP proxies that overwrite the header (nginx proxy_set_header X-Real-IP $remote_addr, Cloudflare)

TrustedProxies takes CIDR blocks or single addresses. An empty list trusts nobody and always returns the TCP peer address.

Example: Cloudflare → AWS ELB → nginx → service on the same host. The service sees X-Forwarded-For: client, cloudflare-ip, elb-ip from peer 127.0.0.1. Trust Cloudflare's ranges (ips-v4, ips-v6), your VPC ranges and loopback; the client is the first hop left of a Cloudflare address, and a request that reaches the ELB without Cloudflare resolves to its real sender.

How a request is resolved:

  1. The TCP peer (ctx.RemoteIP()) is the starting point, because it is the only value a client cannot forge. If the peer is not a trusted proxy it is returned and no header is read.
  2. All field lines of the configured header are read in order (repeated lines form one list, RFC 9110 section 5.3).
  3. The list is walked from the right. Every hop written by a trusted proxy is accepted; the first hop that is not a trusted proxy is the client.
  4. A hop that is not an IP (unknown, an obfuscated _name, a zone or any syntax error) stops the walk and the last verified address is returned. If every hop is trusted, the leftmost one is returned.
  5. The result is always a parsed IP in canonical form, never header text.

Security

The address a client reports in a header is client input. Only a proxy you operate can turn it into something trustworthy, and only for the header it writes.

FromRequest (kept unchanged for compatibility):

  • reads X-Client-IP first and returns it verbatim, without checking that it is an IP; CF-Connecting-IP, Fastly-Client-Ip, True-Client-Ip and X-Real-IP are also returned verbatim;
  • takes the leftmost X-Forwarded-For entry, which is the one the client writes;
  • has no notion of trusted proxies, so any client can choose its own IP.

Do not use it for IP allow lists, rate limits, lockouts or audit logs unless your edge proxy removes or overwrites every header listed in How It Works.

Resolver guarantees:

  • an untrusted peer can never be overridden by a header;
  • only the configured header is read; other forwarding headers are ignored;
  • Forwarded is parsed strictly by RFC 7239 (token / quoted-string values, quoted-pair escapes, one for per element, bracketed and quoted IPv6, quoted ip:port, numeric or obfuscated ports), and the list is split quote-aware from the right, so an unterminated quote sent by the client cannot hide elements appended by your proxies;
  • configuration errors fail in NewResolver: invalid CIDR or IP, invalid header name, /0 blocks (also IPv4-mapped ones such as ::ffff:0.0.0.0/96) and client-written headers (Host, Cookie, Authorization, User-Agent, Referer, Origin, Content-*, Connection, Transfer-Encoding);
  • cost is linear in header size, and header size is bounded by the fasthttp server read buffer.

Your responsibilities as operator:

  • list only the proxies that really sit in front of the service and keep the blocks narrow; several wide blocks (for example 0.0.0.0/1 and 128.0.0.0/1) still trust everyone;
  • make the edge proxy append to X-Forwarded-For / Forwarded, or overwrite a single value header; if it forwards a client copy of a single value header, the result falls back to the proxy address (safe, but every client then shares one IP for per-IP limits);
  • make trusted proxies write RFC 7239 compliant for= values when using Forwarded, otherwise the walk stops at them;
  • behind a unix socket fasthttp reports the peer as 0.0.0.0, which is trusted only if you list it.

Developing

Committed code must pass (see .github/workflows/go.yml):

  • golangci-lint with revive, gofmt, goimports, gocritic, misspell and friends
  • go vet
  • go test -race ./...
  • go test -run '^$' -fuzz FuzzResolver -fuzztime 30s . (Go 1.18+)

realip_test.go pins the legacy FromRequest behavior; resolver_test.go and resolver_fuzz_test.go cover Resolver.

License

MIT. This project builds on tomasen/realip (MIT, Copyright (c) 2018 SHEN SHENG) and ferluci/fasthttp-realip; the original copyright notice is kept in LICENSE. Changes in this soft fork are released under the same MIT license, Copyright (c) 2026 Abdulkadir D <github.com/akdilsiz>.

Documentation

Overview

Package realip returns the client IP address of a fasthttp request.

FromRequest keeps the historical header order and trusts every header; Resolver trusts only configured proxies and is the safe choice for access control, rate limiting and audit records.

Index

Constants

View Source
const (
	// HeaderXForwardedFor selects the de facto X-Forwarded-For list.
	HeaderXForwardedFor = "X-Forwarded-For"
	// HeaderForwarded selects the RFC 7239 Forwarded list.
	HeaderForwarded = "Forwarded"
)

Header names accepted by Options.Header with a dedicated parser.

Variables

This section is empty.

Functions

func FromRequest

func FromRequest(ctx *fasthttp.RequestCtx) string

FromRequest returns client's real public IP address from http request headers.

Security: FromRequest trusts every header it reads, and clients can set all of them. X-Client-IP, CF-Connecting-IP, Fastly-Client-Ip, True-Client-Ip and X-Real-IP are returned verbatim, without checking that they hold an IP, and X-Forwarded-For uses its leftmost entry, which the client writes. Use it only for display or logging, or when every one of these headers is removed or overwritten by your edge proxy. For access control, rate limiting or audit records use Resolver, which trusts only configured proxies. The behavior is kept unchanged for compatibility.

Types

type Options

type Options struct {
	TrustedProxies []string
	Header         string
}

Options configures a Resolver.

TrustedProxies lists the proxies allowed to set the client address, as CIDR blocks ("10.0.0.0/8", "2001:db8::/32") or single addresses ("192.0.2.10"). An empty list trusts nobody: the TCP peer is returned.

Header names the one header the trusted proxies write. It must be the header your edge proxy overwrites or appends to; reading any other header lets clients forge their address. Empty means X-Forwarded-For. "Forwarded" uses RFC 7239 parsing. Any other name (for example X-Real-IP or CF-Connecting-IP) is read as a single IP value.

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver returns the client IP of a fasthttp or net/http request while trusting only configured proxies. It is safe for concurrent use; it is immutable after NewResolver returns.

func NewResolver

func NewResolver(opts Options) (*Resolver, error)

NewResolver validates opts and returns a ready Resolver.

It returns an error when a TrustedProxies entry is neither a CIDR nor an IP address, or when Header is not a valid HTTP field name. Validation happens here, not per request, so a bad configuration fails at startup instead of silently trusting or distrusting traffic.

func (*Resolver) FromHTTPRequest added in v0.2.0

func (r *Resolver) FromHTTPRequest(req *http.Request) string

FromHTTPRequest is FromRequest for net/http servers: the peer is taken from req.RemoteAddr, which net/http sets from the TCP connection, and the header lines from req.Header in receive order. A RemoteAddr that is not an "ip:port" (for example a unix socket) is reported as 0.0.0.0, matching fasthttp, and is only trusted if that address is configured.

func (*Resolver) FromRequest

func (r *Resolver) FromRequest(ctx *fasthttp.RequestCtx) string

FromRequest returns the client IP of a fasthttp request in canonical text form (net.IP.String).

The TCP peer is returned unless it is a trusted proxy. For a trusted peer the configured header is read from the right: each hop a trusted proxy wrote is accepted, and the first hop that is not itself a trusted proxy is the client. Malformed, "unknown" or obfuscated hops stop the walk and the last verified address is returned, never a guessed one. The result is therefore always an IP address and never raw header text.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL