XFF - X-Forwarded-For parsing and trusted proxy detection#
Parse X-Forwarded-For headers to extract client IP addresses behind proxies. Supports trusted proxy validation using CIDR ranges to prevent IP spoofing. Implements de-facto X-Forwarded-For standard and aligns with RFC 7239.
Installation#
Install with opam:
$ opam install xff
If opam cannot find the package, it may not yet be released in the public
opam-repository. Add the overlay repository, then install it:
$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install xff
Reference#
Documentation#
Example#
Extract the real client IP while treating your own load balancers as trusted
hops. client_ip only believes the X-Forwarded-For header when the socket
peer is itself a trusted proxy, which is what prevents spoofing:
let trusted =
List.map Xff.parse_cidr_exn [ "10.0.0.0/8"; "127.0.0.1/32" ]
let socket_ip = Ipaddr.of_string_exn "10.0.0.12"
let client =
Xff.client_ip
~socket_ip:(Some socket_ip)
~xff_header:(Some "203.0.113.7, 10.0.0.12, 127.0.0.1")
~trusted_proxies:(Some trusted)
(* client = Some 203.0.113.7 *)
Invalid or missing data#
parse_xff silently drops entries that are not valid IPs (so a malicious
proxy cannot inject garbage), and client_ip returns None when the socket
peer is unknown:
let () =
let ips = Xff.parse_xff "203.0.113.7, not-an-ip, 10.0.0.12" in
List.iter (fun ip -> Fmt.pr "%a@." Ipaddr.pp ip) ips
(* prints 203.0.113.7 and 10.0.0.12; "not-an-ip" is dropped *)
let no_peer =
Xff.client_ip
~socket_ip:None
~xff_header:(Some "203.0.113.7")
~trusted_proxies:(Some trusted)
(* no_peer = None -- never trust an XFF header without a socket peer *)
let bad_cidr =
match Xff.parse_cidr "not-a-cidr" with
| Ok p -> Some p
| Error (`Msg msg) ->
Fmt.epr "bad CIDR: %s@." msg;
None
Use client_ip_string when you want a single string suitable for logs;
it returns "unknown" instead of None.