A python package for server applications to retrieve client's IP address
Best attempt to get client's IP address while keeping it DRY.
There is no perfect out-of-the-box
solution to counteract fake IP addresses, or IP Address Spoofing. We strongly recommend reading the Advanced Users section. Utilize the proxy_list
and proxy_count
features to adapt the functionality to your specific requirements, especially if you plan to incorporate python-ipware
into authentication, security, or anti-fraud systems.
Keep in mind that python-ipware
is an open-source project, meaning its source code is accessible to everyone. While this openness promotes community engagement and scrutiny, it also exposes the code to potential exploiters who might take advantage of unimplemented or improperly implemented features.
Use python-ipware
only as an additional layer to bolster your security, not as a primary defense mechanism. Always pair it with robust firewall security protocols to ensure comprehensive protection against a variety of security threats, including IP spoofing.
pip install python-ipware
-- or --
pip3 install python-ipware
Here's a basic example of how to use python-ipware
in a view or middleware where the request
object is available. This can be applied in Django, Flask, or other similar frameworks.
from python_ipware import IpWare
# Instantiate IpWare with default values
ipw = IpWare()
# Get the META data from the request object
meta = request.META # Django
# meta = request.environ # Flask
# Get the client IP and the trusted route flag
ip, trusted_route = ipw.get_client_ip(meta)
if ip:
# The 'ip' is an object of type IPv4Address() or IPv6Address() with properties like:
# - ip.is_global: True if the IP is globally routable
# - ip.is_private: True if the IP is a private address
# - ip.is_loopback: True if the IP is a loopback address
# - ip.is_multicast: True if the IP is a multicast address
# - ip.is_unspecified: True if the IP is an unspecified address
# - ip.is_reserved: True if the IP is a reserved address
if trusted_route:
# Indicates if the request came through our trusted proxies
# You can now use the IP address as needed, for example, attaching it to the request object.
# Consider caching the IP address for performance, as it doesn't change often.
# It's also advisable to have distinct session IDs for public and anonymous users to cache the IP address effectively.
Params ⇩ | ⇩ Description |
---|---|
proxy_count ⇨ |
: Total number of expected proxies (pattern: client, proxy1, ..., proxy2 ): if proxy_count = 0 then client : if proxy_count = 1 then client, proxy1 : if proxy_count = 2 then client, proxy1, proxy2 : if proxy_count = 3 then client, proxy1, proxy2 proxy3 |
proxy_list ⇨ |
: List of trusted proxies (ip header pattern: client, proxy1, ,..., proxyN ): if proxy_list = ['10.1.'] then client, proxy1 : if proxy_list = ['10.1', '10.2.3'] then client, proxy1 proxy2 : if proxy_list = ['10.1', '10.2.', '10.3.4.4'] then client, proxy1, proxy2, proxy3 |
leftmost ⇨ |
: leftmost = True is default for de-facto standard.: leftmost = False for rare legacy networks that are configured with the rightmost pattern.: It converts client, proxy1 proxy2 to proxy2, proxy1, client |
Output ⇩ | ⇩ Description |
---|---|
ip ⇨ |
: Client IP address object of type IPv4Address() or IPv6Address() |
trusted_route ⇨ |
: If proxy proxy_count and/or proxy_list were provided and matched, True , else False |
The client IP address can be found in one or more request headers attributes. The lookup order is top to bottom and the default attributes are as follow.
# The default meta precedence order - you can be more specific as per your configuration
# It will start looking through the request headers from top to bottom to find the best match
# It will return the first qualified global (public) ip address it finds, else
# It will return the first qualified private ip address it finds, else
# It will return the first qualified loopback up address it finds, else it returns None
# Update as per your network topology, reduce the numbers and/or reorder the list
request_headers_precedence_order = (
"X_FORWARDED_FOR", # Load balancers or proxies such as AWS ELB (default client is `left-most` [`<client>, <proxy1>, <proxy2>`])
"HTTP_X_FORWARDED_FOR", # Similar to X_FORWARDED_TO
"HTTP_CLIENT_IP", # Standard headers used by providers such as Amazon EC2, Heroku etc.
"HTTP_X_REAL_IP", # Standard headers used by providers such as Amazon EC2, Heroku etc.
"HTTP_X_FORWARDED", # Squid and others
"HTTP_X_CLUSTER_CLIENT_IP", # Rackspace LB and Riverbed Stingray
"HTTP_FORWARDED_FOR", # RFC 7239
"HTTP_FORWARDED", # RFC 7239
"HTTP_CF_CONNECTING_IP", # CloudFlare
"X-CLIENT-IP", # Microsoft Azure
"X-REAL-IP", # NGINX
"X-CLUSTER-CLIENT-IP", # Rackspace Cloud Load Balancers
"X_FORWARDED", # Squid
"FORWARDED_FOR", # RFC 7239
"CF-CONNECTING-IP", # CloudFlare
"TRUE-CLIENT-IP", # CloudFlare Enterprise,
"FASTLY-CLIENT-IP", # Firebase, Fastly
"FORWARDED", # RFC 7239
"CLIENT-IP", # Akamai and Cloudflare: True-Client-IP and Fastly: Fastly-Client-IP
"REMOTE_ADDR", # Default
)
You can customize the order by providing your own list during initialization when calling IpWare()
.
# specific meta key
ipw = IpWare(precedence=("X_FORWARDED_FOR"))
# multiple meta keys
ipw = IpWare(precedence=("X_FORWARDED_FOR", "HTTP_X_FORWARDED_FOR"))
# Django (request.META)
ip, proxy_verified = ipw.get_client_ip(meta=request.META)
# Flask (request.environ)
ip, proxy_verified = ipw.get_client_ip(meta=request.environ)
# ... etc.
If your node server is behind one or more known proxy server(s), you can filter out unwanted requests
by providing a trusted proxy list
, or a known proxy count
.
You can customize the proxy IP prefixes by providing your own list during initialization when calling IpWare(proxy_list)
.
You can pass your custom list on every call, when calling the proxy-aware api to fetch the ip.
# In the above scenario, use your load balancer IP address as a way to filter out unwanted requests.
ipw = IpWare(proxy_list=["198.84.193.157"])
# If you have multiple proxies, simply add them to the list
ipw = IpWare(proxy_list=["198.84.193.157", "198.84.193.158"])
# For proxy servers with fixed sub-domain and dynamic IP, use the following pattern.
ipw = IpWare(proxy_list=["177.139.", "177.140"])
# usage: non-strict mode (X-Forwarded-For: <fake>, <client>, <proxy1>, <proxy2>)
# The request went through our <proxy1> and <proxy2>, then our server
# We choose the <client> ip address to the left our <proxy1> and ignore other ips
ip, trusted_route = ipw.get_client_ip(meta=request.META)
# usage: strict mode (X-Forwarded-For: <client>, <proxy1>, <proxy2>)
# The request went through our <proxy1> and <proxy2>, then our server
# Total ip address are total trusted proxies + client ip
# We don't allow far-end proxies, or fake addresses (exact or None)
ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
In the following example
, your public load balancer (LB) can be seen as a trusted
proxy.
`Real` Client <public> <-> <public> LB (Server) <private> <-----> <private> Django Server
^
|
`Fake` Client <private> <-> <private> LB (Server) <private> -+
If your python server is behind a known
number of proxies, but you deploy on multiple providers and don't want to track proxy IPs, you still can filter out unwanted requests by providing proxy count
.
You can customize the proxy count by providing your proxy_count
during initialization when calling IpWare(proxy_count=2)
.
from python_ipware import IpWare
# Enforce proxy count
# proxy_count=0 is valid
# proxy_count=None to disable proxy_count check
ipw = IpWare(proxy_count=2)
# Example usage in non-strict mode:
# X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
# At least `proxy_count` number of proxies
ip, trusted_route = ipw.get_client_ip(meta=request.META)
# Example usage in strict mode:
# X-Forwarded-For format: <client>, <proxy1>, <proxy2>
# Exact `proxy_count` number of proxies
ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
In this example, we utilize the total number of proxies as a method to filter out unwanted requests while verifying the trust proxies.
from python_ipware import IpWare
# Enforce both proxy count and trusted proxies
ipw = IpWare(proxy_count=1, proxy_list=["198.84.193.157"])
# Example usage in non-strict mode:
# X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
# At least `proxy_count` number of proxies
ip, trusted_route = ipw.get_client_ip(meta=request.META)
# Example usage in strict mode:
# X-Forwarded-For format: <client>, <proxy1>
# Exact `proxy_count` number of proxies
ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
In the following example
, your public load balancer (LB) can be seen as the only
proxy.
`Real` Client <public> <-> <public> LB (Server) <private> <---> <private> Node Server
^
|
`Fake` Client <private> ---+
# We make best attempt to return the first public IP address based on header precedence
# Then we fall back on private, followed by loopback
from python_ipware import IpWare
# no proxy enforce in this example
ipw = IpWare()
ip, _ = ipw.get_client_ip(meta=request.META)
if ip.is_global:
print('Public IP')
else if ip.is_private:
print('Private IP')
else if ip.is_loopback:
print('Loopback IP')
else if ip.is_multicast:
print('Multicast IP')
else if ip.is_unspecified:
print('Unspecified IP')
else if ip.is_reserved:
print('Reserved IP')
python-ipware
is designed to handle various IP address formats efficiently:
- Ports Stripping: Automatically removes ports from IP addresses, ensuring only the IP is processed.
- IPv6 Unwrapping: Extracts and processes IPv4 addresses wrapped in IPv6 containers.
The de-facto standard for identifying the originating client IP address is to use the leftmost
IP in the X-Forwarded-For
header, following the pattern client, proxy1, proxy2
. Here, the rightmost
IP is considered the most trusted proxy.
In some rare scenarios, networks might be configured such that the rightmost
IP address represents the originating client. In such cases, instantiate IpWare
with the leftmost=False
parameter:
To run the tests against the current environment:
./test.sh
Released under a (MIT) license.
X.Y.Z Version
`MAJOR` version -- making incompatible API changes
`MINOR` version -- adding functionality in a backwards-compatible manner
`PATCH` version -- making backwards-compatible bug fixes
Neekware Inc. (reach out at info@neekware.com)