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
# >>>
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:
import testtools
import mock
+import netaddr
from neutron._i18n import _
from neutron.api.v2 import attributes
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
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):