From 5f3552e4d1f242e89e4d9c27ea17a8c08dc99d9d Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Wed, 10 May 2017 09:05:53 -0700 Subject: [PATCH] Send both gratuitous ARP REQUESTs and REPLYs Due to a linux kernel bug [1], network peers risk missing a gratuitous arp update. This can be safely worked around by enabling arp_accept for network interfaces that may receive those ARP packets [2]. Sadly, due to another kernel bug [3], gratuitous ARP packets that we send are not honoured by arp_accept. If we switch from REPLY to REQUEST packets, then all recent kernels should enforce ARP table entry override on gARP received. REPLYs are left for backwards compatibility in case some network peers honor REPLYs and not REQUESTs. [1] https://patchwork.ozlabs.org/patch/760372/ [2] https://bugs.launchpad.net/fuel/+bug/1456272 [3] https://patchwork.ozlabs.org/patch/760373/ Related-Bug: #1690165 Change-Id: Iacd01875bf1299ff68fb5d1009d72088270320bb (cherry picked from commit 82831b9d8d4bd61f90610df9eca8c7f6e447f8d8) --- neutron/agent/linux/ip_lib.py | 47 +++++++++++-------- neutron/tests/unit/agent/linux/test_ip_lib.py | 15 +++--- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py index 52841ea53a4..1849f795510 100644 --- a/neutron/agent/linux/ip_lib.py +++ b/neutron/agent/linux/ip_lib.py @@ -1051,26 +1051,33 @@ def _arping(ns_name, iface_name, address, count, log_exception): time.sleep(2) first = False - # Pass -w to set timeout to ensure exit if interface removed while - # running - arping_cmd = ['arping', '-A', '-I', iface_name, '-c', 1, - '-w', 1.5, address] - try: - ip_wrapper = IPWrapper(namespace=ns_name) - # Since arping is used to send gratuitous ARP, a response is not - # expected. In some cases (no response) and with some platforms - # (>=Ubuntu 14.04), arping exit code can be 1. - ip_wrapper.netns.execute(arping_cmd, extra_ok_codes=[1]) - except Exception as exc: - msg = _("Failed sending gratuitous ARP " - "to %(addr)s on %(iface)s in namespace %(ns)s: %(err)s") - logger_method = LOG.exception - if not log_exception: - logger_method = LOG.warning - logger_method(msg, {'addr': address, - 'iface': iface_name, - 'ns': ns_name, - 'err': exc}) + # some Linux kernels* don't honour REPLYs. Send both gratuitous REQUEST + # and REPLY packets (REQUESTs are left for backwards compatibility for + # in case if some network peers, vice versa, honor REPLYs and not + # REQUESTs) + # + # * https://patchwork.ozlabs.org/patch/763016/ + for arg in ('-U', '-A'): + arping_cmd = ['arping', arg, '-I', iface_name, '-c', 1, + # Pass -w to set timeout to ensure exit if interface + # removed while running + '-w', 1.5, address] + try: + ip_wrapper = IPWrapper(namespace=ns_name) + # Since arping is used to send gratuitous ARP, a response is + # not expected. In some cases (no response) and with some + # platforms (>=Ubuntu 14.04), arping exit code can be 1. + ip_wrapper.netns.execute(arping_cmd, extra_ok_codes=[1]) + except Exception as exc: + msg = _("Failed sending gratuitous ARP to %(addr)s on " + "%(iface)s in namespace %(ns)s: %(err)s") + logger_method = LOG.exception + if not log_exception: + logger_method = LOG.warning + logger_method(msg, {'addr': address, + 'iface': iface_name, + 'ns': ns_name, + 'err': exc}) def send_ip_addr_adv_notif( diff --git a/neutron/tests/unit/agent/linux/test_ip_lib.py b/neutron/tests/unit/agent/linux/test_ip_lib.py index e48027008c2..2a92077e597 100644 --- a/neutron/tests/unit/agent/linux/test_ip_lib.py +++ b/neutron/tests/unit/agent/linux/test_ip_lib.py @@ -1407,13 +1407,14 @@ class TestArpPing(TestIPCmdBase): ip_wrapper = mIPWrapper(namespace=mock.sentinel.ns_name) # Just test that arping is called with the right arguments - arping_cmd = ['arping', '-A', - '-I', mock.sentinel.iface_name, - '-c', 1, - '-w', mock.ANY, - address] - ip_wrapper.netns.execute.assert_any_call(arping_cmd, - extra_ok_codes=[1]) + for arg in ('-A', '-U'): + arping_cmd = ['arping', arg, + '-I', mock.sentinel.iface_name, + '-c', 1, + '-w', mock.ANY, + address] + ip_wrapper.netns.execute.assert_any_call(arping_cmd, + extra_ok_codes=[1]) @mock.patch('eventlet.spawn_n') def test_no_ipv6_addr_notif(self, spawn_n):