Files

55 lines
1.9 KiB
Python
Raw Permalink Normal View History

"""Bounded PROXY-v1 parser and trusted reverse-proxy client-IP extraction."""
import ipaddress
CLIENT_HEADERS = ('CF-Connecting-IP', 'True-Client-IP', 'X-Real-IP')
def public_ip(value):
"""Return a normalized global client IP, or None for private/spoofed values."""
try:
ip = ipaddress.ip_address(value.strip())
except (ValueError, AttributeError):
return None
if not ip.is_global or ip.is_multicast:
return None
return str(ip)
def forwarded_client(headers):
"""Real client IP from a trusted proxy. Priority: single-hop headers, then nearest XFF hop."""
for name in CLIENT_HEADERS:
value = headers.get(name)
if value:
ip = public_ip(value)
if ip:
return ip, name
for hop in reversed((headers.get('X-Forwarded-For') or '').split(',')):
ip = public_ip(hop)
if ip:
return ip, 'X-Forwarded-For'
return None, None
def source(connection, transport, trusted):
if transport[0] != trusted:
raise ValueError('untrusted PROXY transport')
header = bytearray()
while len(header) < 108:
value = connection.recv(1)
if not value:
raise ValueError('incomplete PROXY header')
header.extend(value)
if header.endswith(b'\r\n'):
break
else:
raise ValueError('oversize PROXY header')
fields = header.decode('ascii').strip().split(' ')
if len(fields) != 6 or fields[0] != 'PROXY' or fields[1] not in ('TCP4', 'TCP6'):
raise ValueError('invalid PROXY header')
version = 4 if fields[1] == 'TCP4' else 6
if any(ipaddress.ip_address(ip).version != version for ip in fields[2:4]):
raise ValueError('PROXY address family mismatch')
ports = [int(value) for value in fields[4:]]
if any(not 0 < port <= 65535 for port in ports):
raise ValueError('invalid PROXY port')
return (fields[2], ports[0]), bytes(header)