attributes: Additional IP address validation

Introduce an additional IP address validation instead of assuming
that netaddr provides it.  Namely, it ensures that an address
either has ':' (IPv6) or 3 periods like 'xx.xx.xx.xx'. (IPv4)

The "'1' * 59" test case recently introduced by
commit 1681f62ec9 fails on
some platforms because it's considered a valid address by
their inet_aton.  Examples of such platforms: NetBSD, OS X

While one might argue it's a fault of the platforms, this is
a historical behavior which is probably too late to change there.

(The breakage has been hidden by later UT changes in
commit 35662d0762 .
This commit includes a UT change to uncover the problem again.)

(cherry-picked from 2794bb89d6)
Closes-Bug: #1394867
Related-Bug: #1378450
Change-Id: Ibe02f8b7c4d437bf7abbe7304ca138bdcf4bfdb9
This commit is contained in:
YAMAMOTO Takashi 2014-11-21 14:16:03 +09:00 committed by Stephen Ma
parent 91a4593346
commit d329c221c3
2 changed files with 35 additions and 0 deletions

View File

@ -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)

View File

@ -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'}],