History

net: arp: introduce arp_evict_nocarrier sysctl parameter

This change introduces a new sysctl parameter, arp_evict_nocarrier. When set (default) the ARP cache will be cleared on a NOCARRIER event. This new option has been defaulted to '1' which maintains existing behavior. Clearing the ARP cache on NOCARRIER is relatively new, introduced by: commit 859bd2ef1fc1110a8031b967ee656c53a6260a76 Author: David Ahern <dsahern@gmail.com> Date: Thu Oct 11 20:33:49 2018 -0700 net: Evict neighbor entries on carrier down The reason for this changes is to prevent the ARP cache from being cleared when a wireless device roams. Specifically for wireless roams the ARP cache should not be cleared because the underlying network has not changed. Clearing the ARP cache in this case can introduce significant delays sending out packets after a roam. A user reported such a situation here: https://lore.kernel.org/linux-wireless/CACsRnHWa47zpx3D1oDq9JYnZWniS8yBwW1h0WAVZ6vrbwL_S0w@mail.gmail.com/ After some investigation it was found that the kernel was holding onto packets until ARP finished which resulted in this 1 second delay. It was also found that the first ARP who-has was never responded to, which is actually what caues the delay. This change is more or less working around this behavior, but again, there is no reason to clear the cache on a roam anyways. As for the unanswered who-has, we know the packet made it OTA since it was seen while monitoring. Why it never received a response is unknown. In any case, since this is a problem on the AP side of things all that can be done is to work around it until it is solved. Some background on testing/reproducing the packet delay: Hardware: - 2 access points configured for Fast BSS Transition (Though I don't see why regular reassociation wouldn't have the same behavior) - Wireless station running IWD as supplicant - A device on network able to respond to pings (I used one of the APs) Procedure: - Connect to first AP - Ping once to establish an ARP entry - Start a tcpdump - Roam to second AP - Wait for operstate UP event, and note the timestamp - Start pinging Results: Below is the tcpdump after UP. It was recorded the interface went UP at 10:42:01.432875. 10:42:01.461871 ARP, Request who-has 192.168.254.1 tell 192.168.254.71, length 28 10:42:02.497976 ARP, Request who-has 192.168.254.1 tell 192.168.254.71, length 28 10:42:02.507162 ARP, Reply 192.168.254.1 is-at ac:86:74:55:b0:20, length 46 10:42:02.507185 IP 192.168.254.71 > 192.168.254.1: ICMP echo request, id 52792, seq 1, length 64 10:42:02.507205 IP 192.168.254.71 > 192.168.254.1: ICMP echo request, id 52792, seq 2, length 64 10:42:02.507212 IP 192.168.254.71 > 192.168.254.1: ICMP echo request, id 52792, seq 3, length 64 10:42:02.507219 IP 192.168.254.71 > 192.168.254.1: ICMP echo request, id 52792, seq 4, length 64 10:42:02.507225 IP 192.168.254.71 > 192.168.254.1: ICMP echo request, id 52792, seq 5, length 64 10:42:02.507232 IP 192.168.254.71 > 192.168.254.1: ICMP echo request, id 52792, seq 6, length 64 10:42:02.515373 IP 192.168.254.1 > 192.168.254.71: ICMP echo reply, id 52792, seq 1, length 64 10:42:02.521399 IP 192.168.254.1 > 192.168.254.71: ICMP echo reply, id 52792, seq 2, length 64 10:42:02.521612 IP 192.168.254.1 > 192.168.254.71: ICMP echo reply, id 52792, seq 3, length 64 10:42:02.521941 IP 192.168.254.1 > 192.168.254.71: ICMP echo reply, id 52792, seq 4, length 64 10:42:02.522419 IP 192.168.254.1 > 192.168.254.71: ICMP echo reply, id 52792, seq 5, length 64 10:42:02.523085 IP 192.168.254.1 > 192.168.254.71: ICMP echo reply, id 52792, seq 6, length 64 You can see the first ARP who-has went out very quickly after UP, but was never responded to. Nearly a second later the kernel retries and gets a response. Only then do the ping packets go out. If an ARP entry is manually added prior to UP (after the cache is cleared) it is seen that the first ping is never responded to, so its not only an issue with ARP but with data packets in general. As mentioned prior, the wireless interface was also monitored to verify the ping/ARP packet made it OTA which was observed to be true.

James Prestwood committed 5 years ago

Commit fcdb44d1 parent 1d6d3366 files · +27 −1Browse files

Unified · Split

6 files
  1. Documentation/networking/ip-sysctl.rst +9 −0
  2. include/linux/inetdevice.h +2 −0
  3. include/uapi/linux/ip.h +1 −0
  4. include/uapi/linux/sysctl.h +1 −0
  5. net/ipv4/arp.c +10 −1
  6. net/ipv4/devinet.c +4 −0
Documentation/networking/ip-sysctl.rst+9−0
@@ -1611,6 +1611,15 @@ arp_accept - BOOLEAN · 1610 unmodified lines
1611 gratuitous arp frame, the arp table will be updated regardless1611 gratuitous arp frame, the arp table will be updated regardless
1612 if this setting is on or off.1612 if this setting is on or off.
1613 1613
1614arp_evict_nocarrier - BOOLEAN
1615 Clears the ARP cache on NOCARRIER events. This option is important for
1616 wireless devices where the ARP cache should not be cleared when roaming
1617 between access points on the same network. In most cases this should
1618 remain as the default (1).
1619
1620 - 1 - (default): Clear the ARP cache on NOCARRIER events
1621 - 0 - Do not clear ARP cache on NOCARRIER events
1622
1614mcast_solicit - INTEGER1623mcast_solicit - INTEGER
1615 The maximum number of multicast probes in INCOMPLETE state,1624 The maximum number of multicast probes in INCOMPLETE state,
1616 when the associated hardware address is unknown. Defaults1625 when the associated hardware address is unknown. Defaults
include/linux/inetdevice.h+2−0
@@ -133,6 +133,8 @@ static inline void ipv4_devconf_setall(struct in_device *in_dev) · 132 unmodified lines
133#define IN_DEV_ARP_ANNOUNCE(in_dev) IN_DEV_MAXCONF((in_dev), ARP_ANNOUNCE)133#define IN_DEV_ARP_ANNOUNCE(in_dev) IN_DEV_MAXCONF((in_dev), ARP_ANNOUNCE)
134#define IN_DEV_ARP_IGNORE(in_dev) IN_DEV_MAXCONF((in_dev), ARP_IGNORE)134#define IN_DEV_ARP_IGNORE(in_dev) IN_DEV_MAXCONF((in_dev), ARP_IGNORE)
135#define IN_DEV_ARP_NOTIFY(in_dev) IN_DEV_MAXCONF((in_dev), ARP_NOTIFY)135#define IN_DEV_ARP_NOTIFY(in_dev) IN_DEV_MAXCONF((in_dev), ARP_NOTIFY)
136#define IN_DEV_ARP_EVICT_NOCARRIER(in_dev) IN_DEV_ANDCONF((in_dev), \
137 ARP_EVICT_NOCARRIER)
136 138
137struct in_ifaddr {139struct in_ifaddr {
138 struct hlist_node hash;140 struct hlist_node hash;
include/uapi/linux/ip.h+1−0
@@ -169,6 +169,7 @@ enum · 168 unmodified lines
169 IPV4_DEVCONF_DROP_UNICAST_IN_L2_MULTICAST,169 IPV4_DEVCONF_DROP_UNICAST_IN_L2_MULTICAST,
170 IPV4_DEVCONF_DROP_GRATUITOUS_ARP,170 IPV4_DEVCONF_DROP_GRATUITOUS_ARP,
171 IPV4_DEVCONF_BC_FORWARDING,171 IPV4_DEVCONF_BC_FORWARDING,
172 IPV4_DEVCONF_ARP_EVICT_NOCARRIER,
172 __IPV4_DEVCONF_MAX173 __IPV4_DEVCONF_MAX
173};174};
174 175
include/uapi/linux/sysctl.h+1−0
@@ -482,6 +482,7 @@ enum · 481 unmodified lines
482 NET_IPV4_CONF_PROMOTE_SECONDARIES=20,482 NET_IPV4_CONF_PROMOTE_SECONDARIES=20,
483 NET_IPV4_CONF_ARP_ACCEPT=21,483 NET_IPV4_CONF_ARP_ACCEPT=21,
484 NET_IPV4_CONF_ARP_NOTIFY=22,484 NET_IPV4_CONF_ARP_NOTIFY=22,
485 NET_IPV4_CONF_ARP_EVICT_NOCARRIER=23,
485};486};
486 487
487/* /proc/sys/net/ipv4/netfilter */488/* /proc/sys/net/ipv4/netfilter */
net/ipv4/arp.c+10−1
@@ -1247,6 +1247,8 @@ static int arp_netdev_event(struct notifier_block *this, unsigned long event, · 1246 unmodified lines
1247{1247{
1248 struct net_device *dev = netdev_notifier_info_to_dev(ptr);1248 struct net_device *dev = netdev_notifier_info_to_dev(ptr);
1249 struct netdev_notifier_change_info *change_info;1249 struct netdev_notifier_change_info *change_info;
1250 struct in_device *in_dev;
1251 bool evict_nocarrier;
1250 1252
1251 switch (event) {1253 switch (event) {
1252 case NETDEV_CHANGEADDR:1254 case NETDEV_CHANGEADDR:
@@ -1257,7 +1259,14 @@ static int arp_netdev_event(struct notifier_block *this, unsigned long event, · 4 unmodified lines
1257 change_info = ptr;1259 change_info = ptr;
1258 if (change_info->flags_changed & IFF_NOARP)1260 if (change_info->flags_changed & IFF_NOARP)
1259 neigh_changeaddr(&arp_tbl, dev);1261 neigh_changeaddr(&arp_tbl, dev);
1260 if (!netif_carrier_ok(dev))1262
1263 in_dev = __in_dev_get_rtnl(dev);
1264 if (!in_dev)
1265 evict_nocarrier = true;
1266 else
1267 evict_nocarrier = IN_DEV_ARP_EVICT_NOCARRIER(in_dev);
1268
1269 if (evict_nocarrier && !netif_carrier_ok(dev))
1261 neigh_carrier_down(&arp_tbl, dev);1270 neigh_carrier_down(&arp_tbl, dev);
1262 break;1271 break;
1263 default:1272 default:
net/ipv4/devinet.c+4−0
@@ -75,6 +75,7 @@ static struct ipv4_devconf ipv4_devconf = { · 74 unmodified lines
75 [IPV4_DEVCONF_SHARED_MEDIA - 1] = 1,75 [IPV4_DEVCONF_SHARED_MEDIA - 1] = 1,
76 [IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL - 1] = 10000 /*ms*/,76 [IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL - 1] = 10000 /*ms*/,
77 [IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL - 1] = 1000 /*ms*/,77 [IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL - 1] = 1000 /*ms*/,
78 [IPV4_DEVCONF_ARP_EVICT_NOCARRIER - 1] = 1,
78 },79 },
79};80};
80 81
@@ -87,6 +88,7 @@ static struct ipv4_devconf ipv4_devconf_dflt = { · 6 unmodified lines
87 [IPV4_DEVCONF_ACCEPT_SOURCE_ROUTE - 1] = 1,88 [IPV4_DEVCONF_ACCEPT_SOURCE_ROUTE - 1] = 1,
88 [IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL - 1] = 10000 /*ms*/,89 [IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL - 1] = 10000 /*ms*/,
89 [IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL - 1] = 1000 /*ms*/,90 [IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL - 1] = 1000 /*ms*/,
91 [IPV4_DEVCONF_ARP_EVICT_NOCARRIER - 1] = 1,
90 },92 },
91};93};
92 94
@@ -2532,6 +2534,8 @@ static struct devinet_sysctl_table { · 2439 unmodified lines
2532 DEVINET_SYSCTL_RW_ENTRY(ARP_IGNORE, "arp_ignore"),2534 DEVINET_SYSCTL_RW_ENTRY(ARP_IGNORE, "arp_ignore"),
2533 DEVINET_SYSCTL_RW_ENTRY(ARP_ACCEPT, "arp_accept"),2535 DEVINET_SYSCTL_RW_ENTRY(ARP_ACCEPT, "arp_accept"),
2534 DEVINET_SYSCTL_RW_ENTRY(ARP_NOTIFY, "arp_notify"),2536 DEVINET_SYSCTL_RW_ENTRY(ARP_NOTIFY, "arp_notify"),
2537 DEVINET_SYSCTL_RW_ENTRY(ARP_EVICT_NOCARRIER,
2538 "arp_evict_nocarrier"),
2535 DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP_PVLAN, "proxy_arp_pvlan"),2539 DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP_PVLAN, "proxy_arp_pvlan"),
2536 DEVINET_SYSCTL_RW_ENTRY(FORCE_IGMP_VERSION,2540 DEVINET_SYSCTL_RW_ENTRY(FORCE_IGMP_VERSION,
2537 "force_igmp_version"),2541 "force_igmp_version"),