From d515a8db880388092b61f4649f8dd0b29a2c5de3 Mon Sep 17 00:00:00 2001 From: Bo Chi Date: Thu, 10 Dec 2015 08:42:26 -0500 Subject: [PATCH] reject leading '0's in IPv4 addr to avoid ambiguity If a IPv4 address has a leading '0', it will be interpreted as an octal number, e.g. 10.0.0.011 will be interpreted as 10.0.0.9. Users who are not familiar with or not expecting octal interpretation will not get the address they intended. Since there is no standard around this, we reject leading '0's to avoid ambiguity. APIImpact Change-Id: I3163ba13468c47d385585221d2167fbe31d24010 Closes-Bug: #1524220 --- neutron/api/v2/attributes.py | 12 ++++++++- neutron/tests/unit/api/v2/test_attributes.py | 26 +++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/neutron/api/v2/attributes.py b/neutron/api/v2/attributes.py index e7b5ce592..af4a69ba4 100644 --- a/neutron/api/v2/attributes.py +++ b/neutron/api/v2/attributes.py @@ -206,7 +206,10 @@ def _validate_mac_address_or_none(data, valid_values=None): def _validate_ip_address(data, valid_values=None): msg = None try: - netaddr.IPAddress(_validate_no_whitespace(data)) + # netaddr.core.ZEROFILL is only applicable to IPv4. + # it will remove leading zeros from IPv4 address octets. + ip = netaddr.IPAddress(_validate_no_whitespace(data), + flags=netaddr.core.ZEROFILL) # The followings are quick checks for IPv6 (has ':') and # IPv4. (has 3 periods like 'xx.xx.xx.xx') # NOTE(yamamoto): netaddr uses libraries provided by the underlying @@ -222,6 +225,13 @@ def _validate_ip_address(data, valid_values=None): # >>> if ':' not in data and data.count('.') != 3: msg = _("'%s' is not a valid IP address") % data + # A leading '0' in IPv4 address may be interpreted as an octal number, + # e.g. 011 octal is 9 decimal. Since there is no standard saying + # whether IP address with leading '0's should be interpreted as octal + # or decimal, hence we reject leading '0's to avoid ambiguity. + if ip.version == 4 and str(ip) != data: + msg = _("'%(data)s' is not an accepted IP address, " + "'%(ip)s' is recommended") % {"data": data, "ip": ip} except Exception: msg = _("'%s' is not a valid IP address") % data if msg: diff --git a/neutron/tests/unit/api/v2/test_attributes.py b/neutron/tests/unit/api/v2/test_attributes.py index 4401566f6..57dda2523 100644 --- a/neutron/tests/unit/api/v2/test_attributes.py +++ b/neutron/tests/unit/api/v2/test_attributes.py @@ -17,6 +17,7 @@ import string import testtools import mock +import netaddr from neutron._i18n import _ from neutron.api.v2 import attributes @@ -270,6 +271,28 @@ class TestAttributes(base.BaseTestCase): msg = attributes._validate_ip_address(ip_addr) self.assertEqual("'%s' is not a valid IP address" % ip_addr, msg) + def test_validate_ip_address_with_leading_zero(self): + ip_addr = '1.1.1.01' + expected_msg = ("'%(data)s' is not an accepted IP address, " + "'%(ip)s' is recommended") + msg = attributes._validate_ip_address(ip_addr) + self.assertEqual(expected_msg % {"data": ip_addr, "ip": '1.1.1.1'}, + msg) + + ip_addr = '1.1.1.011' + msg = attributes._validate_ip_address(ip_addr) + self.assertEqual(expected_msg % {"data": ip_addr, "ip": '1.1.1.11'}, + msg) + + ip_addr = '1.1.1.09' + msg = attributes._validate_ip_address(ip_addr) + self.assertEqual(expected_msg % {"data": ip_addr, "ip": '1.1.1.9'}, + msg) + + ip_addr = "fe80:0:0:0:0:0:0:0001" + msg = attributes._validate_ip_address(ip_addr) + self.assertIsNone(msg) + def test_validate_ip_address_bsd(self): # NOTE(yamamoto): On NetBSD and OS X, netaddr.IPAddress() accepts # '1' * 59 as a valid address. The behaviour is inherited from @@ -279,7 +302,8 @@ class TestAttributes(base.BaseTestCase): ip_addr = '1' * 59 with mock.patch('netaddr.IPAddress') as ip_address_cls: msg = attributes._validate_ip_address(ip_addr) - ip_address_cls.assert_called_once_with(ip_addr) + ip_address_cls.assert_called_once_with(ip_addr, + flags=netaddr.core.ZEROFILL) self.assertEqual("'%s' is not a valid IP address" % ip_addr, msg) def test_validate_ip_pools(self): -- 2.45.2