Merge "Implement "ip neigh flush" with Pyroute2"
This commit is contained in:
commit
dcb032777f
|
@ -659,11 +659,12 @@ class IPRoute(SubProcessBase):
|
|||
class IpNeighCommand(IpDeviceCommandBase):
|
||||
COMMAND = 'neigh'
|
||||
|
||||
def add(self, ip_address, mac_address, **kwargs):
|
||||
def add(self, ip_address, mac_address, nud_state=None, **kwargs):
|
||||
add_neigh_entry(ip_address,
|
||||
mac_address,
|
||||
self.name,
|
||||
self._parent.namespace,
|
||||
namespace=self._parent.namespace,
|
||||
nud_state=nud_state,
|
||||
**kwargs)
|
||||
|
||||
def delete(self, ip_address, mac_address, **kwargs):
|
||||
|
@ -685,11 +686,20 @@ class IpNeighCommand(IpDeviceCommandBase):
|
|||
Given address entry is removed from neighbour cache (ARP or NDP). To
|
||||
flush all entries pass string 'all' as an address.
|
||||
|
||||
From https://man.archlinux.org/man/core/iproute2/ip-neighbour.8.en:
|
||||
"the default neighbour states to be flushed do not include permanent
|
||||
and noarp".
|
||||
|
||||
:param ip_version: Either 4 or 6 for IPv4 or IPv6 respectively
|
||||
:param ip_address: The prefix selecting the neighbours to flush
|
||||
:param ip_address: The prefix selecting the neighbours to flush or
|
||||
"all"
|
||||
"""
|
||||
# NOTE(haleyb): There is no equivalent to 'flush' in pyroute2
|
||||
self._as_root([ip_version], ('flush', 'to', ip_address))
|
||||
cidr = netaddr.IPNetwork(ip_address) if ip_address != 'all' else None
|
||||
for entry in self.dump(ip_version):
|
||||
if entry['state'] in ('permanent', 'noarp'):
|
||||
continue
|
||||
if ip_address == 'all' or entry['dst'] in cidr:
|
||||
self.delete(entry['dst'], entry['lladdr'])
|
||||
|
||||
|
||||
class IpNetnsCommand(IpCommandBase):
|
||||
|
@ -843,20 +853,25 @@ def get_routing_table(ip_version, namespace=None):
|
|||
|
||||
# NOTE(haleyb): These neighbour functions live outside the IpNeighCommand
|
||||
# class since not all callers require it.
|
||||
def add_neigh_entry(ip_address, mac_address, device, namespace=None, **kwargs):
|
||||
def add_neigh_entry(ip_address, mac_address, device, namespace=None,
|
||||
nud_state=None, **kwargs):
|
||||
"""Add a neighbour entry.
|
||||
|
||||
:param ip_address: IP address of entry to add
|
||||
:param mac_address: MAC address of entry to add
|
||||
:param device: Device name to use in adding entry
|
||||
:param namespace: The name of the namespace in which to add the entry
|
||||
:param nud_state: The NUD (Neighbour Unreachability Detection) state of
|
||||
the entry; defaults to "permanent"
|
||||
"""
|
||||
ip_version = common_utils.get_ip_version(ip_address)
|
||||
nud_state = nud_state or 'permanent'
|
||||
privileged.add_neigh_entry(ip_version,
|
||||
ip_address,
|
||||
mac_address,
|
||||
device,
|
||||
namespace,
|
||||
nud_state,
|
||||
**kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ _IP_VERSION_FAMILY_MAP = {4: socket.AF_INET, 6: socket.AF_INET6}
|
|||
|
||||
NETNS_RUN_DIR = '/var/run/netns'
|
||||
|
||||
NUD_STATES = {state[1]: state[0] for state in ndmsg.states.items()}
|
||||
|
||||
|
||||
def _get_scope_name(scope):
|
||||
"""Return the name of the scope (given as a number), or the scope number
|
||||
|
@ -458,13 +460,15 @@ def get_link_vfs(device, namespace):
|
|||
|
||||
@privileged.default.entrypoint
|
||||
def add_neigh_entry(ip_version, ip_address, mac_address, device, namespace,
|
||||
**kwargs):
|
||||
nud_state, **kwargs):
|
||||
"""Add a neighbour entry.
|
||||
|
||||
:param ip_address: IP address of entry to add
|
||||
:param mac_address: MAC address of entry to add
|
||||
:param device: Device name to use in adding entry
|
||||
:param namespace: The name of the namespace in which to add the entry
|
||||
:param nud_state: The NUD (Neighbour Unreachability Detection) state of
|
||||
the entry
|
||||
"""
|
||||
family = _IP_VERSION_FAMILY_MAP[ip_version]
|
||||
_run_iproute_neigh('replace',
|
||||
|
@ -473,7 +477,7 @@ def add_neigh_entry(ip_version, ip_address, mac_address, device, namespace,
|
|||
dst=ip_address,
|
||||
lladdr=mac_address,
|
||||
family=family,
|
||||
state=ndmsg.states['permanent'],
|
||||
state=ndmsg.states[nud_state],
|
||||
**kwargs)
|
||||
|
||||
|
||||
|
@ -526,9 +530,10 @@ def dump_neigh_entries(ip_version, device, namespace, **kwargs):
|
|||
|
||||
for entry in dump:
|
||||
attrs = dict(entry['attrs'])
|
||||
entries += [{'dst': attrs['NDA_DST'],
|
||||
'lladdr': attrs.get('NDA_LLADDR'),
|
||||
'device': device}]
|
||||
entries.append({'dst': attrs['NDA_DST'],
|
||||
'lladdr': attrs.get('NDA_LLADDR'),
|
||||
'device': device,
|
||||
'state': NUD_STATES[entry['state']]})
|
||||
return entries
|
||||
|
||||
|
||||
|
|
|
@ -46,6 +46,12 @@ WRONG_IP = '0.0.0.0'
|
|||
TEST_IP = '240.0.0.1'
|
||||
TEST_IP_NEIGH = '240.0.0.2'
|
||||
TEST_IP_SECONDARY = '240.0.0.3'
|
||||
TEST_IP6_NEIGH = 'fd00::2'
|
||||
TEST_IP6_SECONDARY = 'fd00::3'
|
||||
TEST_IP_NUD_STATES = ((TEST_IP_NEIGH, 'permanent'),
|
||||
(TEST_IP_SECONDARY, 'reachable'),
|
||||
(TEST_IP6_NEIGH, 'permanent'),
|
||||
(TEST_IP6_SECONDARY, 'reachable'))
|
||||
|
||||
|
||||
class IpLibTestFramework(functional_base.BaseSudoTestCase):
|
||||
|
@ -416,7 +422,8 @@ class IpLibTestCase(IpLibTestFramework):
|
|||
|
||||
expected_neighs = [{'dst': TEST_IP_NEIGH,
|
||||
'lladdr': mac_address,
|
||||
'device': attr.name}]
|
||||
'device': attr.name,
|
||||
'state': 'permanent'}]
|
||||
|
||||
neighs = device.neigh.dump(4)
|
||||
self.assertItemsEqual(expected_neighs, neighs)
|
||||
|
@ -449,6 +456,41 @@ class IpLibTestCase(IpLibTestFramework):
|
|||
# trying to delete a non-existent entry shouldn't raise an error
|
||||
device.neigh.delete(TEST_IP_NEIGH, mac_address)
|
||||
|
||||
def test_flush_neigh_ipv4(self):
|
||||
# Entry with state "reachable" deleted.
|
||||
self._flush_neigh(constants.IP_VERSION_4, TEST_IP_SECONDARY,
|
||||
{TEST_IP_NEIGH})
|
||||
# Entries belong to "ip_to_flush" passed CIDR, but "permanent" entry
|
||||
# is not deleted.
|
||||
self._flush_neigh(constants.IP_VERSION_4, '240.0.0.0/28',
|
||||
{TEST_IP_NEIGH})
|
||||
# "all" passed, but "permanent" entry is not deleted.
|
||||
self._flush_neigh(constants.IP_VERSION_4, 'all', {TEST_IP_NEIGH})
|
||||
|
||||
def test_flush_neigh_ipv6(self):
|
||||
# Entry with state "reachable" deleted.
|
||||
self._flush_neigh(constants.IP_VERSION_6, TEST_IP6_SECONDARY,
|
||||
{TEST_IP6_NEIGH})
|
||||
# Entries belong to "ip_to_flush" passed CIDR, but "permanent" entry
|
||||
# is not deleted.
|
||||
self._flush_neigh(constants.IP_VERSION_6, 'fd00::0/64',
|
||||
{TEST_IP6_NEIGH})
|
||||
# "all" passed, but "permanent" entry is not deleted.
|
||||
self._flush_neigh(constants.IP_VERSION_6, 'all', {TEST_IP6_NEIGH})
|
||||
|
||||
def _flush_neigh(self, version, ip_to_flush, ips_expected):
|
||||
attr = self.generate_device_details(
|
||||
ip_cidrs=['%s/24' % TEST_IP, 'fd00::1/64'],
|
||||
namespace=utils.get_rand_name(20, 'ns-'))
|
||||
device = self.manage_device(attr)
|
||||
for test_ip, nud_state in TEST_IP_NUD_STATES:
|
||||
mac_address = net.get_random_mac('fa:16:3e:00:00:00'.split(':'))
|
||||
device.neigh.add(test_ip, mac_address, nud_state)
|
||||
|
||||
device.neigh.flush(version, ip_to_flush)
|
||||
ips = {e['dst'] for e in device.neigh.dump(version)}
|
||||
self.assertEqual(ips_expected, ips)
|
||||
|
||||
def _check_for_device_name(self, ip, name, should_exist):
|
||||
exist = any(d for d in ip.get_devices() if d.name == name)
|
||||
self.assertEqual(should_exist, exist)
|
||||
|
|
|
@ -551,11 +551,6 @@ class TestIPCmdBase(base.BaseTestCase):
|
|||
self.parent._run.assert_has_calls([
|
||||
mock.call(options, self.command, args)])
|
||||
|
||||
def _assert_sudo(self, options, args, use_root_namespace=False):
|
||||
self.parent._as_root.assert_has_calls(
|
||||
[mock.call(options, self.command, args,
|
||||
use_root_namespace=use_root_namespace)])
|
||||
|
||||
|
||||
class TestIpRuleCommand(TestIPCmdBase):
|
||||
def setUp(self):
|
||||
|
@ -1367,8 +1362,15 @@ class TestIpNeighCommand(TestIPCmdBase):
|
|||
ifindex=1)
|
||||
|
||||
def test_flush(self):
|
||||
self.neigh_cmd.flush(4, '192.168.0.1')
|
||||
self._assert_sudo([4], ('flush', 'to', '192.168.0.1'))
|
||||
with mock.patch.object(self.neigh_cmd, 'dump') as mock_dump, \
|
||||
mock.patch.object(self.neigh_cmd, 'delete') as mock_delete:
|
||||
mock_dump.return_value = (
|
||||
{'state': 'permanent', 'dst': '1.2.3.4', 'lladdr': 'mac_1'},
|
||||
{'state': 'reachable', 'dst': '1.2.3.5', 'lladdr': 'mac_2'})
|
||||
self.neigh_cmd.flush(4, '1.2.3.4')
|
||||
mock_delete.assert_not_called()
|
||||
self.neigh_cmd.flush(4, '1.2.3.5')
|
||||
mock_delete.assert_called_once_with('1.2.3.5', 'mac_2')
|
||||
|
||||
|
||||
class TestArpPing(TestIPCmdBase):
|
||||
|
|
Loading…
Reference in New Issue