Skip to content

Commit

Permalink
use python-ipware
Browse files Browse the repository at this point in the history
  • Loading branch information
un33k committed Nov 22, 2023
1 parent e0aaacd commit 86cb317
Show file tree
Hide file tree
Showing 12 changed files with 71 additions and 897 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"python.linting.enabled": false,
"cSpell.words": [
"cmdclass",
"Fastly",
"getattr",
"ipware",
"multicast",
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# 6.0.0

Enhancement: (breaking changes)

- Use python-ipware under the hood
- Minor behavior changes as python-ipware is more accurate

# 5.0.2

Enhancement:
Expand Down
94 changes: 24 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@

# Alternative package

If you prefer a python only version that does not integrate with Django, you can use the [python-ipware](https://github.com/un33k/python-ipware) package instead. `django-ipware` will use python-ipware in the future.
If you prefer a python only version that does not integrate with Django directly, but allows for more flexibility and advanced features, you can use the [python-ipware](https://github.com/un33k/python-ipware) package instead. `django-ipware` is now a wrapper using python-ipware underneath.

# Overview

**Best attempt** to get client's IP address while keeping it **DRY**.

# Alternative package

If you prefer a python version, you can use the [python-ipware](https://github.com/un33k/python-ipware) package instead. `python-ipware` is a newer package, with more advanced features. While this a Django specific package, `python-ipware` can be used with Django, Flask, etc.

# Notice

There is no perfect `out-of-the-box` solution against fake IP addresses, aka `IP Address Spoofing`.
Expand Down Expand Up @@ -56,30 +52,39 @@ Please use ipware `ONLY` as a complement to your `firewall` security measures!
# The client's IP address is publicly routable on the Internet
else:
# The client's IP address is private

# Order of precedence is (Public, Private, Loopback, None)
```

# Advanced users:

- ### Precedence Order

The default meta precedence order is top to bottom. However, you may customize the order
The default meta precedence order is top to bottom. You may customize the order
by providing your own `IPWARE_META_PRECEDENCE_ORDER` by adding it to your project's settings.py

```python
# The default meta precedence order
# The default meta precedence order (update as needed)
IPWARE_META_PRECEDENCE_ORDER = (
'HTTP_X_FORWARDED_FOR', 'X_FORWARDED_FOR', # <client>, <proxy1>, <proxy2>
'HTTP_CLIENT_IP',
'HTTP_X_REAL_IP',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'HTTP_VIA',
'REMOTE_ADDR',
)
"X_FORWARDED_FOR", # 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_VIA", # Squid and others
"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
)
```

**Alternatively**, you can provide your custom _request header meta precedence order_ when calling `get_client_ip()`.
Expand All @@ -89,57 +94,6 @@ get_client_ip(request, request_header_order=['X_FORWARDED_FOR'])
get_client_ip(request, request_header_order=['X_FORWARDED_FOR', 'HTTP_X_FORWARDED_FOR'])
```

### Private Prefixes

You may customize the prefixes to indicate an IP address is private. This is done by adding your
own `IPWARE_PRIVATE_IP_PREFIX` to your project's settings.py. IP addresses matching the following
prefixes are considered _private_ & are **not** publicly routable.

```python
# The default private IP prefixes
IPWARE_PRIVATE_IP_PREFIX = getattr(settings,
'IPWARE_PRIVATE_IP_PREFIX', (
'0.', # messages to software
'10.', # class A private block
'100.64.', '100.65.', '100.66.', '100.67.', '100.68.', '100.69.',
'100.70.', '100.71.', '100.72.', '100.73.', '100.74.', '100.75.',
'100.76.', '100.77.', '100.78.', '100.79.', '100.80.', '100.81.',
'100.82.', '100.83.', '100.84.', '100.85.', '100.86.', '100.87.',
'100.88.', '100.89.', '100.90.', '100.91.', '100.92.', '100.93.',
'100.94.', '100.95.', '100.96.', '100.97.', '100.98.', '100.99.',
'100.100.', '100.101.', '100.102.', '100.103.', '100.104.', '100.105.',
'100.106.', '100.107.', '100.108.', '100.109.', '100.110.', '100.111.',
'100.112.', '100.113.', '100.114.', '100.115.', '100.116.', '100.117.',
'100.118.', '100.119.', '100.120.', '100.121.', '100.122.', '100.123.',
'100.124.', '100.125.', '100.126.', '100.127.', # carrier-grade NAT
'169.254.', # link-local block
'172.16.', '172.17.', '172.18.', '172.19.',
'172.20.', '172.21.', '172.22.', '172.23.',
'172.24.', '172.25.', '172.26.', '172.27.',
'172.28.', '172.29.', '172.30.', '172.31.', # class B private blocks
'192.0.0.', # reserved for IANA special purpose address registry
'192.0.2.', # reserved for documentation and example code
'192.168.', # class C private block
'198.18.', '198.19.', # reserved for inter-network communications between two separate subnets
'198.51.100.', # reserved for documentation and example code
'203.0.113.', # reserved for documentation and example code
'224.', '225.', '226.', '227.', '228.', '229.', '230.', '231.', '232.',
'233.', '234.', '235.', '236.', '237.', '238.', '239.', # multicast
'240.', '241.', '242.', '243.', '244.', '245.', '246.', '247.', '248.',
'249.', '250.', '251.', '252.', '253.', '254.', '255.', # reserved
'::', # Unspecified address
'::ffff:', '2001:10:', '2001:20:', # messages to software
'2001::', # TEREDO
'2001:2::', # benchmarking
'2001:db8:', # reserved for documentation and example code
'fc', # IPv6 ULA (RFC4193) - NOTE: Reserved for future use, not currently in widespread use.
'fd', # IPv6 ULA (RFC4193) - Mainly used for private network addressing
'fe80:', # link-local unicast
'ff00:', # IPv6 multicast
)
)
```

### Trusted Proxies

If your Django server is behind one or more known proxy server(s), you can filter out unwanted requests
Expand Down
2 changes: 1 addition & 1 deletion ipware/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
__url__ = 'https://github.com/un33k/django-ipware'
__license__ = 'MIT'
__copyright__ = 'Copyright 2020 Val Neekman @ Neekware Inc.'
__version__ = '5.0.2'
__version__ = '6.0.0'
78 changes: 0 additions & 78 deletions ipware/defaults.py

This file was deleted.

64 changes: 17 additions & 47 deletions ipware/ip.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from . import defaults as defs
from . import utils as util

from django.conf import settings
from python_ipware import IpWare

def get_client_ip(
request,
Expand All @@ -9,53 +8,24 @@ def get_client_ip(
proxy_trusted_ips=None,
request_header_order=None,
):
client_ip = None
routable = False

if proxy_count is None:
proxy_count = -1

if proxy_trusted_ips is None:
proxy_trusted_ips = []

if request_header_order is None:
request_header_order = defs.IPWARE_META_PRECEDENCE_ORDER
leftmost = True if proxy_order == 'left-most' else False
proxy_count = proxy_count if proxy_count is not None else 0
proxy_list = proxy_trusted_ips if proxy_trusted_ips is not None else []
request_header_order = request_header_order if request_header_order is not None else getattr(settings, 'IPWARE_META_PRECEDENCE_ORDER', None)

for key in request_header_order:
value = util.get_request_meta(request, key)
if value:
ips, ip_count = util.get_ips_from_string(value)
# Instantiate IpWare with values from the function arguments
ipw = IpWare(precedence=request_header_order,
leftmost=leftmost,
proxy_count=proxy_count,
proxy_list=proxy_list)

if ip_count < 1:
# we are expecting at least one IP address to process
continue
ip, _ = ipw.get_client_ip(request.META, True)

if proxy_count == 0 and ip_count > 1:
# we are not expecting requests via any proxies
continue

if proxy_count > 0 and proxy_count != ip_count - 1:
# we are expecting requests via `proxy_count` number of proxies
continue

if proxy_trusted_ips and ip_count < 2:
# we are expecting requests via at least one trusted proxy
continue

if proxy_order == 'right-most' and ip_count > 1:
# we are expecting requests via proxies to be custom as per `<proxy2>, <proxy1>, <client>`
ips.reverse()
client_ip = None
routable = False

if proxy_trusted_ips:
for proxy in proxy_trusted_ips:
# right most proxy is the most reliable proxy that talks to the django server
if ips[-1].startswith(proxy):
client_ip, routable = util.get_ip_info(ips[0])
if client_ip and routable:
return client_ip, routable
else:
client_ip, routable = util.get_ip_info(util.get_best_ip(client_ip, ips[0]))
if client_ip and routable:
return client_ip, routable
if ip:
client_ip = str(ip)
routable = ip.is_global

return client_ip, routable
Loading

0 comments on commit 86cb317

Please sign in to comment.