diff --git a/neutron/api/v2/attributes.py b/neutron/api/v2/attributes.py index 85e9b80cf19..b95f0200d9f 100644 --- a/neutron/api/v2/attributes.py +++ b/neutron/api/v2/attributes.py @@ -175,6 +175,21 @@ def _validate_mac_address_or_none(data, valid_values=None): def _validate_ip_address(data, valid_values=None): try: netaddr.IPAddress(_validate_no_whitespace(data)) + # 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 + # platform to convert addresses. For example, inet_aton(3). + # Some platforms, including NetBSD and OS X, have inet_aton + # implementation which accepts more varying forms of addresses than + # we want to accept here. The following check is to reject such + # addresses. For Example: + # >>> netaddr.IPAddress('1' * 59) + # IPAddress('199.28.113.199') + # >>> netaddr.IPAddress(str(int('1' * 59) & 0xffffffff)) + # IPAddress('199.28.113.199') + # >>> + if ':' not in data and data.count('.') != 3: + raise ValueError() except Exception: msg = _("'%s' is not a valid IP address") % data LOG.debug(msg) diff --git a/neutron/tests/unit/test_attributes.py b/neutron/tests/unit/test_attributes.py index 2707af27f9e..fae2ec1fc4d 100644 --- a/neutron/tests/unit/test_attributes.py +++ b/neutron/tests/unit/test_attributes.py @@ -15,6 +15,8 @@ import testtools +import mock + from neutron.api.v2 import attributes from neutron.common import exceptions as n_exc from neutron.tests import base @@ -204,6 +206,12 @@ class TestAttributes(base.BaseTestCase): msg = attributes._validate_ip_address(ip_addr) self.assertEqual(msg, "'%s' is not a valid IP address" % ip_addr) + # Depending on platform to run UTs, this case might or might not be + # an equivalent to test_validate_ip_address_bsd. + ip_addr = '1' * 59 + msg = attributes._validate_ip_address(ip_addr) + self.assertEqual("'%s' is not a valid IP address" % ip_addr, msg) + ip_addr = '1.1.1.1 has whitespace' msg = attributes._validate_ip_address(ip_addr) self.assertEqual(msg, "'%s' is not a valid IP address" % ip_addr) @@ -216,6 +224,18 @@ class TestAttributes(base.BaseTestCase): msg = attributes._validate_ip_address(ip_addr) self.assertEqual(msg, "'%s' is not a valid IP address" % ip_addr) + 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 + # libc behaviour there. This test ensures that our validator reject + # such addresses on such platforms by mocking netaddr to emulate + # the behaviour. + 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) + self.assertEqual("'%s' is not a valid IP address" % ip_addr, msg) + def test_validate_ip_pools(self): pools = [[{'end': '10.0.0.254'}], [{'start': '10.0.0.254'}],