Replacing the definition of bond_params.arp_targets (__be32 arp_targets[]) with: struct bond_arp_target { __be32 target_ip; u32 flags; struct bond_vlan_tag *tags; }; To provide storage for a list of vlan tags for each target. All references to arp_target are change to use the new structure. Signed-off-by: David Wilder --- drivers/net/bonding/bond_main.c | 29 ++++++++++++++++------------- drivers/net/bonding/bond_netlink.c | 4 ++-- drivers/net/bonding/bond_options.c | 18 +++++++++--------- drivers/net/bonding/bond_procfs.c | 4 ++-- drivers/net/bonding/bond_sysfs.c | 4 ++-- include/net/bond_options.h | 20 ++++++++++++++++++++ include/net/bonding.h | 15 +++++---------- 7 files changed, 56 insertions(+), 38 deletions(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 1ea41f1a9190..57cf4585816d 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -3066,26 +3066,29 @@ static void bond_arp_send_all(struct bonding *bond, struct slave *slave) { struct rtable *rt; struct bond_vlan_tag *tags; - __be32 *targets = bond->params.arp_targets, addr; + struct bond_arp_target *targets = bond->params.arp_targets; + __be32 target_ip, addr; int i; - for (i = 0; i < BOND_MAX_ARP_TARGETS && targets[i]; i++) { + for (i = 0; i < BOND_MAX_ARP_TARGETS && targets[i].target_ip; i++) { + target_ip = targets[i].target_ip; + tags = targets[i].tags; + slave_dbg(bond->dev, slave->dev, "%s: target %pI4\n", - __func__, &targets[i]); - tags = NULL; + __func__, &target_ip); /* Find out through which dev should the packet go */ - rt = ip_route_output(dev_net(bond->dev), targets[i], 0, 0, 0, + rt = ip_route_output(dev_net(bond->dev), target_ip, 0, 0, 0, RT_SCOPE_LINK); if (IS_ERR(rt)) { - /* there's no route to target - try to send arp + /* there's no route to target_ip - try to send arp * probe to generate any traffic (arp_validate=0) */ if (bond->params.arp_validate) pr_warn_once("%s: no route to arp_ip_target %pI4 and arp_validate is set\n", bond->dev->name, - &targets[i]); - bond_arp_send(slave, ARPOP_REQUEST, targets[i], + &target_ip); + bond_arp_send(slave, ARPOP_REQUEST, target_ip, 0, tags); continue; } @@ -3103,15 +3106,15 @@ static void bond_arp_send_all(struct bonding *bond, struct slave *slave) /* Not our device - skip */ slave_dbg(bond->dev, slave->dev, "no path to arp_ip_target %pI4 via rt.dev %s\n", - &targets[i], rt->dst.dev ? rt->dst.dev->name : "NULL"); + &target_ip, rt->dst.dev ? rt->dst.dev->name : "NULL"); ip_rt_put(rt); continue; found: - addr = bond_confirm_addr(rt->dst.dev, targets[i], 0); + addr = bond_confirm_addr(rt->dst.dev, target_ip, 0); ip_rt_put(rt); - bond_arp_send(slave, ARPOP_REQUEST, targets[i], addr, tags); + bond_arp_send(slave, ARPOP_REQUEST, target_ip, addr, tags); kfree(tags); } } @@ -6066,7 +6069,7 @@ static int __init bond_check_params(struct bond_params *params) int arp_all_targets_value = 0; u16 ad_actor_sys_prio = 0; u16 ad_user_port_key = 0; - __be32 arp_target[BOND_MAX_ARP_TARGETS] = { 0 }; + struct bond_arp_target arp_target[BOND_MAX_ARP_TARGETS] = { 0 }; int arp_ip_count; int bond_mode = BOND_MODE_ROUNDROBIN; int xmit_hashtype = BOND_XMIT_POLICY_LAYER2; @@ -6260,7 +6263,7 @@ static int __init bond_check_params(struct bond_params *params) arp_interval = 0; } else { if (bond_get_targets_ip(arp_target, ip) == -1) - arp_target[arp_ip_count++] = ip; + arp_target[arp_ip_count++].target_ip = ip; else pr_warn("Warning: duplicate address %pI4 in arp_ip_target, skipping\n", &ip); diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c index ba71d95a82d2..cc00fbad9dd3 100644 --- a/drivers/net/bonding/bond_netlink.c +++ b/drivers/net/bonding/bond_netlink.c @@ -714,8 +714,8 @@ static int bond_fill_info(struct sk_buff *skb, targets_added = 0; for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) { - if (bond->params.arp_targets[i]) { - if (nla_put_be32(skb, i, bond->params.arp_targets[i])) + if (bond->params.arp_targets[i].target_ip) { + if (nla_put_be32(skb, i, bond->params.arp_targets[i].target_ip)) goto nla_put_failure; targets_added = 1; } diff --git a/drivers/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c index 495a87f2ea7c..91d57ba968d6 100644 --- a/drivers/net/bonding/bond_options.c +++ b/drivers/net/bonding/bond_options.c @@ -1125,7 +1125,7 @@ static int bond_option_arp_interval_set(struct bonding *bond, netdev_dbg(bond->dev, "ARP monitoring cannot be used with MII monitoring. Disabling MII monitoring\n"); bond->params.miimon = 0; } - if (!bond->params.arp_targets[0]) + if (!bond->params.arp_targets[0].target_ip) netdev_dbg(bond->dev, "ARP monitoring has been set up, but no ARP targets have been specified\n"); } if (bond->dev->flags & IFF_UP) { @@ -1153,20 +1153,20 @@ static void _bond_options_arp_ip_target_set(struct bonding *bond, int slot, __be32 target, unsigned long last_rx) { - __be32 *targets = bond->params.arp_targets; + struct bond_arp_target *targets = bond->params.arp_targets; struct list_head *iter; struct slave *slave; if (slot >= 0 && slot < BOND_MAX_ARP_TARGETS) { bond_for_each_slave(bond, slave, iter) slave->target_last_arp_rx[slot] = last_rx; - targets[slot] = target; + targets[slot].target_ip = target; } } static int _bond_option_arp_ip_target_add(struct bonding *bond, __be32 target) { - __be32 *targets = bond->params.arp_targets; + struct bond_arp_target *targets = bond->params.arp_targets; int ind; if (!bond_is_ip_target_ok(target)) { @@ -1201,7 +1201,7 @@ static int bond_option_arp_ip_target_add(struct bonding *bond, __be32 target) static int bond_option_arp_ip_target_rem(struct bonding *bond, __be32 target) { - __be32 *targets = bond->params.arp_targets; + struct bond_arp_target *targets = bond->params.arp_targets; struct list_head *iter; struct slave *slave; unsigned long *targets_rx; @@ -1220,20 +1220,20 @@ static int bond_option_arp_ip_target_rem(struct bonding *bond, __be32 target) return -EINVAL; } - if (ind == 0 && !targets[1] && bond->params.arp_interval) + if (ind == 0 && !targets[1].target_ip && bond->params.arp_interval) netdev_warn(bond->dev, "Removing last arp target with arp_interval on\n"); netdev_dbg(bond->dev, "Removing ARP target %pI4\n", &target); bond_for_each_slave(bond, slave, iter) { targets_rx = slave->target_last_arp_rx; - for (i = ind; (i < BOND_MAX_ARP_TARGETS-1) && targets[i+1]; i++) + for (i = ind; (i < BOND_MAX_ARP_TARGETS - 1) && targets[i + 1].target_ip; i++) targets_rx[i] = targets_rx[i+1]; targets_rx[i] = 0; } - for (i = ind; (i < BOND_MAX_ARP_TARGETS-1) && targets[i+1]; i++) + for (i = ind; (i < BOND_MAX_ARP_TARGETS - 1) && targets[i + 1].target_ip; i++) targets[i] = targets[i+1]; - targets[i] = 0; + targets[i].target_ip = 0; return 0; } diff --git a/drivers/net/bonding/bond_procfs.c b/drivers/net/bonding/bond_procfs.c index 7edf72ec816a..94e6fd7041ee 100644 --- a/drivers/net/bonding/bond_procfs.c +++ b/drivers/net/bonding/bond_procfs.c @@ -121,11 +121,11 @@ static void bond_info_show_master(struct seq_file *seq) seq_printf(seq, "ARP IP target/s (n.n.n.n form):"); for (i = 0; (i < BOND_MAX_ARP_TARGETS); i++) { - if (!bond->params.arp_targets[i]) + if (!bond->params.arp_targets[i].target_ip) break; if (printed) seq_printf(seq, ","); - seq_printf(seq, " %pI4", &bond->params.arp_targets[i]); + seq_printf(seq, " %pI4", &bond->params.arp_targets[i].target_ip); printed = 1; } seq_printf(seq, "\n"); diff --git a/drivers/net/bonding/bond_sysfs.c b/drivers/net/bonding/bond_sysfs.c index 9a75ad3181ab..7114bd4d7735 100644 --- a/drivers/net/bonding/bond_sysfs.c +++ b/drivers/net/bonding/bond_sysfs.c @@ -290,9 +290,9 @@ static ssize_t bonding_show_arp_targets(struct device *d, int i, res = 0; for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) { - if (bond->params.arp_targets[i]) + if (bond->params.arp_targets[i].target_ip) res += sysfs_emit_at(buf, res, "%pI4 ", - &bond->params.arp_targets[i]); + &bond->params.arp_targets[i].target_ip); } if (res) buf[res-1] = '\n'; /* eat the leftover space */ diff --git a/include/net/bond_options.h b/include/net/bond_options.h index e6eedf23aea1..dea58a07e4cc 100644 --- a/include/net/bond_options.h +++ b/include/net/bond_options.h @@ -121,6 +121,26 @@ struct bond_option { int (*set)(struct bonding *bond, const struct bond_opt_value *val); }; +struct bond_vlan_tag { + __be16 vlan_proto; + unsigned short vlan_id; +}; + +/* Value type flags: + * BOND_TARGET_DONTFREE - never free the tags + * BOND_TARGET_USERTAGS - tags have been supplied by the user + */ +enum { + BOND_TARGET_DONTFREE = BIT(0), + BOND_TARGET_USERTAGS = BIT(1), +}; + +struct bond_arp_target { + __be32 target_ip; + u32 flags; + struct bond_vlan_tag *tags; +}; + int __bond_opt_set(struct bonding *bond, unsigned int option, struct bond_opt_value *val, struct nlattr *bad_attr, struct netlink_ext_ack *extack); diff --git a/include/net/bonding.h b/include/net/bonding.h index 37335f62f579..a0eae209315f 100644 --- a/include/net/bonding.h +++ b/include/net/bonding.h @@ -136,7 +136,7 @@ struct bond_params { int ad_select; char primary[IFNAMSIZ]; int primary_reselect; - __be32 arp_targets[BOND_MAX_ARP_TARGETS]; + struct bond_arp_target arp_targets[BOND_MAX_ARP_TARGETS]; int tx_queues; int all_slaves_active; int resend_igmp; @@ -276,11 +276,6 @@ struct bonding { void bond_queue_slave_event(struct slave *slave); void bond_lower_state_changed(struct slave *slave); -struct bond_vlan_tag { - __be16 vlan_proto; - unsigned short vlan_id; -}; - /* * Returns NULL if the net_device does not belong to any of the bond's slaves * @@ -524,7 +519,7 @@ static inline unsigned long slave_oldest_target_arp_rx(struct bonding *bond, int i = 1; unsigned long ret = slave->target_last_arp_rx[0]; - for (; (i < BOND_MAX_ARP_TARGETS) && bond->params.arp_targets[i]; i++) + for (; (i < BOND_MAX_ARP_TARGETS) && bond->params.arp_targets[i].target_ip; i++) if (time_before(slave->target_last_arp_rx[i], ret)) ret = slave->target_last_arp_rx[i]; @@ -762,14 +757,14 @@ static inline bool bond_slave_has_mac_rcu(struct bonding *bond, const u8 *mac) /* Check if the ip is present in arp ip list, or first free slot if ip == 0 * Returns -1 if not found, index if found */ -static inline int bond_get_targets_ip(__be32 *targets, __be32 ip) +static inline int bond_get_targets_ip(struct bond_arp_target *targets, __be32 ip) { int i; for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) - if (targets[i] == ip) + if (targets[i].target_ip == ip) return i; - else if (targets[i] == 0) + else if (targets[i].target_ip == 0) break; return -1; -- 2.50.1 Used to record the size of the extra array. __bond_opt_init() is updated to set extra_len. BOND_OPT_EXTRA_MAXLEN is increased from 16 to 64. This is needed for the extended arp_ip_target option. The ip command will now pass a variable length value when setting arp_ip_target. Signed-off-by: David Wilder --- include/net/bond_options.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/net/bond_options.h b/include/net/bond_options.h index dea58a07e4cc..e3eb5fe6d1e8 100644 --- a/include/net/bond_options.h +++ b/include/net/bond_options.h @@ -87,14 +87,15 @@ enum { * - if value != ULLONG_MAX -> parse value * - if string != NULL -> parse string * - if the opt is RAW data and length less than maxlen, - * copy the data to extra storage + * copy the data to extra storage, extra_len is set to the size of data copied. */ -#define BOND_OPT_EXTRA_MAXLEN 16 +#define BOND_OPT_EXTRA_MAXLEN 64 struct bond_opt_value { char *string; u64 value; u32 flags; + size_t extra_len; union { char extra[BOND_OPT_EXTRA_MAXLEN]; struct net_device *slave_dev; @@ -169,8 +170,10 @@ static inline void __bond_opt_init(struct bond_opt_value *optval, else if (string) optval->string = string; - if (extra && extra_len <= BOND_OPT_EXTRA_MAXLEN) + if (extra && extra_len <= BOND_OPT_EXTRA_MAXLEN) { memcpy(optval->extra, extra, extra_len); + optval->extra_len = extra_len; + } } #define bond_opt_initval(optval, value) __bond_opt_init(optval, NULL, value, NULL, 0) #define bond_opt_initstr(optval, str) __bond_opt_init(optval, str, ULLONG_MAX, NULL, 0) -- 2.50.1 Adding helpers and defines needed for extending the arp_ip_target parameters. Signed-off-by: David Wilder --- include/net/bonding.h | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/include/net/bonding.h b/include/net/bonding.h index a0eae209315f..75f6b1e32b3d 100644 --- a/include/net/bonding.h +++ b/include/net/bonding.h @@ -808,4 +808,50 @@ static inline netdev_tx_t bond_tx_drop(struct net_device *dev, struct sk_buff *s return NET_XMIT_DROP; } +/* Helpers for handling arp_ip_target */ +#define BOND_OPTION_STRING_MAX_SIZE 64 +#define BOND_MAX_VLAN_TAGS 5 +#define BOND_VLAN_PROTO_NONE cpu_to_be16(0xffff) + +static inline char *bond_arp_target_to_string(struct bond_arp_target *target, + char *buf, int size) +{ + struct bond_vlan_tag *tags = target->tags; + int i, num = 0; + + if (!(target->flags & BOND_TARGET_USERTAGS)) { + num = snprintf(&buf[0], size, "%pI4", &target->target_ip); + return buf; + } + + num = snprintf(&buf[0], size, "%pI4[", &target->target_ip); + if (tags) { + for (i = 0; (tags[i].vlan_proto != BOND_VLAN_PROTO_NONE); i++) { + if (!tags[i].vlan_id) + continue; + if (i != 0) + num = num + snprintf(&buf[num], size - num, "/"); + num = num + snprintf(&buf[num], size - num, "%u", + tags[i].vlan_id); + } + } + snprintf(&buf[num], size - num, "]"); + return buf; +} + +static inline void bond_free_vlan_tag(struct bond_arp_target *target) +{ + kfree(target->tags); +} + +static inline void __bond_free_vlan_tags(struct bond_arp_target *targets) +{ + int i; + + for (i = 0; i < BOND_MAX_ARP_TARGETS && targets[i].tags; i++) + bond_free_vlan_tag(&targets[i]); +} + +#define bond_free_vlan_tags(targets) __bond_free_vlan_tags(targets) + #endif /* _NET_BONDING_H */ -- 2.50.1 Changes to bond_netlink and bond_options to process extended format arp_ip_target option sent from user space via the ip command. The extended format adds a list of vlan tags to the ip target address. Signed-off-by: David Wilder --- drivers/net/bonding/bond_netlink.c | 5 +- drivers/net/bonding/bond_options.c | 126 +++++++++++++++++++++++------ 2 files changed, 104 insertions(+), 27 deletions(-) diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c index cc00fbad9dd3..97fdbd962513 100644 --- a/drivers/net/bonding/bond_netlink.c +++ b/drivers/net/bonding/bond_netlink.c @@ -307,9 +307,10 @@ static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[], if (nla_len(attr) < sizeof(target)) return -EINVAL; - target = nla_get_be32(attr); + bond_opt_initextra(&newval, + (__force void *)nla_data(attr), + nla_len(attr)); - bond_opt_initval(&newval, (__force u64)target); err = __bond_opt_set(bond, BOND_OPT_ARP_TARGETS, &newval, data[IFLA_BOND_ARP_IP_TARGET], diff --git a/drivers/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c index 91d57ba968d6..ba97479080ba 100644 --- a/drivers/net/bonding/bond_options.c +++ b/drivers/net/bonding/bond_options.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -31,8 +32,10 @@ static int bond_option_use_carrier_set(struct bonding *bond, const struct bond_opt_value *newval); static int bond_option_arp_interval_set(struct bonding *bond, const struct bond_opt_value *newval); -static int bond_option_arp_ip_target_add(struct bonding *bond, __be32 target); -static int bond_option_arp_ip_target_rem(struct bonding *bond, __be32 target); +static int bond_option_arp_ip_target_add(struct bonding *bond, + struct bond_arp_target target); +static int bond_option_arp_ip_target_rem(struct bonding *bond, + struct bond_arp_target target); static int bond_option_arp_ip_targets_set(struct bonding *bond, const struct bond_opt_value *newval); static int bond_option_ns_ip6_targets_set(struct bonding *bond, @@ -1150,7 +1153,7 @@ static int bond_option_arp_interval_set(struct bonding *bond, } static void _bond_options_arp_ip_target_set(struct bonding *bond, int slot, - __be32 target, + struct bond_arp_target target, unsigned long last_rx) { struct bond_arp_target *targets = bond->params.arp_targets; @@ -1160,24 +1163,26 @@ static void _bond_options_arp_ip_target_set(struct bonding *bond, int slot, if (slot >= 0 && slot < BOND_MAX_ARP_TARGETS) { bond_for_each_slave(bond, slave, iter) slave->target_last_arp_rx[slot] = last_rx; - targets[slot].target_ip = target; + memcpy(&targets[slot], &target, sizeof(target)); } } -static int _bond_option_arp_ip_target_add(struct bonding *bond, __be32 target) +static int _bond_option_arp_ip_target_add(struct bonding *bond, + struct bond_arp_target target) { struct bond_arp_target *targets = bond->params.arp_targets; + char pbuf[BOND_OPTION_STRING_MAX_SIZE]; int ind; - if (!bond_is_ip_target_ok(target)) { + if (!bond_is_ip_target_ok(target.target_ip)) { netdev_err(bond->dev, "invalid ARP target %pI4 specified for addition\n", - &target); + &target.target_ip); return -EINVAL; } - if (bond_get_targets_ip(targets, target) != -1) { /* dup */ + if (bond_get_targets_ip(targets, target.target_ip) != -1) { /* dup */ netdev_err(bond->dev, "ARP target %pI4 is already present\n", - &target); + &target.target_ip); return -EINVAL; } @@ -1187,43 +1192,46 @@ static int _bond_option_arp_ip_target_add(struct bonding *bond, __be32 target) return -EINVAL; } - netdev_dbg(bond->dev, "Adding ARP target %pI4\n", &target); + netdev_dbg(bond->dev, "Adding ARP target %s\n", + bond_arp_target_to_string(&target, pbuf, sizeof(pbuf))); _bond_options_arp_ip_target_set(bond, ind, target, jiffies); return 0; } -static int bond_option_arp_ip_target_add(struct bonding *bond, __be32 target) +static int bond_option_arp_ip_target_add(struct bonding *bond, + struct bond_arp_target target) { return _bond_option_arp_ip_target_add(bond, target); } -static int bond_option_arp_ip_target_rem(struct bonding *bond, __be32 target) +static int bond_option_arp_ip_target_rem(struct bonding *bond, + struct bond_arp_target target) { struct bond_arp_target *targets = bond->params.arp_targets; + unsigned long *targets_rx; struct list_head *iter; struct slave *slave; - unsigned long *targets_rx; int ind, i; - if (!bond_is_ip_target_ok(target)) { + if (!bond_is_ip_target_ok(target.target_ip)) { netdev_err(bond->dev, "invalid ARP target %pI4 specified for removal\n", - &target); + &target.target_ip); return -EINVAL; } - ind = bond_get_targets_ip(targets, target); + ind = bond_get_targets_ip(targets, target.target_ip); if (ind == -1) { netdev_err(bond->dev, "unable to remove nonexistent ARP target %pI4\n", - &target); + &target.target_ip); return -EINVAL; } if (ind == 0 && !targets[1].target_ip && bond->params.arp_interval) netdev_warn(bond->dev, "Removing last arp target with arp_interval on\n"); - netdev_dbg(bond->dev, "Removing ARP target %pI4\n", &target); + netdev_dbg(bond->dev, "Removing ARP target %pI4\n", &target.target_ip); bond_for_each_slave(bond, slave, iter) { targets_rx = slave->target_last_arp_rx; @@ -1231,30 +1239,77 @@ static int bond_option_arp_ip_target_rem(struct bonding *bond, __be32 target) targets_rx[i] = targets_rx[i+1]; targets_rx[i] = 0; } - for (i = ind; (i < BOND_MAX_ARP_TARGETS - 1) && targets[i + 1].target_ip; i++) - targets[i] = targets[i+1]; + + bond_free_vlan_tag(&targets[ind]); + + for (i = ind; (i < BOND_MAX_ARP_TARGETS - 1) && targets[i + 1].target_ip; i++) { + targets[i].target_ip = targets[i + 1].target_ip; + targets[i].tags = targets[i + 1].tags; + targets[i].flags = targets[i + 1].flags; + } targets[i].target_ip = 0; + targets[i].flags = 0; + targets[i].tags = NULL; return 0; } void bond_option_arp_ip_targets_clear(struct bonding *bond) { + struct bond_arp_target empty_target = {}; int i; for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) - _bond_options_arp_ip_target_set(bond, i, 0, 0); + _bond_options_arp_ip_target_set(bond, i, empty_target, 0); +} + +/** + * bond_validate_tags - validate an array of bond_vlan_tag. + * @tags: the array to validate + * @len: the length in bytes of @tags + * + * Validate that @tags points to a valid array of struct bond_vlan_tag. + * Returns: the length of the validated bytes in the array or -1 if no + * valid list is found. + */ +static int bond_validate_tags(struct bond_vlan_tag *tags, size_t len) +{ + size_t i, ntags = 0; + + if (len == 0 || !tags) + return 0; + + if (len % sizeof(struct bond_vlan_tag) != 0) + return -1; + + for (i = 0; i <= len; i = i + sizeof(struct bond_vlan_tag)) { + if (ntags > BOND_MAX_VLAN_TAGS) + break; + + if (tags->vlan_proto == BOND_VLAN_PROTO_NONE) + return i + sizeof(struct bond_vlan_tag); + + if (!tags->vlan_id || tags->vlan_id >= VLAN_VID_MASK) + break; + tags++; + ntags++; + } + return -1; } static int bond_option_arp_ip_targets_set(struct bonding *bond, const struct bond_opt_value *newval) { - int ret = -EPERM; - __be32 target; + size_t len = (size_t)newval->extra_len; + char *extra = (char *)newval->extra; + struct bond_arp_target target; + int size, ret = -EPERM; if (newval->string) { + /* Adding or removing arp_ip_target from sysfs */ if (strlen(newval->string) < 1 || - !in4_pton(newval->string + 1, -1, (u8 *)&target, -1, NULL)) { + !in4_pton(newval->string + 1, -1, (u8 *)&target.target_ip, + -1, NULL)) { netdev_err(bond->dev, "invalid ARP target specified\n"); return ret; } @@ -1265,8 +1320,29 @@ static int bond_option_arp_ip_targets_set(struct bonding *bond, else netdev_err(bond->dev, "no command found in arp_ip_targets file - use + or -\n"); } else { - target = newval->value; + /* Adding arp_ip_target from netlink. aka: ip command */ + memcpy(&target.target_ip, newval->extra, sizeof(__be32)); + len = len - sizeof(target.target_ip); + extra = extra + sizeof(target.target_ip); + + size = bond_validate_tags((struct bond_vlan_tag *)extra, len); + + if (size > 0) { + target.tags = kmalloc((size_t)size, GFP_ATOMIC); + if (!target.tags) + return -ENOMEM; + memcpy(target.tags, extra, size); + target.flags |= BOND_TARGET_USERTAGS; + } else if (size == -1) { + netdev_err(bond->dev, "Invalid list of vlans provided with %pI4\n", + &target.target_ip); + return -EINVAL; + } + ret = bond_option_arp_ip_target_add(bond, target); + + if (ret) + kfree(target.tags); } return ret; -- 2.50.1 bond_arp_send_all() will pass the vlan tags supplied by the user to bond_arp_send(). If vlan tags have not been supplied the vlans in the path to the target will be discovered by bond_verify_device_path(). bond_uninit() is also updated to free vlan tags when a bond is destroyed. Signed-off-by: David Wilder --- drivers/net/bonding/bond_main.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 57cf4585816d..8ef8b062d6f3 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -3064,18 +3064,21 @@ struct bond_vlan_tag *bond_verify_device_path(struct net_device *start_dev, static void bond_arp_send_all(struct bonding *bond, struct slave *slave) { - struct rtable *rt; - struct bond_vlan_tag *tags; struct bond_arp_target *targets = bond->params.arp_targets; + char pbuf[BOND_OPTION_STRING_MAX_SIZE]; + struct bond_vlan_tag *tags; __be32 target_ip, addr; + struct rtable *rt; + u32 flags; int i; for (i = 0; i < BOND_MAX_ARP_TARGETS && targets[i].target_ip; i++) { target_ip = targets[i].target_ip; tags = targets[i].tags; + flags = targets[i].flags; - slave_dbg(bond->dev, slave->dev, "%s: target %pI4\n", - __func__, &target_ip); + slave_dbg(bond->dev, slave->dev, "%s: target %s\n", __func__, + bond_arp_target_to_string(&targets[i], pbuf, sizeof(pbuf))); /* Find out through which dev should the packet go */ rt = ip_route_output(dev_net(bond->dev), target_ip, 0, 0, 0, @@ -3097,9 +3100,11 @@ static void bond_arp_send_all(struct bonding *bond, struct slave *slave) if (rt->dst.dev == bond->dev) goto found; - rcu_read_lock(); - tags = bond_verify_device_path(bond->dev, rt->dst.dev, 0); - rcu_read_unlock(); + if (!tags) { + rcu_read_lock(); + tags = bond_verify_device_path(bond->dev, rt->dst.dev, 0); + rcu_read_unlock(); + } if (!IS_ERR_OR_NULL(tags)) goto found; @@ -3115,7 +3120,8 @@ static void bond_arp_send_all(struct bonding *bond, struct slave *slave) addr = bond_confirm_addr(rt->dst.dev, target_ip, 0); ip_rt_put(rt); bond_arp_send(slave, ARPOP_REQUEST, target_ip, addr, tags); - kfree(tags); + if (!(flags & BOND_TARGET_USERTAGS)) + kfree(tags); } } @@ -6047,6 +6053,7 @@ static void bond_uninit(struct net_device *bond_dev) bond_for_each_slave(bond, slave, iter) __bond_release_one(bond_dev, slave->dev, true, true); netdev_info(bond_dev, "Released all slaves\n"); + // bond_free_vlan_tags(bond->params.arp_targets); #ifdef CONFIG_XFRM_OFFLOAD mutex_destroy(&bond->ipsec_lock); -- 2.50.1 Updated bond_fill_info() to support extended arp_ip_target format. Forward and backward compatibility between the kernel and iproute2 is preserved. Signed-off-by: David Wilder --- drivers/net/bonding/bond_netlink.c | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c index 97fdbd962513..349b5525d007 100644 --- a/drivers/net/bonding/bond_netlink.c +++ b/drivers/net/bonding/bond_netlink.c @@ -676,6 +676,7 @@ static int bond_fill_info(struct sk_buff *skb, const struct net_device *bond_dev) { struct bonding *bond = netdev_priv(bond_dev); + struct bond_arp_target *arptargets; unsigned int packets_per_slave; int ifindex, i, targets_added; struct nlattr *targets; @@ -714,12 +715,31 @@ static int bond_fill_info(struct sk_buff *skb, goto nla_put_failure; targets_added = 0; - for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) { - if (bond->params.arp_targets[i].target_ip) { - if (nla_put_be32(skb, i, bond->params.arp_targets[i].target_ip)) - goto nla_put_failure; - targets_added = 1; + + arptargets = bond->params.arp_targets; + for (i = 0; i < BOND_MAX_ARP_TARGETS && arptargets[i].target_ip ; i++) { + struct data { + __be32 addr; + struct bond_vlan_tag vlans[BOND_MAX_VLAN_TAGS + 1]; + } __packed data; + int level, size; + + data.addr = arptargets[i].target_ip; + size = sizeof(__be32); + targets_added = 1; + + if (arptargets[i].flags & BOND_TARGET_USERTAGS) { + for (level = 0; level < BOND_MAX_VLAN_TAGS + 1 ; level++) { + data.vlans[level].vlan_proto = arptargets[i].tags[level].vlan_proto; + data.vlans[level].vlan_id = arptargets[i].tags[level].vlan_id; + size = size + sizeof(struct bond_vlan_tag); + if (arptargets[i].tags[level].vlan_proto == BOND_VLAN_PROTO_NONE) + break; + } } + + if (nla_put(skb, i, size, &data)) + goto nla_put_failure; } if (targets_added) -- 2.50.1 Selftest provided as a functional test for the arp_ip_target parameter both with and without user supplied vlan tags. Bonding documentation has been updated for the arp_ip_target option. Signed-off-by: David Wilder --- Documentation/networking/bonding.rst | 11 + .../selftests/drivers/net/bonding/Makefile | 3 +- .../drivers/net/bonding/bond-arp-ip-target.sh | 205 ++++++++++++++++++ 3 files changed, 218 insertions(+), 1 deletion(-) create mode 100755 tools/testing/selftests/drivers/net/bonding/bond-arp-ip-target.sh diff --git a/Documentation/networking/bonding.rst b/Documentation/networking/bonding.rst index e700bf1d095c..08a3191a0322 100644 --- a/Documentation/networking/bonding.rst +++ b/Documentation/networking/bonding.rst @@ -330,6 +330,17 @@ arp_ip_target maximum number of targets that can be specified is 16. The default value is no IP addresses. + When an arp_ip_target is configured the bonding driver will + attempt to automatically determine what vlans the arp probe will + pass through. This process of gathering vlan tags is required + for the arp probe to be sent. However, in some configurations + this process may fail. In these cases you may manually + supply a list of vlan tags. To specify a list of vlan tags + append the ipv4 address with [tag1/tag2...]. For example: + arp_ip_target=10.0.0.1[10]. If you simply need to disable the + vlan discovery process you may provide an empty list, for example: + arp_ip_target=10.0.0.1[]. + ns_ip6_target Specifies the IPv6 addresses to use as IPv6 monitoring peers when diff --git a/tools/testing/selftests/drivers/net/bonding/Makefile b/tools/testing/selftests/drivers/net/bonding/Makefile index 3462783ed3ac..44965026fe45 100644 --- a/tools/testing/selftests/drivers/net/bonding/Makefile +++ b/tools/testing/selftests/drivers/net/bonding/Makefile @@ -12,7 +12,8 @@ TEST_PROGS := \ bond-eth-type-change.sh \ bond_macvlan_ipvlan.sh \ bond_passive_lacp.sh \ - bond_lacp_prio.sh + bond_lacp_prio.sh \ + bond-arp-ip-target.sh TEST_FILES := \ lag_lib.sh \ diff --git a/tools/testing/selftests/drivers/net/bonding/bond-arp-ip-target.sh b/tools/testing/selftests/drivers/net/bonding/bond-arp-ip-target.sh new file mode 100755 index 000000000000..40cbd56c00f9 --- /dev/null +++ b/tools/testing/selftests/drivers/net/bonding/bond-arp-ip-target.sh @@ -0,0 +1,205 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Test bonding arp_ip_target. +# Topology for Bond mode 1,5,6 testing +# +# +-------------------------+ +# | | Server +# | bond0.10.20 | 192.20.2.1/24 +# | | | +# | bond0.10 | 192.10.2.1/24 +# | | | +# | bond0 | 192.0.2.1/24 +# | | | +# | + | +# | eth0 | eth1 | +# | +---+---+ | +# | | | | +# +-------------------------+ +# | | +# +-------------------------+ +# | | | | +# | +---+-------+---+ | Gateway +# | | br0 | | +# | +-------+-------+ | +# | | | +# +-------------------------+ +# | +# +-------------------------+ +# | | | Client +# | eth0 | 192.0.0.2/24 +# | | | +# | eth0.10 | 192.10.10.2/24 +# | | | +# | eth0.10.20 | 192.20.20.2/24 +# +-------------------------+ + +# shellcheck disable=SC2317 + +lib_dir=$(dirname "$0") + +# shellcheck source=/dev/null # Ignore source warning. +source "${lib_dir}"/bond_topo_2d1c.sh + +# shellcheck disable=SC2154 # Ignore unassigned referenced warning. +echo "${c_ns}" "${s_ns}" > /dev/null + +DEBUG=${DEBUG:-0} +test "${DEBUG}" -ne 0 && set -x + +# vlan subnets +c_ip4="192.0.2.10" +c_ip4v10="192.10.2.10" +c_ip4v20="192.20.2.10" + +export ALL_TESTS=" + no_vlan_hints + with_vlan_hints +" + +# Build stacked vlans on top of an interface. +stack_vlans() +{ + RET=0 + local interface="$1" + local ns=$2 + local last="$interface" + local tags="10 20" + + if ! ip -n "${ns}" link show "${interface}" > /dev/null; then + RET=1 + msg="Failed to create ${interface}" + return 1 + fi + + if [ "$ns" == "${s_ns}" ]; then host=1; else host=10;fi + + for tag in $tags; do + ip -n "${ns}" link add link "$last" name "$last"."$tag" type vlan id "$tag" + ip -n "${ns}" address add 192."$tag".2."$host"/24 dev "$last"."$tag" + ip -n "${ns}" link set up dev "$last"."$tag" + last=$last.$tag + done +} + +wait_for_arp_request() +{ + local target=$1 + local ip + local interface + + ip=$(echo "${target}" | awk -F "[" '{print $1}') + interface="$(ip -n "${c_ns}" -br addr show | grep "${ip}" | awk -F @ '{print $1}')" + + tc -n "${c_ns}" qdisc add dev "${interface}" clsact + tc -n "${c_ns}" filter add dev "${interface}" ingress protocol arp \ + handle 101 flower skip_hw arp_op request arp_tip "${ip}" action pass + + slowwait_for_counter 5 5 tc_rule_handle_stats_get \ + "dev ${interface} ingress" 101 ".packets" "-n ${c_ns}" &> /dev/null || RET=1 + + tc -n "${c_ns}" filter del dev "${interface}" ingress + tc -n "${c_ns}" qdisc del dev "${interface}" clsact + + if [ "$RET" -ne 0 ]; then + msg="Arp probe not received by ${interface}" + return 1 + fi +} + +# Check for link flapping. +# First verify the arp requests are being received +# by the target. Then verify that the Link Failure +# Counts are not increasing over time. +# Arp probes are sent every 100ms, two probes must +# be missed to trigger a slave failure. A one second +# wait should be sufficient. +check_failure_count() +{ + local bond=$1 + local target=$2 + local proc_file=/proc/net/bonding/${bond} + + wait_for_arp_request "${target}" || return 1 + + LinkFailureCount1=$(ip netns exec "${s_ns}" grep -F "Link Failure Count" "${proc_file}" \ + | awk -F: '{ sum += $2 } END { print sum }') + sleep 1 + LinkFailureCount2=$(ip netns exec "${s_ns}" grep -F "Link Failure Count" "${proc_file}" \ + | awk -F: '{ sum += $2 } END { print sum }') + + [ "$LinkFailureCount1" != "$LinkFailureCount2" ] && RET=1 +} + +setup_bond_topo() +{ + setup_prepare + setup_wait + stack_vlans bond0 "${s_ns}" + stack_vlans eth0 "${c_ns}" +} + +skip_with_vlan_hints() +{ + # check if iproute supports arp_ip_target with vlans option. + if ! ip -n "${s_ns}" link add bond2 type bond arp_ip_target 10.0.0.1[10]; then + ip -n "${s_ns}" link del bond2 2> /dev/null + return 0 + fi + return 1 +} + +no_vlan_hints() +{ + RET=0 + local targets="${c_ip4} ${c_ip4v10} ${c_ip4v20}" + local target + msg="" + + for target in $targets; do + bond_reset "mode $mode arp_interval 100 arp_ip_target ${target}" + stack_vlans bond0 "${s_ns}" + if [ "$RET" -ne 0 ]; then + log_test "no_vlan_hints" "${msg}" + return + fi + check_failure_count bond0 "${target}" + log_test "arp_ip_target=${target} ${msg}" + done +} + +with_vlan_hints() +{ + RET=0 + local targets="${c_ip4}[] ${c_ip4v10}[10] ${c_ip4v20}[10/20]" + local target + msg="" + + if skip_with_vlan_hints; then + log_test_skip "skip_with_vlan_hints" \ + "Installed iproute doesn't support extended arp_ip_target options." + return 0 + fi + + for target in $targets; do + bond_reset "mode $mode arp_interval 100 arp_ip_target ${target}" + stack_vlans bond0 "${s_ns}" + if [ "$RET" -ne 0 ]; then + log_test "no_vlan_hints" "${msg}" + return + fi + + check_failure_count bond0 "${target}" + log_test "arp_ip_target=${target} ${msg}" + done +} + +trap cleanup EXIT + +mode=active-backup + +setup_bond_topo +tests_run + +exit "$EXIT_STATUS" -- 2.50.1