Package: python-ipware
Python IPware (A Python Package)A python package for server applications to retrieve client's IP addressOverviewBest attemptto get client's IP address while keeping itDRY.NoticeAddressing IP Address SpoofingThere is no perfectout-of-the-boxsolution to counteract fake IP addresses, or IP Address Spoofing. We strongly recommend reading theAdvanced Userssection. Utilize theproxy_listandproxy_countfeatures to adapt the functionality to your specific requirements, especially if you plan to incorporatepython-ipwareinto authentication, security, or anti-fraud systems.Open Source ConsiderationsKeep in mind thatpython-ipwareis 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.Complementary Security MeasureUsepython-ipwareonlyas 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.How to installpip install python-ipware-- or --pip3 install python-ipwareHow to useUsing python-ipware to Retrieve Client IP in Django or FlaskHere's a basic example of how to usepython-ipwarein a view or middleware where therequestobject is available. This can be applied in Django, Flask, or other similar frameworks.frompython_ipwareimportIpWare# Instantiate IpWare with default valuesipw=IpWare()# Get the META data from the request objectmeta=request.META# Django# meta = request.environ # Flask# Get the client IP and the trusted route flagip,trusted_route=ipw.get_client_ip(meta)ifip:# 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 addressiftrusted_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.Advanced users:Params ⇩⇩ Descriptionproxy_count⇨: Total number of expected proxies (pattern:client, proxy1, ..., proxy2): ifproxy_count = 0thenclient: ifproxy_count = 1thenclient, proxy1: ifproxy_count = 2thenclient, proxy1, proxy2: ifproxy_count = 3thenclient, proxy1, proxy2 proxy3proxy_list⇨: List of trusted proxies (ip header pattern:client, proxy1, ,..., proxyN): ifproxy_list = ['10.1.']thenclient, proxy1: ifproxy_list = ['10.1', '10.2.3']thenclient, proxy1 proxy2: ifproxy_list = ['10.1', '10.2.', '10.3.4.4']thenclient, proxy1, proxy2, proxy3leftmost⇨:leftmost = Trueis default for de-facto standard.:leftmost = Falsefor rare legacy networks that are configured with therightmostpattern.: It convertsclient, proxy1 proxy2toproxy2, proxy1, clientOutput ⇩⇩ Descriptionip⇨: Client IP address object of type IPv4Address() or IPv6Address()trusted_route⇨: If proxyproxy_countand/orproxy_listwere provided and matched,True, elseFalsePrecedence OrderThe 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 listrequest_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"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 callingIpWare().# specific meta keyipw=IpWare(precedence=("X_FORWARDED_FOR"))# multiple meta keysipw=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.Trusted ProxiesIf your node server is behind one or more known proxy server(s), you can filter out unwanted requests
by providing atrusted proxy list, or a known proxycount.You can customize the proxy IP prefixes by providing your own list during initialization when callingIpWare(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 listipw=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 ipsip,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 followingexample, your public load balancer (LB) can be seen as atrustedproxy.`Real` Client <public> <-> <public> LB (Server) <private> <-----> <private> Django Server
                                                             ^
                                                             |
`Fake` Client <private> <-> <private> LB (Server) <private> -+Proxy CountIf your python server is behind aknownnumber 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 proxycount.You can customize the proxy count by providing yourproxy_countduring initialization when callingIpWare(proxy_count=2).# In the above scenario, the total number of proxies can be used as a way to filter out unwanted requests.frompython_ipwareimportIpWare# enforce proxy countipw=IpWare(proxy_count=1)# enforce proxy count and trusted proxiesipw=IpWare(proxy_count=1,proxy_list=["198.84.193.157"])# usage: non-strict mode (X-Forwarded-For: <fake>, <client>, <proxy1>, <proxy2>)# total number of ip addresses are greater than the total countip,trusted_route=ipw.get_client_ip(meta=request.META)# usage: strict mode (X-Forwarded-For: <client>, <proxy1>, <proxy2>)# total number of ip addresses are exactly equal to client ip + proxy_countip,trusted_route=ipw.get_client_ip(meta=request.META,strict=True)In the followingexample, your public load balancer (LB) can be seen as theonlyproxy.`Real` Client <public> <-> <public> LB (Server) <private> <---> <private> Node Server
                                                            ^
                                                            |
                                `Fake` Client  <private> ---+Support for Public IP Address (routable on the internet), Private and Loopback# We make best attempt to return the first public IP address based on header precedence# Then we fall back on private, followed by loopbackfrompython_ipwareimportIpWare# no proxy enforce in this exampleipw=IpWare()ip,_=ipw.get_client_ip(meta=request.META)ifip.is_global:print('Public IP')elseifip.is_private:print('Private IP')elseifip.is_loopback:print('Loopback IP')elseifip.is_multicast:print('Multicast IP')elseifip.is_unspecified:print('Unspecified IP')elseifip.is_reserved:print('Reserved IP')IP Address HandlingSupport for IPv4, IPv6, and IP:Port Patternspython-ipwareis 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.Identifying the Originating IP AddressThede-facto standardfor identifying the originating client IP address is to use theleftmostIP in theX-Forwarded-Forheader, following the patternclient, proxy1, proxy2. Here, therightmostIP is considered the most trusted proxy.Custom Network ConfigurationsIn some rare scenarios, networks might be configured such that therightmostIP address represents the originating client. In such cases, instantiateIpWarewith theleftmost=Falseparameter:Running the testsTo run the tests against the current environment:./test.shLicenseReleased under a (MIT) license.VersionX.Y.Z Version`MAJOR` version -- making incompatible API changes
`MINOR` version -- adding functionality in a backwards-compatible manner
`PATCH` version -- making backwards-compatible bug fixesSponsorsNeekware Inc.Need Support?Neekware Inc.(reach out atinfo@neekware.com)