X-Forwarded-For parsing and trusted proxy detection for OCaml
at main 102 lines 3.4 kB view raw
1(** X-Forwarded-For parsing and trusted proxy detection. 2 3 This module provides utilities for extracting client IP addresses from 4 X-Forwarded-For headers when running behind reverse proxies, with support 5 for trusted proxy validation to prevent IP spoofing. 6 7 {2 Example} 8 9 {[ 10 (* Configure trusted proxies *) 11 let trusted = Xff.private_ranges 12 13 (* Extract client IP from request *) 14 let client_ip = 15 Xff.get_client_ip_string ~socket_ip:(Some socket_addr) 16 ~xff_header:(Some "203.0.113.50, 10.0.0.1") 17 ~trusted_proxies:(Some trusted) 18 (* Returns "203.0.113.50" if socket_ip is in trusted range *) 19 ]} 20 21 {2 References} 22 23 - {{:https://datatracker.ietf.org/doc/html/rfc7239} RFC 7239} - Forwarded 24 HTTP Extension 25 - X-Forwarded-For is a de-facto standard *) 26 27(** {1 Types} *) 28 29type ip = Ipaddr.t 30(** An IP address (IPv4 or IPv6). *) 31 32type prefix = Ipaddr.Prefix.t 33(** A CIDR prefix for matching IP ranges. *) 34 35(** {1 CIDR Parsing} *) 36 37val parse_cidr : string -> (prefix, [ `Msg of string ]) result 38(** [parse_cidr s] parses a CIDR string like ["192.168.0.0/16"] or 39 ["2001:db8::/32"]. *) 40 41val parse_cidr_exn : string -> prefix 42(** [parse_cidr_exn s] is like {!parse_cidr} but raises [Invalid_argument] on 43 error. *) 44 45(** {1 Trusted Proxy Detection} *) 46 47val ip_in_prefix : ip -> prefix -> bool 48(** [ip_in_prefix ip prefix] returns [true] if [ip] is within the CIDR [prefix]. 49*) 50 51val is_trusted_proxy : ip -> prefix list -> bool 52(** [is_trusted_proxy ip prefixes] returns [true] if [ip] matches any prefix in 53 the list. *) 54 55(** {1 X-Forwarded-For Parsing} *) 56 57val parse_xff : string -> ip list 58(** [parse_xff header] parses an X-Forwarded-For header value and returns all 59 valid IP addresses. The header format is ["client, proxy1, proxy2, ..."]. 60 Invalid entries are silently skipped. Port suffixes are stripped. *) 61 62val first_xff_ip : string -> ip option 63(** [first_xff_ip header] extracts the leftmost (original client) IP from an 64 X-Forwarded-For header. Returns [None] if no valid IP is found. *) 65 66(** {1 Client IP Extraction} *) 67 68val client_ip : 69 socket_ip:ip option -> 70 xff_header:string option -> 71 trusted_proxies:prefix list option -> 72 ip option 73(** [client_ip ~socket_ip ~xff_header ~trusted_proxies] extracts the real client 74 IP address considering trusted proxy configuration. 75 76 - If [socket_ip] is from a trusted proxy and [xff_header] is present, 77 returns the client IP from X-Forwarded-For 78 - Otherwise returns [socket_ip] 79 - Returns [None] if [socket_ip] is [None] 80 81 This prevents IP spoofing: only connections from trusted proxies can set the 82 client IP via X-Forwarded-For. *) 83 84val client_ip_string : 85 socket_ip:ip option -> 86 xff_header:string option -> 87 trusted_proxies:prefix list option -> 88 string 89(** [client_ip_string ~socket_ip ~xff_header ~trusted_proxies] is like 90 {!client_ip} but returns a string. Returns ["unknown"] if the IP cannot be 91 determined. *) 92 93(** {1 Common Trusted Proxy Ranges} *) 94 95val private_ranges : prefix list 96(** [private_ranges] is a list of RFC 1918 private ranges, loopback, and IPv6 97 equivalents: [10.0.0.0/8], [172.16.0.0/12], [192.168.0.0/16], [127.0.0.0/8], 98 [::1/128], [fc00::/7], [fe80::/10]. *) 99 100val cloudflare_ranges : prefix list 101(** Cloudflare proxy IP ranges (IPv4 and IPv6). See 102 {{:https://www.cloudflare.com/ips/} Cloudflare IPs}. *)