commit 195f36b382fd9825269d057fde775dcce25d482b Author: Damian Johnson atagar@torproject.org Date: Sat Jan 23 14:25:14 2016 -0800
Support IPv4-mapped IPv6 addresses
Ohhh IPv6, how many quirks can you manage to cram into one spec? I know, I know, it's the new hotness but why couldn't they keep the @*!& addresses simple as its predecessor? </grumble>
Thanks to cypherpunks for pointing this out!
https://trac.torproject.org/projects/tor/ticket/18079#comment:6 --- docs/change_log.rst | 1 + stem/util/connection.py | 38 ++++++++++++++++++++++++++++++++++++++ test/unit/util/connection.py | 31 ++++++++++++++++++------------- 3 files changed, 57 insertions(+), 13 deletions(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst index 1420ddf..9c6b88b 100644 --- a/docs/change_log.rst +++ b/docs/change_log.rst @@ -74,6 +74,7 @@ The following are only available within Stem's `git repository * Added :func:`~stem.util.__init__.datetime_to_unix` * Basic IPv6 support in :func:`~stem.util.connection.get_connections` * The 'ss' connection resolver didn't work on Gentoo (:trac:`18079`) + * Recognize IPv4-mapped IPv6 addresses in our utils (:trac:`18079`) * Allow :func:`stem.util.conf.Config.set` to remove values when provided with a **None** value
* **Interpreter** diff --git a/stem/util/connection.py b/stem/util/connection.py index 0490f9c..37839cb 100644 --- a/stem/util/connection.py +++ b/stem/util/connection.py @@ -393,6 +393,22 @@ def is_valid_ipv6_address(address, allow_brackets = False): if address.startswith('[') and address.endswith(']'): address = address[1:-1]
+ if address.count('.') == 3: + # Likely an ipv4-mapped portion. Check that its vaild, then replace with a + # filler. + + ipv4_start = address.rfind(':', 0, address.find('.')) + 1 + ipv4_end = address.find(':', ipv4_start + 1) + + if ipv4_end == -1: + ipv4_end = None # don't crop the last character + + if not is_valid_ipv4_address(address[ipv4_start:ipv4_end]): + return False + + addr_comp = [address[:ipv4_start - 1] if ipv4_start != 0 else None, 'ff:ff', address[ipv4_end + 1:] if ipv4_end else None] + address = ':'.join(filter(None, addr_comp)) + # addresses are made up of eight colon separated groups of four hex digits # with leading zeros being optional # https://en.wikipedia.org/wiki/IPv6#Address_format @@ -494,6 +510,9 @@ def expand_ipv6_address(address): >>> expand_ipv6_address('::') '0000:0000:0000:0000:0000:0000:0000:0000'
+ >>> expand_ipv6_address('::ffff:5.9.158.75') + '0000:0000:0000:0000:0000:ffff:0509:9e4b' + :param str address: IPv6 address to be expanded
:raises: **ValueError** if the address can't be expanded due to being malformed @@ -502,6 +521,25 @@ def expand_ipv6_address(address): if not is_valid_ipv6_address(address): raise ValueError("'%s' isn't a valid IPv6 address" % address)
+ # expand ipv4-mapped portions of addresses + if address.count('.') == 3: + ipv4_start = address.rfind(':', 0, address.find('.')) + 1 + ipv4_end = address.find(':', ipv4_start + 1) + + if ipv4_end == -1: + ipv4_end = None # don't crop the last character + + # Converts ipv4 address to its hex ipv6 representation. For instance... + # + # '5.9.158.75' => '0509:9e4b' + + ipv4_bin = _get_address_binary(address[ipv4_start:ipv4_end]) + groupings = [ipv4_bin[16 * i:16 * (i + 1)] for i in range(2)] + ipv6_snippet = ':'.join(['%04x' % int(group, 2) for group in groupings]) + + addr_comp = [address[:ipv4_start - 1] if ipv4_start != 0 else None, ipv6_snippet, address[ipv4_end + 1:] if ipv4_end else None] + address = ':'.join(filter(None, addr_comp)) + # expands collapsed groupings, there can only be a single '::' in a valid # address if '::' in address: diff --git a/test/unit/util/connection.py b/test/unit/util/connection.py index 776182d..793bcc6 100644 --- a/test/unit/util/connection.py +++ b/test/unit/util/connection.py @@ -53,7 +53,7 @@ tcp ESTAB 0 0 127.0.0.1:22 127.0.0.1:56673 tcp ESTAB 0 0 192.168.0.1:44415 38.229.79.2:443 users:(("tor",15843,9)) """
-SS_GENTOO_OUTPUT = """\ +SS_IPV6_OUTPUT = """\ Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp ESTAB 0 0 5.9.158.75:443 107.170.93.13:56159 users:(("tor",pid=25056,fd=997)) tcp ESTAB 0 0 5.9.158.75:443 159.203.97.91:37802 users:(("tor",pid=25056,fd=77)) @@ -241,15 +241,16 @@ class TestConnection(unittest.TestCase): self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.SS, process_pid = 1111)
@patch('stem.util.system.call') - def test_get_connections_by_ss_on_gentoo(self, call_mock): + def test_get_connections_by_ss_ipv6(self, call_mock): """ - Checks the get_connections function with the ss resolver results on a - hardened Gentoo system... + Checks the get_connections function with the ss resolver results on IPv6 + conections. This also checks with the output from a hardened Gentoo system + which has subtle differences...
https://trac.torproject.org/projects/tor/ticket/18079 """
- call_mock.return_value = SS_GENTOO_OUTPUT.split('\n') + call_mock.return_value = SS_IPV6_OUTPUT.split('\n') expected = [ Connection('5.9.158.75', 443, '107.170.93.13', 56159, 'tcp'), Connection('5.9.158.75', 443, '159.203.97.91', 37802, 'tcp'), @@ -257,12 +258,7 @@ class TestConnection(unittest.TestCase): Connection('2a01:4f8:190:514a::2', 443, '2001:858:2:2:aabb:0:563b:1526', 51428, 'tcp'), ] self.assertEqual(expected, stem.util.connection.get_connections(Resolver.SS, process_pid = 25056, process_name = 'tor')) - - # TODO: This example ss output has entries like "::ffff:5.9.158.75:5222". - # What is this? Looks like a IPv6 *and* IPv4 address mashed together. Our - # resolver understandably thinks this is invalid right now. - - self.assertRaises(IOError, stem.util.connection.get_connections, Resolver.SS, process_name = 'beam') + self.assertEqual(2, len(stem.util.connection.get_connections(Resolver.SS, process_name = 'beam')))
@patch('stem.util.system.call') def test_get_connections_by_lsof(self, call_mock): @@ -399,6 +395,9 @@ class TestConnection(unittest.TestCase): 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', 'fe80:0:0:0:202:b3ff:fe1e:8329', 'fe80::202:b3ff:fe1e:8329', + '::ffff:5.9.158.75', + '5.9.158.75::ffff', + '::5.9.158.75:ffff', '::', )
@@ -406,16 +405,19 @@ class TestConnection(unittest.TestCase): 'fe80:0000:0000:0000:0202:b3ff:fe1e:829g', 'fe80:0000:0000:0000:0202:b3ff:fe1e: 8329', '2001:db8::aaaa::1', + '::ffff:5.9.158.75.12', + '::ffff:5.9.158', + '::ffff:5.9', ':::', ':', '', )
for address in valid_addresses: - self.assertTrue(stem.util.connection.is_valid_ipv6_address(address)) + self.assertTrue(stem.util.connection.is_valid_ipv6_address(address), "%s isn't a valid IPv6 address" % address)
for address in invalid_addresses: - self.assertFalse(stem.util.connection.is_valid_ipv6_address(address)) + self.assertFalse(stem.util.connection.is_valid_ipv6_address(address), '%s should be an invalid IPv6 address' % address)
def test_is_valid_port(self): """ @@ -464,6 +466,9 @@ class TestConnection(unittest.TestCase): '::': '0000:0000:0000:0000:0000:0000:0000:0000', '::1': '0000:0000:0000:0000:0000:0000:0000:0001', '1::1': '0001:0000:0000:0000:0000:0000:0000:0001', + '::ffff:5.9.158.75': '0000:0000:0000:0000:0000:ffff:0509:9e4b', + '5.9.158.75::ffff': '0509:9e4b:0000:0000:0000:0000:0000:ffff', + '::5.9.158.75:ffff': '0000:0000:0000:0000:0000:0509:9e4b:ffff', }
for test_arg, expected in test_values.items():
tor-commits@lists.torproject.org