[PATCH net-next 1/3] net: ipv4: new arp_accept option to accept garp only if in-network

Jaehee Park
2022-07-11T17:51:27Z

In many deployments, we want the option to not learn a neighbor from
garp if the src ip is not in the subnet of addresses configured on the
interface. net.ipv4.arp_accept sysctl is currently used to control
creation of a neigh from a received garp packet. This patch adds a
new option '2' to net.ipv4.arp_accept which extends option '1' by
including the subnet check.

Signed-off-by: Jaehee Park <jhpark1013@gmail.com>
Suggested-by: Roopa Prabhu <roopa@nvidia.com>
---
Documentation/networking/ip-sysctl.rst | 4 +++-
include/linux/inetdevice.h | 2 +-
net/ipv4/arp.c | 24 ++++++++++++++++++++++--
3 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst
index 4c8bbf5acfd1..599373601a2b 100644
--- a/Documentation/networking/ip-sysctl.rst
+++ b/Documentation/networking/ip-sysctl.rst
@@ -1633,12 +1633,14 @@ arp_notify - BOOLEAN
or hardware address changes.
== ==========================================================

-arp_accept - BOOLEAN
+arp_accept - INTEGER
Define behavior for gratuitous ARP frames who's IP is not
already present in the ARP table:

- 0 - don't create new entries in the ARP table
- 1 - create new entries in the ARP table
+ - 2 - create new entries only if src ip is in the same subnet as
+ the configured address on the received interface

Both replies and requests type gratuitous arp will trigger the
ARP table to be updated, if this setting is on.
diff --git a/include/linux/inetdevice.h b/include/linux/inetdevice.h
index ead323243e7b..ddb27fc0ee8c 100644
--- a/include/linux/inetdevice.h
+++ b/include/linux/inetdevice.h
@@ -131,7 +131,7 @@ static inline void ipv4_devconf_setall(struct in_device *in_dev)
IN_DEV_ORCONF((in_dev), IGNORE_ROUTES_WITH_LINKDOWN)

#define IN_DEV_ARPFILTER(in_dev) IN_DEV_ORCONF((in_dev), ARPFILTER)
-#define IN_DEV_ARP_ACCEPT(in_dev) IN_DEV_ORCONF((in_dev), ARP_ACCEPT)
+#define IN_DEV_ARP_ACCEPT(in_dev) IN_DEV_MAXCONF((in_dev), ARP_ACCEPT)
#define IN_DEV_ARP_ANNOUNCE(in_dev) IN_DEV_MAXCONF((in_dev), ARP_ANNOUNCE)
#define IN_DEV_ARP_IGNORE(in_dev) IN_DEV_MAXCONF((in_dev), ARP_IGNORE)
#define IN_DEV_ARP_NOTIFY(in_dev) IN_DEV_MAXCONF((in_dev), ARP_NOTIFY)
diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c
index af2f12ffc9ca..5eedb042c50b 100644
--- a/net/ipv4/arp.c
+++ b/net/ipv4/arp.c
@@ -429,6 +429,26 @@ static int arp_ignore(struct in_device *in_dev, __be32 sip, __be32 tip)
return !inet_confirm_addr(net, in_dev, sip, tip, scope);
}

+static int arp_accept(struct in_device *in_dev, __be32 sip)
+{
+ struct net *net = dev_net(in_dev->dev);
+ int scope = RT_SCOPE_LINK;
+
+ switch (IN_DEV_ARP_ACCEPT(in_dev)) {
+ case 0: /* don't create new entries from garp */
+ return 0;
+ case 1: /* create new entries from garp */
+ return 1;
+ case 2: /*
+ * create garp only if sip is in the same subnet
+ * as an address configured on the incoming interface
+ */
+ return inet_confirm_addr(net, in_dev, sip, 0, scope) ? 1 : 0;
+ default:
+ return 0;
+ }
+}
+
static int arp_filter(__be32 sip, __be32 tip, struct net_device *dev)
{
struct rtable *rt;
@@ -868,12 +888,12 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
n = __neigh_lookup(&arp_tbl, &sip, dev, 0);

addr_type = -1;
- if (n || IN_DEV_ARP_ACCEPT(in_dev)) {
+ if (n || arp_accept(in_dev, sip)) {
is_garp = arp_is_garp(net, dev, &addr_type, arp->ar_op,
sip, tip, sha, tha);
}

- if (IN_DEV_ARP_ACCEPT(in_dev)) {
+ if (arp_accept(in_dev, sip)) {
/* Unsolicited ARP is not accepted by default.
It is possible, that this option should be enabled for some
devices (strip is candidate)
--
2.30.2