bonding: add new parameter ns_targets
authorHangbin Liu <liuhangbin@gmail.com>
Mon, 21 Feb 2022 05:54:56 +0000 (13:54 +0800)
committerDavid S. Miller <davem@davemloft.net>
Mon, 21 Feb 2022 12:13:45 +0000 (12:13 +0000)
Add a new bonding parameter ns_targets to store IPv6 address.
Add required bond_ns_send/rcv functions first before adding
IPv6 address option setting.

Add two functions bond_send/rcv_validate so we can send/recv
ARP and NS at the same time.

Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/bonding/bond_main.c
drivers/net/bonding/bond_options.c
include/net/bonding.h

index ca8613d..55e0ba2 100644 (file)
@@ -88,6 +88,7 @@
 #if IS_ENABLED(CONFIG_TLS_DEVICE)
 #include <net/tls.h>
 #endif
+#include <net/ip6_route.h>
 
 #include "bonding_priv.h"
 
@@ -2975,30 +2976,17 @@ static void bond_validate_arp(struct bonding *bond, struct slave *slave, __be32
        slave->target_last_arp_rx[i] = jiffies;
 }
 
-int bond_arp_rcv(const struct sk_buff *skb, struct bonding *bond,
-                struct slave *slave)
+static int bond_arp_rcv(const struct sk_buff *skb, struct bonding *bond,
+                       struct slave *slave)
 {
        struct arphdr *arp = (struct arphdr *)skb->data;
        struct slave *curr_active_slave, *curr_arp_slave;
        unsigned char *arp_ptr;
        __be32 sip, tip;
-       int is_arp = skb->protocol == __cpu_to_be16(ETH_P_ARP);
        unsigned int alen;
 
-       if (!slave_do_arp_validate(bond, slave)) {
-               if ((slave_do_arp_validate_only(bond) && is_arp) ||
-                   !slave_do_arp_validate_only(bond))
-                       slave->last_rx = jiffies;
-               return RX_HANDLER_ANOTHER;
-       } else if (!is_arp) {
-               return RX_HANDLER_ANOTHER;
-       }
-
        alen = arp_hdr_len(bond->dev);
 
-       slave_dbg(bond->dev, slave->dev, "%s: skb->dev %s\n",
-                  __func__, skb->dev->name);
-
        if (alen > skb_headlen(skb)) {
                arp = kmalloc(alen, GFP_ATOMIC);
                if (!arp)
@@ -3069,6 +3057,216 @@ out_unlock:
        return RX_HANDLER_ANOTHER;
 }
 
+#if IS_ENABLED(CONFIG_IPV6)
+static void bond_ns_send(struct slave *slave, const struct in6_addr *daddr,
+                        const struct in6_addr *saddr, struct bond_vlan_tag *tags)
+{
+       struct net_device *bond_dev = slave->bond->dev;
+       struct net_device *slave_dev = slave->dev;
+       struct in6_addr mcaddr;
+       struct sk_buff *skb;
+
+       slave_dbg(bond_dev, slave_dev, "NS on slave: dst %pI6c src %pI6c\n",
+                 daddr, saddr);
+
+       skb = ndisc_ns_create(slave_dev, daddr, saddr, 0);
+       if (!skb) {
+               net_err_ratelimited("NS packet allocation failed\n");
+               return;
+       }
+
+       addrconf_addr_solict_mult(daddr, &mcaddr);
+       if (bond_handle_vlan(slave, tags, skb))
+               ndisc_send_skb(skb, &mcaddr, saddr);
+}
+
+static void bond_ns_send_all(struct bonding *bond, struct slave *slave)
+{
+       struct in6_addr *targets = bond->params.ns_targets;
+       struct bond_vlan_tag *tags;
+       struct dst_entry *dst;
+       struct in6_addr saddr;
+       struct flowi6 fl6;
+       int i;
+
+       for (i = 0; i < BOND_MAX_NS_TARGETS && !ipv6_addr_any(&targets[i]); i++) {
+               slave_dbg(bond->dev, slave->dev, "%s: target %pI6c\n",
+                         __func__, &targets[i]);
+               tags = NULL;
+
+               /* Find out through which dev should the packet go */
+               memset(&fl6, 0, sizeof(struct flowi6));
+               fl6.daddr = targets[i];
+               fl6.flowi6_oif = bond->dev->ifindex;
+
+               dst = ip6_route_output(dev_net(bond->dev), NULL, &fl6);
+               if (dst->error) {
+                       dst_release(dst);
+                       /* there's no route to target - try to send arp
+                        * probe to generate any traffic (arp_validate=0)
+                        */
+                       if (bond->params.arp_validate)
+                               pr_warn_once("%s: no route to ns_ip6_target %pI6c and arp_validate is set\n",
+                                            bond->dev->name,
+                                            &targets[i]);
+                       bond_ns_send(slave, &targets[i], &in6addr_any, tags);
+                       continue;
+               }
+
+               /* bond device itself */
+               if (dst->dev == bond->dev)
+                       goto found;
+
+               rcu_read_lock();
+               tags = bond_verify_device_path(bond->dev, dst->dev, 0);
+               rcu_read_unlock();
+
+               if (!IS_ERR_OR_NULL(tags))
+                       goto found;
+
+               /* Not our device - skip */
+               slave_dbg(bond->dev, slave->dev, "no path to ns_ip6_target %pI6c via dst->dev %s\n",
+                         &targets[i], dst->dev ? dst->dev->name : "NULL");
+
+               dst_release(dst);
+               continue;
+
+found:
+               if (!ipv6_dev_get_saddr(dev_net(dst->dev), dst->dev, &targets[i], 0, &saddr))
+                       bond_ns_send(slave, &targets[i], &saddr, tags);
+               dst_release(dst);
+               kfree(tags);
+       }
+}
+
+static int bond_confirm_addr6(struct net_device *dev,
+                             struct netdev_nested_priv *priv)
+{
+       struct in6_addr *addr = (struct in6_addr *)priv->data;
+
+       return ipv6_chk_addr(dev_net(dev), addr, dev, 0);
+}
+
+static bool bond_has_this_ip6(struct bonding *bond, struct in6_addr *addr)
+{
+       struct netdev_nested_priv priv = {
+               .data = addr,
+       };
+       int ret = false;
+
+       if (bond_confirm_addr6(bond->dev, &priv))
+               return true;
+
+       rcu_read_lock();
+       if (netdev_walk_all_upper_dev_rcu(bond->dev, bond_confirm_addr6, &priv))
+               ret = true;
+       rcu_read_unlock();
+
+       return ret;
+}
+
+static void bond_validate_ns(struct bonding *bond, struct slave *slave,
+                            struct in6_addr *saddr, struct in6_addr *daddr)
+{
+       int i;
+
+       if (ipv6_addr_any(saddr) || !bond_has_this_ip6(bond, daddr)) {
+               slave_dbg(bond->dev, slave->dev, "%s: sip %pI6c tip %pI6c not found\n",
+                         __func__, saddr, daddr);
+               return;
+       }
+
+       i = bond_get_targets_ip6(bond->params.ns_targets, saddr);
+       if (i == -1) {
+               slave_dbg(bond->dev, slave->dev, "%s: sip %pI6c not found in targets\n",
+                         __func__, saddr);
+               return;
+       }
+       slave->last_rx = jiffies;
+       slave->target_last_arp_rx[i] = jiffies;
+}
+
+static int bond_na_rcv(const struct sk_buff *skb, struct bonding *bond,
+                      struct slave *slave)
+{
+       struct slave *curr_active_slave, *curr_arp_slave;
+       struct icmp6hdr *hdr = icmp6_hdr(skb);
+       struct in6_addr *saddr, *daddr;
+
+       if (skb->pkt_type == PACKET_OTHERHOST ||
+           skb->pkt_type == PACKET_LOOPBACK ||
+           hdr->icmp6_type != NDISC_NEIGHBOUR_ADVERTISEMENT)
+               goto out;
+
+       saddr = &ipv6_hdr(skb)->saddr;
+       daddr = &ipv6_hdr(skb)->daddr;
+
+       slave_dbg(bond->dev, slave->dev, "%s: %s/%d av %d sv %d sip %pI6c tip %pI6c\n",
+                 __func__, slave->dev->name, bond_slave_state(slave),
+                 bond->params.arp_validate, slave_do_arp_validate(bond, slave),
+                 saddr, daddr);
+
+       curr_active_slave = rcu_dereference(bond->curr_active_slave);
+       curr_arp_slave = rcu_dereference(bond->current_arp_slave);
+
+       /* We 'trust' the received ARP enough to validate it if:
+        * see bond_arp_rcv().
+        */
+       if (bond_is_active_slave(slave))
+               bond_validate_ns(bond, slave, saddr, daddr);
+       else if (curr_active_slave &&
+                time_after(slave_last_rx(bond, curr_active_slave),
+                           curr_active_slave->last_link_up))
+               bond_validate_ns(bond, slave, saddr, daddr);
+       else if (curr_arp_slave &&
+                bond_time_in_interval(bond,
+                                      dev_trans_start(curr_arp_slave->dev), 1))
+               bond_validate_ns(bond, slave, saddr, daddr);
+
+out:
+       return RX_HANDLER_ANOTHER;
+}
+#endif
+
+int bond_rcv_validate(const struct sk_buff *skb, struct bonding *bond,
+                     struct slave *slave)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+       bool is_ipv6 = skb->protocol == __cpu_to_be16(ETH_P_IPV6);
+#endif
+       bool is_arp = skb->protocol == __cpu_to_be16(ETH_P_ARP);
+
+       slave_dbg(bond->dev, slave->dev, "%s: skb->dev %s\n",
+                 __func__, skb->dev->name);
+
+       /* Use arp validate logic for both ARP and NS */
+       if (!slave_do_arp_validate(bond, slave)) {
+               if ((slave_do_arp_validate_only(bond) && is_arp) ||
+#if IS_ENABLED(CONFIG_IPV6)
+                   (slave_do_arp_validate_only(bond) && is_ipv6) ||
+#endif
+                   !slave_do_arp_validate_only(bond))
+                       slave->last_rx = jiffies;
+               return RX_HANDLER_ANOTHER;
+       } else if (is_arp) {
+               return bond_arp_rcv(skb, bond, slave);
+#if IS_ENABLED(CONFIG_IPV6)
+       } else if (is_ipv6) {
+               return bond_na_rcv(skb, bond, slave);
+#endif
+       } else {
+               return RX_HANDLER_ANOTHER;
+       }
+}
+
+static void bond_send_validate(struct bonding *bond, struct slave *slave)
+{
+       bond_arp_send_all(bond, slave);
+#if IS_ENABLED(CONFIG_IPV6)
+       bond_ns_send_all(bond, slave);
+#endif
+}
+
 /* function to verify if we're in the arp_interval timeslice, returns true if
  * (last_act - arp_interval) <= jiffies <= (last_act + mod * arp_interval +
  * arp_interval/2) . the arp_interval/2 is needed for really fast networks.
@@ -3164,7 +3362,7 @@ static void bond_loadbalance_arp_mon(struct bonding *bond)
                 * to be unstable during low/no traffic periods
                 */
                if (bond_slave_is_up(slave))
-                       bond_arp_send_all(bond, slave);
+                       bond_send_validate(bond, slave);
        }
 
        rcu_read_unlock();
@@ -3378,7 +3576,7 @@ static bool bond_ab_arp_probe(struct bonding *bond)
                            curr_active_slave->dev->name);
 
        if (curr_active_slave) {
-               bond_arp_send_all(bond, curr_active_slave);
+               bond_send_validate(bond, curr_active_slave);
                return should_notify_rtnl;
        }
 
@@ -3430,7 +3628,7 @@ static bool bond_ab_arp_probe(struct bonding *bond)
        bond_set_slave_link_state(new_slave, BOND_LINK_BACK,
                                  BOND_SLAVE_NOTIFY_LATER);
        bond_set_slave_active_flags(new_slave, BOND_SLAVE_NOTIFY_LATER);
-       bond_arp_send_all(bond, new_slave);
+       bond_send_validate(bond, new_slave);
        new_slave->last_link_up = jiffies;
        rcu_assign_pointer(bond->current_arp_slave, new_slave);
 
@@ -3966,7 +4164,7 @@ static int bond_open(struct net_device *bond_dev)
 
        if (bond->params.arp_interval) {  /* arp interval, in milliseconds. */
                queue_delayed_work(bond->wq, &bond->arp_work, 0);
-               bond->recv_probe = bond_arp_rcv;
+               bond->recv_probe = bond_rcv_validate;
        }
 
        if (BOND_MODE(bond) == BOND_MODE_8023AD) {
@@ -5947,6 +6145,7 @@ static int bond_check_params(struct bond_params *params)
                strscpy_pad(params->primary, primary, sizeof(params->primary));
 
        memcpy(params->arp_targets, arp_target, sizeof(arp_target));
+       memset(params->ns_targets, 0, sizeof(struct in6_addr) * BOND_MAX_NS_TARGETS);
 
        return 0;
 }
index 2e8484a..ab57513 100644 (file)
@@ -1052,7 +1052,7 @@ static int bond_option_arp_interval_set(struct bonding *bond,
                        cancel_delayed_work_sync(&bond->arp_work);
                } else {
                        /* arp_validate can be set only in active-backup mode */
-                       bond->recv_probe = bond_arp_rcv;
+                       bond->recv_probe = bond_rcv_validate;
                        cancel_delayed_work_sync(&bond->mii_work);
                        queue_delayed_work(bond->wq, &bond->arp_work, 0);
                }
index 7dead85..f3b986f 100644 (file)
 #include <net/bond_3ad.h>
 #include <net/bond_alb.h>
 #include <net/bond_options.h>
+#include <net/ipv6.h>
+#include <net/addrconf.h>
 
 #define BOND_MAX_ARP_TARGETS   16
+#define BOND_MAX_NS_TARGETS    BOND_MAX_ARP_TARGETS
 
 #define BOND_DEFAULT_MIIMON    100
 
@@ -146,6 +149,7 @@ struct bond_params {
        struct reciprocal_value reciprocal_packets_per_slave;
        u16 ad_actor_sys_prio;
        u16 ad_user_port_key;
+       struct in6_addr ns_targets[BOND_MAX_NS_TARGETS];
 
        /* 2 bytes of padding : see ether_addr_equal_64bits() */
        u8 ad_actor_system[ETH_ALEN + 2];
@@ -628,7 +632,7 @@ struct bond_net {
        struct class_attribute  class_attr_bonding_masters;
 };
 
-int bond_arp_rcv(const struct sk_buff *skb, struct bonding *bond, struct slave *slave);
+int bond_rcv_validate(const struct sk_buff *skb, struct bonding *bond, struct slave *slave);
 netdev_tx_t bond_dev_queue_xmit(struct bonding *bond, struct sk_buff *skb, struct net_device *slave_dev);
 int bond_create(struct net *net, const char *name);
 int bond_create_sysfs(struct bond_net *net);
@@ -735,6 +739,19 @@ static inline int bond_get_targets_ip(__be32 *targets, __be32 ip)
        return -1;
 }
 
+static inline int bond_get_targets_ip6(struct in6_addr *targets, struct in6_addr *ip)
+{
+       int i;
+
+       for (i = 0; i < BOND_MAX_NS_TARGETS; i++)
+               if (ipv6_addr_equal(&targets[i], ip))
+                       return i;
+               else if (ipv6_addr_any(&targets[i]))
+                       break;
+
+       return -1;
+}
+
 /* exported from bond_main.c */
 extern unsigned int bond_net_id;