bridge: suppress arp pkts on BR_NEIGH_SUPPRESS ports
authorRoopa Prabhu <roopa@cumulusnetworks.com>
Sat, 7 Oct 2017 05:12:38 +0000 (22:12 -0700)
committerDavid S. Miller <davem@davemloft.net>
Mon, 9 Oct 2017 04:12:04 +0000 (21:12 -0700)
This patch avoids flooding and proxies arp packets
for BR_NEIGH_SUPPRESS ports.

Moves existing br_do_proxy_arp to br_do_proxy_suppress_arp
to support both proxy arp and neigh suppress.

Signed-off-by: Roopa Prabhu <roopa@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/bridge/br_arp_nd_proxy.c
net/bridge/br_device.c
net/bridge/br_input.c
net/bridge/br_private.h

index f889ad5f0048b4b7ea635c703f0e0e2bdbea62cd..a79c1824e163668aa4fa0392055780e742fec377 100644 (file)
  */
 
 #include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/neighbour.h>
+#include <net/arp.h>
+#include <linux/if_vlan.h>
+#include <linux/inetdevice.h>
+#include <net/addrconf.h>
+
 #include "br_private.h"
 
 void br_recalculate_neigh_suppress_enabled(struct net_bridge *br)
@@ -30,3 +38,183 @@ void br_recalculate_neigh_suppress_enabled(struct net_bridge *br)
 
        br->neigh_suppress_enabled = neigh_suppress;
 }
+
+#if IS_ENABLED(CONFIG_INET)
+static void br_arp_send(struct net_bridge *br, struct net_bridge_port *p,
+                       struct net_device *dev, __be32 dest_ip, __be32 src_ip,
+                       const unsigned char *dest_hw,
+                       const unsigned char *src_hw,
+                       const unsigned char *target_hw,
+                       __be16 vlan_proto, u16 vlan_tci)
+{
+       struct net_bridge_vlan_group *vg;
+       struct sk_buff *skb;
+       u16 pvid;
+
+       netdev_dbg(dev, "arp send dev %s dst %pI4 dst_hw %pM src %pI4 src_hw %pM\n",
+                  dev->name, &dest_ip, dest_hw, &src_ip, src_hw);
+
+       if (!vlan_tci) {
+               arp_send(ARPOP_REPLY, ETH_P_ARP, dest_ip, dev, src_ip,
+                        dest_hw, src_hw, target_hw);
+               return;
+       }
+
+       skb = arp_create(ARPOP_REPLY, ETH_P_ARP, dest_ip, dev, src_ip,
+                        dest_hw, src_hw, target_hw);
+       if (!skb)
+               return;
+
+       if (p)
+               vg = nbp_vlan_group_rcu(p);
+       else
+               vg = br_vlan_group_rcu(br);
+       pvid = br_get_pvid(vg);
+       if (pvid == (vlan_tci & VLAN_VID_MASK))
+               vlan_tci = 0;
+
+       if (vlan_tci)
+               __vlan_hwaccel_put_tag(skb, vlan_proto, vlan_tci);
+
+       if (p) {
+               arp_xmit(skb);
+       } else {
+               skb_reset_mac_header(skb);
+               __skb_pull(skb, skb_network_offset(skb));
+               skb->ip_summed = CHECKSUM_UNNECESSARY;
+               skb->pkt_type = PACKET_HOST;
+
+               netif_rx_ni(skb);
+       }
+}
+
+static int br_chk_addr_ip(struct net_device *dev, void *data)
+{
+       __be32 ip = *(__be32 *)data;
+       struct in_device *in_dev;
+       __be32 addr = 0;
+
+       in_dev = __in_dev_get_rcu(dev);
+       if (in_dev)
+               addr = inet_confirm_addr(dev_net(dev), in_dev, 0, ip,
+                                        RT_SCOPE_HOST);
+
+       if (addr == ip)
+               return 1;
+
+       return 0;
+}
+
+static bool br_is_local_ip(struct net_device *dev, __be32 ip)
+{
+       if (br_chk_addr_ip(dev, &ip))
+               return true;
+
+       /* check if ip is configured on upper dev */
+       if (netdev_walk_all_upper_dev_rcu(dev, br_chk_addr_ip, &ip))
+               return true;
+
+       return false;
+}
+
+void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
+                             u16 vid, struct net_bridge_port *p)
+{
+       struct net_device *dev = br->dev;
+       struct net_device *vlandev = dev;
+       struct neighbour *n;
+       struct arphdr *parp;
+       u8 *arpptr, *sha;
+       __be32 sip, tip;
+
+       BR_INPUT_SKB_CB(skb)->proxyarp_replied = false;
+
+       if ((dev->flags & IFF_NOARP) ||
+           !pskb_may_pull(skb, arp_hdr_len(dev)))
+               return;
+
+       parp = arp_hdr(skb);
+
+       if (parp->ar_pro != htons(ETH_P_IP) ||
+           parp->ar_hln != dev->addr_len ||
+           parp->ar_pln != 4)
+               return;
+
+       arpptr = (u8 *)parp + sizeof(struct arphdr);
+       sha = arpptr;
+       arpptr += dev->addr_len;        /* sha */
+       memcpy(&sip, arpptr, sizeof(sip));
+       arpptr += sizeof(sip);
+       arpptr += dev->addr_len;        /* tha */
+       memcpy(&tip, arpptr, sizeof(tip));
+
+       if (ipv4_is_loopback(tip) ||
+           ipv4_is_multicast(tip))
+               return;
+
+       if (br->neigh_suppress_enabled) {
+               if (p && (p->flags & BR_NEIGH_SUPPRESS))
+                       return;
+               if (ipv4_is_zeronet(sip) || sip == tip) {
+                       /* prevent flooding to neigh suppress ports */
+                       BR_INPUT_SKB_CB(skb)->proxyarp_replied = true;
+                       return;
+               }
+       }
+
+       if (parp->ar_op != htons(ARPOP_REQUEST))
+               return;
+
+       if (vid != 0) {
+               vlandev = __vlan_find_dev_deep_rcu(br->dev, skb->vlan_proto,
+                                                  vid);
+               if (!vlandev)
+                       return;
+       }
+
+       if (br->neigh_suppress_enabled && br_is_local_ip(vlandev, tip)) {
+               /* its our local ip, so don't proxy reply
+                * and don't forward to neigh suppress ports
+                */
+               BR_INPUT_SKB_CB(skb)->proxyarp_replied = true;
+               return;
+       }
+
+       n = neigh_lookup(&arp_tbl, &tip, vlandev);
+       if (n) {
+               struct net_bridge_fdb_entry *f;
+
+               if (!(n->nud_state & NUD_VALID)) {
+                       neigh_release(n);
+                       return;
+               }
+
+               f = br_fdb_find_rcu(br, n->ha, vid);
+               if (f) {
+                       bool replied = false;
+
+                       if ((p && (p->flags & BR_PROXYARP)) ||
+                           (f->dst && (f->dst->flags & (BR_PROXYARP_WIFI |
+                                                        BR_NEIGH_SUPPRESS)))) {
+                               if (!vid)
+                                       br_arp_send(br, p, skb->dev, sip, tip,
+                                                   sha, n->ha, sha, 0, 0);
+                               else
+                                       br_arp_send(br, p, skb->dev, sip, tip,
+                                                   sha, n->ha, sha,
+                                                   skb->vlan_proto,
+                                                   skb_vlan_tag_get(skb));
+                               replied = true;
+                       }
+
+                       /* If we have replied or as long as we know the
+                        * mac, indicate to arp replied
+                        */
+                       if (replied || br->neigh_suppress_enabled)
+                               BR_INPUT_SKB_CB(skb)->proxyarp_replied = true;
+               }
+
+               neigh_release(n);
+       }
+}
+#endif
index 7acb77c9bd65ccf98788f5ebe72eb2175a54032d..eb30c6a274c376e67d782d785583b4249e7f5377 100644 (file)
@@ -39,6 +39,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
        struct pcpu_sw_netstats *brstats = this_cpu_ptr(br->stats);
        const struct nf_br_ops *nf_ops;
        const unsigned char *dest;
+       struct ethhdr *eth;
        u16 vid = 0;
 
        rcu_read_lock();
@@ -57,11 +58,19 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
        BR_INPUT_SKB_CB(skb)->brdev = dev;
 
        skb_reset_mac_header(skb);
+       eth = eth_hdr(skb);
        skb_pull(skb, ETH_HLEN);
 
        if (!br_allowed_ingress(br, br_vlan_group_rcu(br), skb, &vid))
                goto out;
 
+       if (IS_ENABLED(CONFIG_INET) &&
+           (eth->h_proto == htons(ETH_P_ARP) ||
+            eth->h_proto == htons(ETH_P_RARP)) &&
+           br->neigh_suppress_enabled) {
+               br_do_proxy_suppress_arp(skb, br, vid, NULL);
+       }
+
        dest = eth_hdr(skb)->h_dest;
        if (is_broadcast_ether_addr(dest)) {
                br_flood(br, skb, BR_PKT_BROADCAST, false, true);
index 7cb613776b3182553a62dccc2db68ec9b4271b46..4b8d2ec2fa23cce780fd35b0f35ac97257f4f4d2 100644 (file)
@@ -71,62 +71,6 @@ static int br_pass_frame_up(struct sk_buff *skb)
                       br_netif_receive_skb);
 }
 
-static void br_do_proxy_arp(struct sk_buff *skb, struct net_bridge *br,
-                           u16 vid, struct net_bridge_port *p)
-{
-       struct net_device *dev = br->dev;
-       struct neighbour *n;
-       struct arphdr *parp;
-       u8 *arpptr, *sha;
-       __be32 sip, tip;
-
-       BR_INPUT_SKB_CB(skb)->proxyarp_replied = false;
-
-       if ((dev->flags & IFF_NOARP) ||
-           !pskb_may_pull(skb, arp_hdr_len(dev)))
-               return;
-
-       parp = arp_hdr(skb);
-
-       if (parp->ar_pro != htons(ETH_P_IP) ||
-           parp->ar_op != htons(ARPOP_REQUEST) ||
-           parp->ar_hln != dev->addr_len ||
-           parp->ar_pln != 4)
-               return;
-
-       arpptr = (u8 *)parp + sizeof(struct arphdr);
-       sha = arpptr;
-       arpptr += dev->addr_len;        /* sha */
-       memcpy(&sip, arpptr, sizeof(sip));
-       arpptr += sizeof(sip);
-       arpptr += dev->addr_len;        /* tha */
-       memcpy(&tip, arpptr, sizeof(tip));
-
-       if (ipv4_is_loopback(tip) ||
-           ipv4_is_multicast(tip))
-               return;
-
-       n = neigh_lookup(&arp_tbl, &tip, dev);
-       if (n) {
-               struct net_bridge_fdb_entry *f;
-
-               if (!(n->nud_state & NUD_VALID)) {
-                       neigh_release(n);
-                       return;
-               }
-
-               f = br_fdb_find_rcu(br, n->ha, vid);
-               if (f && ((p->flags & BR_PROXYARP) ||
-                         (f->dst && (f->dst->flags & BR_PROXYARP_WIFI)))) {
-                       arp_send(ARPOP_REPLY, ETH_P_ARP, sip, skb->dev, tip,
-                                sha, n->ha, sha);
-                       BR_INPUT_SKB_CB(skb)->proxyarp_replied = true;
-               }
-
-               neigh_release(n);
-       }
-}
-
 /* note: already called with rcu_read_lock */
 int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
 {
@@ -171,8 +115,11 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
 
        BR_INPUT_SKB_CB(skb)->brdev = br->dev;
 
-       if (IS_ENABLED(CONFIG_INET) && skb->protocol == htons(ETH_P_ARP))
-               br_do_proxy_arp(skb, br, vid, p);
+       if (IS_ENABLED(CONFIG_INET) &&
+           (skb->protocol == htons(ETH_P_ARP) ||
+            skb->protocol == htons(ETH_P_RARP))) {
+               br_do_proxy_suppress_arp(skb, br, vid, p);
+       }
 
        switch (pkt_type) {
        case BR_PKT_MULTICAST:
index 00fa371b1fb2d1f61868ba2fe882790161908449..4e6b25be14d030edc6e5ede52209786398820839 100644 (file)
@@ -1140,5 +1140,8 @@ static inline void br_switchdev_frame_unmark(struct sk_buff *skb)
 }
 #endif /* CONFIG_NET_SWITCHDEV */
 
+/* br_arp_nd_proxy.c */
 void br_recalculate_neigh_suppress_enabled(struct net_bridge *br);
+void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
+                             u16 vid, struct net_bridge_port *p);
 #endif