Skip to content

Commit

Permalink
Added support for 1.0.0.0/8 and 2.0.0.0/8 blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
un33k committed Jun 15, 2015
1 parent 866adba commit 198c2c8
Show file tree
Hide file tree
Showing 12 changed files with 205 additions and 190 deletions.
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ python:
- pypy

env:
- DJANGO="django==1.8"
- DJANGO="django==1.7.7"
- DJANGO="django==1.8.2"
- DJANGO="django==1.7.8"
- DJANGO="django==1.4.20"

install:
Expand All @@ -34,9 +34,9 @@ matrix:
- python: "3.2"
env: DJANGO="django==1.4.20"
- python: "2.6"
env: DJANGO="django==1.7.7"
env: DJANGO="django==1.7.8"
- python: "2.6"
env: DJANGO="django==1.8"
env: DJANGO="django==1.8.2"

script: coverage run --source=ipware manage.py test

Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 1.1.0

Enhancement:

- Added support for 1.0.0.0/8 and 2.0.0.0/8 blocks

## 1.0.0

Enhancement:
Expand Down
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,22 @@ Advanced users:
# including IPWARE_PRIVATE_IP_PREFIX in your setting.py
# IPs that start with items listed below are ignored
# and are not considered a `real` IP address
IPWARE_PRIVATE_IP_PREFIX = (
'0.', '1.', '2.', # externally non-routable
'10.', # class A private block
'169.254.', # link-local block
'IPWARE_PRIVATE_IP_PREFIX', (
'0.', # externally non-routable
'10.', # class A private block
'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.2.', # reserved for documentation and example code
'192.168.', # class C private block
'255.255.255.', # IPv4 broadcast address
) + ( # the following addresses MUST be in lowercase)
'2001:db8:', # reserved for documentation and example code
'fc00:', # IPv6 private block
'fe80:', # link-local unicast
'ff00:', # IPv6 multicast
'172.28.', '172.29.', '172.30.', '172.31.', # class B private blocks
'192.0.2.', # reserved for documentation and example code
'192.168.', # class C private block
'255.255.255.', # IPv4 broadcast address
) + (
'2001:db8:', # reserved for documentation and example code
'fc00:', # IPv6 private block
'fe80:', # link-local unicast
'ff00:', # IPv6 multicast
)
```

Expand Down
2 changes: 1 addition & 1 deletion ipware/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-

__version__ = '1.0.0'
__version__ = '1.1.0'

default_app_config = 'ipware.apps.AppConfig'
15 changes: 9 additions & 6 deletions ipware/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Configurable via settings.py
IPWARE_META_PRECEDENCE_ORDER = getattr(settings,
'IPWARE_META_PRECEDENCE_ORDER', (
'HTTP_X_FORWARDED_FOR', # client, proxy1, proxy2
'HTTP_X_FORWARDED_FOR', # (client, proxy1, proxy2) OR (proxy2, proxy1, client)
'HTTP_CLIENT_IP',
'HTTP_X_REAL_IP',
'HTTP_X_FORWARDED',
Expand All @@ -18,13 +18,14 @@
)

# Private IP addresses
# http://en.wikipedia.org/wiki/List_of_assigned_/8_IPv4_address_blocks
# http://www.ietf.org/rfc/rfc3330.txt (IPv4)
# http://www.ietf.org/rfc/rfc5156.txt (IPv6)
# Regex would be ideal here, but keeping it simple
# as this is configurable via settings.py
# Regex would be ideal here, but this is keeping it simple
# as fields are configurable via settings.py
IPWARE_PRIVATE_IP_PREFIX = getattr(settings,
'IPWARE_PRIVATE_IP_PREFIX', (
'0.', '1.', '2.', # externally non-routable
'0.', # externally non-routable
'10.', # class A private block
'169.254.', # link-local block
'172.16.', '172.17.', '172.18.', '172.19.',
Expand All @@ -34,15 +35,17 @@
'192.0.2.', # reserved for documentation and example code
'192.168.', # class C private block
'255.255.255.', # IPv4 broadcast address
) + ( # the following addresses MUST be in lowercase)
) + (
'2001:db8:', # reserved for documentation and example code
'fc00:', # IPv6 private block
'fe80:', # link-local unicast
'ff00:', # IPv6 multicast
)
)

IPWARE_NON_PUBLIC_IP_PREFIX = IPWARE_PRIVATE_IP_PREFIX + (
IPWARE_LOOPBACK_PREFIX = (
'127.', # IPv4 loopback device
'::1', # IPv6 loopback device
)

IPWARE_NON_PUBLIC_IP_PREFIX = IPWARE_PRIVATE_IP_PREFIX + IPWARE_LOOPBACK_PREFIX
15 changes: 8 additions & 7 deletions ipware/ip.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@

from .utils import is_valid_ip
from .defaults import IPWARE_META_PRECEDENCE_ORDER
from .defaults import IPWARE_NON_PUBLIC_IP_PREFIX
from . import defaults as defs

NON_PUBLIC_IP_PREFIX = tuple([ip.lower() for ip in defs.IPWARE_NON_PUBLIC_IP_PREFIX])


def get_ip(request, real_ip_only=False, right_most_proxy=False):
"""
Returns client's best-matched ip-address, or None
"""
best_matched_ip = None
for key in IPWARE_META_PRECEDENCE_ORDER:
for key in defs.IPWARE_META_PRECEDENCE_ORDER:
value = request.META.get(key, '').strip()
if value != '':
ips = [ip.strip().lower() for ip in value.split(',')]
if right_most_proxy:
ips = reversed(ips)
for ip_str in ips:
if ip_str and is_valid_ip(ip_str):
if not ip_str.startswith(IPWARE_NON_PUBLIC_IP_PREFIX):
if not ip_str.startswith(NON_PUBLIC_IP_PREFIX):
return ip_str
elif not real_ip_only:
loopback = ('127.0.0.1', '::1')
if not real_ip_only:
loopback = defs.IPWARE_LOOPBACK_PREFIX
if best_matched_ip is None:
best_matched_ip = ip_str
elif best_matched_ip in loopback and ip_str not in loopback:
elif best_matched_ip.startswith(loopback) and not ip_str.startswith(loopback):
best_matched_ip = ip_str
return best_matched_ip

Expand Down
Empty file added ipware/tests/__init__.py
Empty file.
156 changes: 0 additions & 156 deletions ipware/tests.py → ipware/tests/tests_ipv4.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,159 +191,3 @@ def test_best_matched_private_ip_2(self):
}
ip = get_ip(request)
self.assertEqual(ip, "192.31.233.133")


class IPv6TestCase(TestCase):
"""IP address Test"""

def test_x_forwarded_for_multiple(self):
request = HttpRequest()
request.META = {
'HTTP_X_FORWARDED_FOR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf, 74dc::02ba',
'HTTP_X_REAL_IP': '74dc::02ba',
'REMOTE_ADDR': '74dc::02ba',
}
ip = get_real_ip(request)
self.assertEqual(ip, "3ffe:1900:4545:3:200:f8ff:fe21:67cf")

def test_x_forwarded_for_multiple_bad_address(self):
request = HttpRequest()
request.META = {
'HTTP_X_FORWARDED_FOR': 'unknown, ::1/128, 74dc::02ba',
'HTTP_X_REAL_IP': '3ffe:1900:4545:3:200:f8ff:fe21:67cf',
'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf',
}
ip = get_real_ip(request)
self.assertEqual(ip, "74dc::02ba")

def test_x_forwarded_for_singleton(self):
request = HttpRequest()
request.META = {
'HTTP_X_FORWARDED_FOR': '74dc::02ba',
'HTTP_X_REAL_IP': '3ffe:1900:4545:3:200:f8ff:fe21:67cf',
'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf',
}
ip = get_real_ip(request)
self.assertEqual(ip, "74dc::02ba")

def test_x_forwarded_for_singleton_private_address(self):
request = HttpRequest()
request.META = {
'HTTP_X_FORWARDED_FOR': '::1/128',
'HTTP_X_REAL_IP': '74dc::02ba',
'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf',
}
ip = get_real_ip(request)
self.assertEqual(ip, "74dc::02ba")

def test_bad_x_forwarded_for_fallback_on_x_real_ip(self):
request = HttpRequest()
request.META = {
'HTTP_X_FORWARDED_FOR': 'unknown ::1/128',
'HTTP_X_REAL_IP': '74dc::02ba',
'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf',
}
ip = get_real_ip(request)
self.assertEqual(ip, "74dc::02ba")

def test_empty_x_forwarded_for_fallback_on_x_real_ip(self):
request = HttpRequest()
request.META = {
'HTTP_X_FORWARDED_FOR': '',
'HTTP_X_REAL_IP': '74dc::02ba',
'REMOTE_ADDR': '3ffe:1900:4545:3:200:f8ff:fe21:67cf',
}
ip = get_real_ip(request)
self.assertEqual(ip, "74dc::02ba")

def test_empty_x_forwarded_for_empty_x_real_ip_fallback_on_remote_addr(self):
request = HttpRequest()
request.META = {
'HTTP_X_FORWARDED_FOR': '',
'HTTP_X_REAL_IP': '',
'REMOTE_ADDR': '74dc::02ba',
}
ip = get_real_ip(request)
self.assertEqual(ip, "74dc::02ba")

def test_empty_x_forwarded_for_private_x_real_ip_fallback_on_remote_addr(self):
request = HttpRequest()
request.META = {
'HTTP_X_FORWARDED_FOR': '',
'HTTP_X_REAL_IP': '::1/128',
'REMOTE_ADDR': '74dc::02ba',
}
ip = get_real_ip(request)
self.assertEqual(ip, "74dc::02ba")

def test_private_x_forward_for_ip_addr(self):
request = HttpRequest()
request.META = {
'HTTP_X_FORWARDED_FOR': '::1/128',
'HTTP_X_REAL_IP': '',
'REMOTE_ADDR': '',
}
ip = get_real_ip(request)
self.assertEqual(ip, None)

def test_private_real_ip_for_ip_addr(self):
request = HttpRequest()
request.META = {
'HTTP_X_FORWARDED_FOR': '',
'HTTP_X_REAL_IP': '::1/128',
'REMOTE_ADDR': '',
}
ip = get_real_ip(request)
self.assertEqual(ip, None)

def test_private_remote_addr_for_ip_addr(self):
request = HttpRequest()
request.META = {
'HTTP_X_FORWARDED_FOR': '',
'HTTP_X_REAL_IP': '',
'REMOTE_ADDR': '::1/128',
}
ip = get_real_ip(request)
self.assertEqual(ip, None)

def test_missing_x_forwarded(self):
request = HttpRequest()
request.META = {
'HTTP_X_REAL_IP': '74dc::02ba',
'REMOTE_ADDR': '74dc::02ba',
}
ip = get_real_ip(request)
self.assertEqual(ip, "74dc::02ba")

def test_missing_x_forwarded_missing_real_ip(self):
request = HttpRequest()
request.META = {
'REMOTE_ADDR': '74dc::02ba',
}
ip = get_real_ip(request)
self.assertEqual(ip, "74dc::02ba")

def test_missing_x_forwarded_missing_real_ip_mix_case(self):
request = HttpRequest()
request.META = {
'REMOTE_ADDR': '74DC::02BA',
}
ip = get_real_ip(request)
self.assertEqual(ip, "74dc::02ba")

def test_private_remote_address(self):
request = HttpRequest()
request.META = {
'REMOTE_ADDR': 'fe80::02ba',
}
ip = get_real_ip(request)
self.assertEqual(ip, None)

def test_best_matched_real_ip(self):
request = HttpRequest()
request.META = {
'HTTP_X_REAL_IP': '::1',
'REMOTE_ADDR': 'fe80::02ba',
}
ip = get_ip(request)
self.assertEqual(ip, "fe80::02ba")
Loading

0 comments on commit 198c2c8

Please sign in to comment.