diff options
Diffstat (limited to 'drivers/net/team')
-rw-r--r-- | drivers/net/team/Kconfig | 11 | ||||
-rw-r--r-- | drivers/net/team/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/team/team.c | 523 | ||||
-rw-r--r-- | drivers/net/team/team_mode_activebackup.c | 20 | ||||
-rw-r--r-- | drivers/net/team/team_mode_loadbalance.c | 174 | ||||
-rw-r--r-- | drivers/net/team/team_mode_roundrobin.c | 2 |
6 files changed, 621 insertions, 110 deletions
diff --git a/drivers/net/team/Kconfig b/drivers/net/team/Kconfig index 248a144033c..89024d5fc33 100644 --- a/drivers/net/team/Kconfig +++ b/drivers/net/team/Kconfig @@ -40,4 +40,15 @@ config NET_TEAM_MODE_ACTIVEBACKUP To compile this team mode as a module, choose M here: the module will be called team_mode_activebackup. +config NET_TEAM_MODE_LOADBALANCE + tristate "Load-balance mode support" + depends on NET_TEAM + ---help--- + This mode provides load balancing functionality. Tx port selection + is done using BPF function set up from userspace (bpf_hash_func + option) + + To compile this team mode as a module, choose M here: the module + will be called team_mode_loadbalance. + endif # NET_TEAM diff --git a/drivers/net/team/Makefile b/drivers/net/team/Makefile index 85f2028a87a..fb9f4c1c51f 100644 --- a/drivers/net/team/Makefile +++ b/drivers/net/team/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_NET_TEAM) += team.o obj-$(CONFIG_NET_TEAM_MODE_ROUNDROBIN) += team_mode_roundrobin.o obj-$(CONFIG_NET_TEAM_MODE_ACTIVEBACKUP) += team_mode_activebackup.o +obj-$(CONFIG_NET_TEAM_MODE_LOADBALANCE) += team_mode_loadbalance.o diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c index 8f81805c682..c61ae35a53c 100644 --- a/drivers/net/team/team.c +++ b/drivers/net/team/team.c @@ -65,7 +65,7 @@ static int __set_port_mac(struct net_device *port_dev, return dev_set_mac_address(port_dev, &addr); } -int team_port_set_orig_mac(struct team_port *port) +static int team_port_set_orig_mac(struct team_port *port) { return __set_port_mac(port->dev, port->orig.dev_addr); } @@ -76,12 +76,26 @@ int team_port_set_team_mac(struct team_port *port) } EXPORT_SYMBOL(team_port_set_team_mac); +static void team_refresh_port_linkup(struct team_port *port) +{ + port->linkup = port->user.linkup_enabled ? port->user.linkup : + port->state.linkup; +} /******************* * Options handling *******************/ -struct team_option *__team_find_option(struct team *team, const char *opt_name) +struct team_option_inst { /* One for each option instance */ + struct list_head list; + struct team_option *option; + struct team_port *port; /* != NULL if per-port */ + bool changed; + bool removed; +}; + +static struct team_option *__team_find_option(struct team *team, + const char *opt_name) { struct team_option *option; @@ -92,9 +106,121 @@ struct team_option *__team_find_option(struct team *team, const char *opt_name) return NULL; } -int __team_options_register(struct team *team, - const struct team_option *option, - size_t option_count) +static int __team_option_inst_add(struct team *team, struct team_option *option, + struct team_port *port) +{ + struct team_option_inst *opt_inst; + + opt_inst = kmalloc(sizeof(*opt_inst), GFP_KERNEL); + if (!opt_inst) + return -ENOMEM; + opt_inst->option = option; + opt_inst->port = port; + opt_inst->changed = true; + opt_inst->removed = false; + list_add_tail(&opt_inst->list, &team->option_inst_list); + return 0; +} + +static void __team_option_inst_del(struct team_option_inst *opt_inst) +{ + list_del(&opt_inst->list); + kfree(opt_inst); +} + +static void __team_option_inst_del_option(struct team *team, + struct team_option *option) +{ + struct team_option_inst *opt_inst, *tmp; + + list_for_each_entry_safe(opt_inst, tmp, &team->option_inst_list, list) { + if (opt_inst->option == option) + __team_option_inst_del(opt_inst); + } +} + +static int __team_option_inst_add_option(struct team *team, + struct team_option *option) +{ + struct team_port *port; + int err; + + if (!option->per_port) + return __team_option_inst_add(team, option, 0); + + list_for_each_entry(port, &team->port_list, list) { + err = __team_option_inst_add(team, option, port); + if (err) + goto inst_del_option; + } + return 0; + +inst_del_option: + __team_option_inst_del_option(team, option); + return err; +} + +static void __team_option_inst_mark_removed_option(struct team *team, + struct team_option *option) +{ + struct team_option_inst *opt_inst; + + list_for_each_entry(opt_inst, &team->option_inst_list, list) { + if (opt_inst->option == option) { + opt_inst->changed = true; + opt_inst->removed = true; + } + } +} + +static void __team_option_inst_del_port(struct team *team, + struct team_port *port) +{ + struct team_option_inst *opt_inst, *tmp; + + list_for_each_entry_safe(opt_inst, tmp, &team->option_inst_list, list) { + if (opt_inst->option->per_port && + opt_inst->port == port) + __team_option_inst_del(opt_inst); + } +} + +static int __team_option_inst_add_port(struct team *team, + struct team_port *port) +{ + struct team_option *option; + int err; + + list_for_each_entry(option, &team->option_list, list) { + if (!option->per_port) + continue; + err = __team_option_inst_add(team, option, port); + if (err) + goto inst_del_port; + } + return 0; + +inst_del_port: + __team_option_inst_del_port(team, port); + return err; +} + +static void __team_option_inst_mark_removed_port(struct team *team, + struct team_port *port) +{ + struct team_option_inst *opt_inst; + + list_for_each_entry(opt_inst, &team->option_inst_list, list) { + if (opt_inst->port == port) { + opt_inst->changed = true; + opt_inst->removed = true; + } + } +} + +static int __team_options_register(struct team *team, + const struct team_option *option, + size_t option_count) { int i; struct team_option **dst_opts; @@ -107,26 +233,32 @@ int __team_options_register(struct team *team, for (i = 0; i < option_count; i++, option++) { if (__team_find_option(team, option->name)) { err = -EEXIST; - goto rollback; + goto alloc_rollback; } dst_opts[i] = kmemdup(option, sizeof(*option), GFP_KERNEL); if (!dst_opts[i]) { err = -ENOMEM; - goto rollback; + goto alloc_rollback; } } for (i = 0; i < option_count; i++) { - dst_opts[i]->changed = true; - dst_opts[i]->removed = false; + err = __team_option_inst_add_option(team, dst_opts[i]); + if (err) + goto inst_rollback; list_add_tail(&dst_opts[i]->list, &team->option_list); } kfree(dst_opts); return 0; -rollback: - for (i = 0; i < option_count; i++) +inst_rollback: + for (i--; i >= 0; i--) + __team_option_inst_del_option(team, dst_opts[i]); + + i = option_count - 1; +alloc_rollback: + for (i--; i >= 0; i--) kfree(dst_opts[i]); kfree(dst_opts); @@ -143,10 +275,8 @@ static void __team_options_mark_removed(struct team *team, struct team_option *del_opt; del_opt = __team_find_option(team, option->name); - if (del_opt) { - del_opt->changed = true; - del_opt->removed = true; - } + if (del_opt) + __team_option_inst_mark_removed_option(team, del_opt); } } @@ -161,6 +291,7 @@ static void __team_options_unregister(struct team *team, del_opt = __team_find_option(team, option->name); if (del_opt) { + __team_option_inst_del_option(team, del_opt); list_del(&del_opt->list); kfree(del_opt); } @@ -193,22 +324,42 @@ void team_options_unregister(struct team *team, } EXPORT_SYMBOL(team_options_unregister); -static int team_option_get(struct team *team, struct team_option *option, - void *arg) +static int team_option_port_add(struct team *team, struct team_port *port) +{ + int err; + + err = __team_option_inst_add_port(team, port); + if (err) + return err; + __team_options_change_check(team); + return 0; +} + +static void team_option_port_del(struct team *team, struct team_port *port) +{ + __team_option_inst_mark_removed_port(team, port); + __team_options_change_check(team); + __team_option_inst_del_port(team, port); +} + +static int team_option_get(struct team *team, + struct team_option_inst *opt_inst, + struct team_gsetter_ctx *ctx) { - return option->getter(team, arg); + return opt_inst->option->getter(team, ctx); } -static int team_option_set(struct team *team, struct team_option *option, - void *arg) +static int team_option_set(struct team *team, + struct team_option_inst *opt_inst, + struct team_gsetter_ctx *ctx) { int err; - err = option->setter(team, arg); + err = opt_inst->option->setter(team, ctx); if (err) return err; - option->changed = true; + opt_inst->changed = true; __team_options_change_check(team); return err; } @@ -408,6 +559,8 @@ static int team_change_mode(struct team *team, const char *kind) * Rx path frame handler ************************/ +static bool team_port_enabled(struct team_port *port); + /* note: already called with rcu_read_lock */ static rx_handler_result_t team_handle_frame(struct sk_buff **pskb) { @@ -424,8 +577,12 @@ static rx_handler_result_t team_handle_frame(struct sk_buff **pskb) port = team_port_get_rcu(skb->dev); team = port->team; - - res = team->ops.receive(team, port, skb); + if (!team_port_enabled(port)) { + /* allow exact match delivery for disabled ports */ + res = RX_HANDLER_EXACT; + } else { + res = team->ops.receive(team, port, skb); + } if (res == RX_HANDLER_ANOTHER) { struct team_pcpu_stats *pcpu_stats; @@ -461,17 +618,25 @@ static bool team_port_find(const struct team *team, return false; } +static bool team_port_enabled(struct team_port *port) +{ + return port->index != -1; +} + /* - * Add/delete port to the team port list. Write guarded by rtnl_lock. - * Takes care of correct port->index setup (might be racy). + * Enable/disable port by adding to enabled port hashlist and setting + * port->index (Might be racy so reader could see incorrect ifindex when + * processing a flying packet, but that is not a problem). Write guarded + * by team->lock. */ -static void team_port_list_add_port(struct team *team, - struct team_port *port) +static void team_port_enable(struct team *team, + struct team_port *port) { - port->index = team->port_count++; + if (team_port_enabled(port)) + return; + port->index = team->en_port_count++; hlist_add_head_rcu(&port->hlist, team_port_index_hash(team, port->index)); - list_add_tail_rcu(&port->list, &team->port_list); } static void __reconstruct_port_hlist(struct team *team, int rm_index) @@ -479,7 +644,7 @@ static void __reconstruct_port_hlist(struct team *team, int rm_index) int i; struct team_port *port; - for (i = rm_index + 1; i < team->port_count; i++) { + for (i = rm_index + 1; i < team->en_port_count; i++) { port = team_get_port_by_index(team, i); hlist_del_rcu(&port->hlist); port->index--; @@ -488,15 +653,17 @@ static void __reconstruct_port_hlist(struct team *team, int rm_index) } } -static void team_port_list_del_port(struct team *team, - struct team_port *port) +static void team_port_disable(struct team *team, + struct team_port *port) { int rm_index = port->index; + if (!team_port_enabled(port)) + return; hlist_del_rcu(&port->hlist); - list_del_rcu(&port->list); __reconstruct_port_hlist(team, rm_index); - team->port_count--; + team->en_port_count--; + port->index = -1; } #define TEAM_VLAN_FEATURES (NETIF_F_ALL_CSUM | NETIF_F_SG | \ @@ -642,7 +809,16 @@ static int team_port_add(struct team *team, struct net_device *port_dev) goto err_handler_register; } - team_port_list_add_port(team, port); + err = team_option_port_add(team, port); + if (err) { + netdev_err(dev, "Device %s failed to add per-port options\n", + portname); + goto err_option_port_add; + } + + port->index = -1; + team_port_enable(team, port); + list_add_tail_rcu(&port->list, &team->port_list); team_adjust_ops(team); __team_compute_features(team); __team_port_change_check(port, !!netif_carrier_ok(port_dev)); @@ -651,6 +827,9 @@ static int team_port_add(struct team *team, struct net_device *port_dev) return 0; +err_option_port_add: + netdev_rx_handler_unregister(port_dev); + err_handler_register: netdev_set_master(port_dev, NULL); @@ -688,8 +867,10 @@ static int team_port_del(struct team *team, struct net_device *port_dev) port->removed = true; __team_port_change_check(port, false); - team_port_list_del_port(team, port); + team_port_disable(team, port); + list_del_rcu(&port->list); team_adjust_ops(team); + team_option_port_del(team, port); netdev_rx_handler_unregister(port_dev); netdev_set_master(port_dev, NULL); vlan_vids_del_by_dev(port_dev, dev); @@ -712,19 +893,66 @@ static int team_port_del(struct team *team, struct net_device *port_dev) static const char team_no_mode_kind[] = "*NOMODE*"; -static int team_mode_option_get(struct team *team, void *arg) +static int team_mode_option_get(struct team *team, struct team_gsetter_ctx *ctx) +{ + ctx->data.str_val = team->mode ? team->mode->kind : team_no_mode_kind; + return 0; +} + +static int team_mode_option_set(struct team *team, struct team_gsetter_ctx *ctx) +{ + return team_change_mode(team, ctx->data.str_val); +} + +static int team_port_en_option_get(struct team *team, + struct team_gsetter_ctx *ctx) +{ + ctx->data.bool_val = team_port_enabled(ctx->port); + return 0; +} + +static int team_port_en_option_set(struct team *team, + struct team_gsetter_ctx *ctx) +{ + if (ctx->data.bool_val) + team_port_enable(team, ctx->port); + else + team_port_disable(team, ctx->port); + return 0; +} + +static int team_user_linkup_option_get(struct team *team, + struct team_gsetter_ctx *ctx) +{ + ctx->data.bool_val = ctx->port->user.linkup; + return 0; +} + +static int team_user_linkup_option_set(struct team *team, + struct team_gsetter_ctx *ctx) +{ + ctx->port->user.linkup = ctx->data.bool_val; + team_refresh_port_linkup(ctx->port); + return 0; +} + +static int team_user_linkup_en_option_get(struct team *team, + struct team_gsetter_ctx *ctx) { - const char **str = arg; + struct team_port *port = ctx->port; - *str = team->mode ? team->mode->kind : team_no_mode_kind; + ctx->data.bool_val = port->user.linkup_enabled; return 0; } -static int team_mode_option_set(struct team *team, void *arg) +static int team_user_linkup_en_option_set(struct team *team, + struct team_gsetter_ctx *ctx) { - const char **str = arg; + struct team_port *port = ctx->port; - return team_change_mode(team, *str); + port->user.linkup_enabled = ctx->data.bool_val; + team_refresh_port_linkup(ctx->port); + return 0; } static const struct team_option team_options[] = { @@ -734,6 +962,27 @@ static const struct team_option team_options[] = { .getter = team_mode_option_get, .setter = team_mode_option_set, }, + { + .name = "enabled", + .type = TEAM_OPTION_TYPE_BOOL, + .per_port = true, + .getter = team_port_en_option_get, + .setter = team_port_en_option_set, + }, + { + .name = "user_linkup", + .type = TEAM_OPTION_TYPE_BOOL, + .per_port = true, + .getter = team_user_linkup_option_get, + .setter = team_user_linkup_option_set, + }, + { + .name = "user_linkup_enabled", + .type = TEAM_OPTION_TYPE_BOOL, + .per_port = true, + .getter = team_user_linkup_en_option_get, + .setter = team_user_linkup_en_option_set, + }, }; static int team_init(struct net_device *dev) @@ -750,12 +999,13 @@ static int team_init(struct net_device *dev) return -ENOMEM; for (i = 0; i < TEAM_PORT_HASHENTRIES; i++) - INIT_HLIST_HEAD(&team->port_hlist[i]); + INIT_HLIST_HEAD(&team->en_port_hlist[i]); INIT_LIST_HEAD(&team->port_list); team_adjust_ops(team); INIT_LIST_HEAD(&team->option_list); + INIT_LIST_HEAD(&team->option_inst_list); err = team_options_register(team, team_options, ARRAY_SIZE(team_options)); if (err) goto err_options_register; @@ -1145,10 +1395,7 @@ team_nl_option_policy[TEAM_ATTR_OPTION_MAX + 1] = { }, [TEAM_ATTR_OPTION_CHANGED] = { .type = NLA_FLAG }, [TEAM_ATTR_OPTION_TYPE] = { .type = NLA_U8 }, - [TEAM_ATTR_OPTION_DATA] = { - .type = NLA_BINARY, - .len = TEAM_STRING_MAX_LEN, - }, + [TEAM_ATTR_OPTION_DATA] = { .type = NLA_BINARY }, }; static int team_nl_cmd_noop(struct sk_buff *skb, struct genl_info *info) @@ -1241,46 +1488,86 @@ static int team_nl_fill_options_get(struct sk_buff *skb, { struct nlattr *option_list; void *hdr; - struct team_option *option; + struct team_option_inst *opt_inst; + int err; hdr = genlmsg_put(skb, pid, seq, &team_nl_family, flags, TEAM_CMD_OPTIONS_GET); if (IS_ERR(hdr)) return PTR_ERR(hdr); - NLA_PUT_U32(skb, TEAM_ATTR_TEAM_IFINDEX, team->dev->ifindex); + if (nla_put_u32(skb, TEAM_ATTR_TEAM_IFINDEX, team->dev->ifindex)) + goto nla_put_failure; option_list = nla_nest_start(skb, TEAM_ATTR_LIST_OPTION); if (!option_list) return -EMSGSIZE; - list_for_each_entry(option, &team->option_list, list) { + list_for_each_entry(opt_inst, &team->option_inst_list, list) { struct nlattr *option_item; - long arg; + struct team_option *option = opt_inst->option; + struct team_gsetter_ctx ctx; /* Include only changed options if fill all mode is not on */ - if (!fillall && !option->changed) + if (!fillall && !opt_inst->changed) continue; option_item = nla_nest_start(skb, TEAM_ATTR_ITEM_OPTION); if (!option_item) goto nla_put_failure; - NLA_PUT_STRING(skb, TEAM_ATTR_OPTION_NAME, option->name); - if (option->changed) { - NLA_PUT_FLAG(skb, TEAM_ATTR_OPTION_CHANGED); - option->changed = false; + if (nla_put_string(skb, TEAM_ATTR_OPTION_NAME, option->name)) + goto nla_put_failure; + if (opt_inst->changed) { + if (nla_put_flag(skb, TEAM_ATTR_OPTION_CHANGED)) + goto nla_put_failure; + opt_inst->changed = false; } - if (option->removed) - NLA_PUT_FLAG(skb, TEAM_ATTR_OPTION_REMOVED); + if (opt_inst->removed && + nla_put_flag(skb, TEAM_ATTR_OPTION_REMOVED)) + goto nla_put_failure; + if (opt_inst->port && + nla_put_u32(skb, TEAM_ATTR_OPTION_PORT_IFINDEX, + opt_inst->port->dev->ifindex)) + goto nla_put_failure; + ctx.port = opt_inst->port; switch (option->type) { case TEAM_OPTION_TYPE_U32: - NLA_PUT_U8(skb, TEAM_ATTR_OPTION_TYPE, NLA_U32); - team_option_get(team, option, &arg); - NLA_PUT_U32(skb, TEAM_ATTR_OPTION_DATA, arg); + if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_U32)) + goto nla_put_failure; + err = team_option_get(team, opt_inst, &ctx); + if (err) + goto errout; + if (nla_put_u32(skb, TEAM_ATTR_OPTION_DATA, + ctx.data.u32_val)) + goto nla_put_failure; break; case TEAM_OPTION_TYPE_STRING: - NLA_PUT_U8(skb, TEAM_ATTR_OPTION_TYPE, NLA_STRING); - team_option_get(team, option, &arg); - NLA_PUT_STRING(skb, TEAM_ATTR_OPTION_DATA, - (char *) arg); + if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_STRING)) + goto nla_put_failure; + err = team_option_get(team, opt_inst, &ctx); + if (err) + goto errout; + if (nla_put_string(skb, TEAM_ATTR_OPTION_DATA, + ctx.data.str_val)) + goto nla_put_failure; + break; + case TEAM_OPTION_TYPE_BINARY: + if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_BINARY)) + goto nla_put_failure; + err = team_option_get(team, opt_inst, &ctx); + if (err) + goto errout; + if (nla_put(skb, TEAM_ATTR_OPTION_DATA, + ctx.data.bin_val.len, ctx.data.bin_val.ptr)) + goto nla_put_failure; + break; + case TEAM_OPTION_TYPE_BOOL: + if (nla_put_u8(skb, TEAM_ATTR_OPTION_TYPE, NLA_FLAG)) + goto nla_put_failure; + err = team_option_get(team, opt_inst, &ctx); + if (err) + goto errout; + if (ctx.data.bool_val && + nla_put_flag(skb, TEAM_ATTR_OPTION_DATA)) + goto nla_put_failure; break; default: BUG(); @@ -1292,8 +1579,10 @@ static int team_nl_fill_options_get(struct sk_buff *skb, return genlmsg_end(skb, hdr); nla_put_failure: + err = -EMSGSIZE; +errout: genlmsg_cancel(skb, hdr); - return -EMSGSIZE; + return err; } static int team_nl_fill_options_get_all(struct sk_buff *skb, @@ -1339,9 +1628,12 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) } nla_for_each_nested(nl_option, info->attrs[TEAM_ATTR_LIST_OPTION], i) { - struct nlattr *mode_attrs[TEAM_ATTR_OPTION_MAX + 1]; + struct nlattr *opt_attrs[TEAM_ATTR_OPTION_MAX + 1]; + struct nlattr *attr_port_ifindex; + struct nlattr *attr_data; enum team_option_type opt_type; - struct team_option *option; + int opt_port_ifindex = 0; /* != 0 for per-port options */ + struct team_option_inst *opt_inst; char *opt_name; bool opt_found = false; @@ -1349,48 +1641,78 @@ static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) err = -EINVAL; goto team_put; } - err = nla_parse_nested(mode_attrs, TEAM_ATTR_OPTION_MAX, + err = nla_parse_nested(opt_attrs, TEAM_ATTR_OPTION_MAX, nl_option, team_nl_option_policy); if (err) goto team_put; - if (!mode_attrs[TEAM_ATTR_OPTION_NAME] || - !mode_attrs[TEAM_ATTR_OPTION_TYPE] || - !mode_attrs[TEAM_ATTR_OPTION_DATA]) { + if (!opt_attrs[TEAM_ATTR_OPTION_NAME] || + !opt_attrs[TEAM_ATTR_OPTION_TYPE]) { err = -EINVAL; goto team_put; } - switch (nla_get_u8(mode_attrs[TEAM_ATTR_OPTION_TYPE])) { + switch (nla_get_u8(opt_attrs[TEAM_ATTR_OPTION_TYPE])) { case NLA_U32: opt_type = TEAM_OPTION_TYPE_U32; break; case NLA_STRING: opt_type = TEAM_OPTION_TYPE_STRING; break; + case NLA_BINARY: + opt_type = TEAM_OPTION_TYPE_BINARY; + break; + case NLA_FLAG: + opt_type = TEAM_OPTION_TYPE_BOOL; + break; default: goto team_put; } - opt_name = nla_data(mode_attrs[TEAM_ATTR_OPTION_NAME]); - list_for_each_entry(option, &team->option_list, list) { - long arg; - struct nlattr *opt_data_attr; + attr_data = opt_attrs[TEAM_ATTR_OPTION_DATA]; + if (opt_type != TEAM_OPTION_TYPE_BOOL && !attr_data) { + err = -EINVAL; + goto team_put; + } + + opt_name = nla_data(opt_attrs[TEAM_ATTR_OPTION_NAME]); + attr_port_ifindex = opt_attrs[TEAM_ATTR_OPTION_PORT_IFINDEX]; + if (attr_port_ifindex) + opt_port_ifindex = nla_get_u32(attr_port_ifindex); + + list_for_each_entry(opt_inst, &team->option_inst_list, list) { + struct team_option *option = opt_inst->option; + struct team_gsetter_ctx ctx; + int tmp_ifindex; + tmp_ifindex = opt_inst->port ? + opt_inst->port->dev->ifindex : 0; if (option->type != opt_type || - strcmp(option->name, opt_name)) + strcmp(option->name, opt_name) || + tmp_ifindex != opt_port_ifindex) continue; opt_found = true; - opt_data_attr = mode_attrs[TEAM_ATTR_OPTION_DATA]; + ctx.port = opt_inst->port; switch (opt_type) { case TEAM_OPTION_TYPE_U32: - arg = nla_get_u32(opt_data_attr); + ctx.data.u32_val = nla_get_u32(attr_data); break; case TEAM_OPTION_TYPE_STRING: - arg = (long) nla_data(opt_data_attr); + if (nla_len(attr_data) > TEAM_STRING_MAX_LEN) { + err = -EINVAL; + goto team_put; + } + ctx.data.str_val = nla_data(attr_data); + break; + case TEAM_OPTION_TYPE_BINARY: + ctx.data.bin_val.len = nla_len(attr_data); + ctx.data.bin_val.ptr = nla_data(attr_data); + break; + case TEAM_OPTION_TYPE_BOOL: + ctx.data.bool_val = attr_data ? true : false; break; default: BUG(); } - err = team_option_set(team, option, &arg); + err = team_option_set(team, opt_inst, &ctx); if (err) goto team_put; } @@ -1420,7 +1742,8 @@ static int team_nl_fill_port_list_get(struct sk_buff *skb, if (IS_ERR(hdr)) return PTR_ERR(hdr); - NLA_PUT_U32(skb, TEAM_ATTR_TEAM_IFINDEX, team->dev->ifindex); + if (nla_put_u32(skb, TEAM_ATTR_TEAM_IFINDEX, team->dev->ifindex)) + goto nla_put_failure; port_list = nla_nest_start(skb, TEAM_ATTR_LIST_PORT); if (!port_list) return -EMSGSIZE; @@ -1434,17 +1757,20 @@ static int team_nl_fill_port_list_get(struct sk_buff *skb, port_item = nla_nest_start(skb, TEAM_ATTR_ITEM_PORT); if (!port_item) goto nla_put_failure; - NLA_PUT_U32(skb, TEAM_ATTR_PORT_IFINDEX, port->dev->ifindex); + if (nla_put_u32(skb, TEAM_ATTR_PORT_IFINDEX, port->dev->ifindex)) + goto nla_put_failure; if (port->changed) { - NLA_PUT_FLAG(skb, TEAM_ATTR_PORT_CHANGED); + if (nla_put_flag(skb, TEAM_ATTR_PORT_CHANGED)) + goto nla_put_failure; port->changed = false; } - if (port->removed) - NLA_PUT_FLAG(skb, TEAM_ATTR_PORT_REMOVED); - if (port->linkup) - NLA_PUT_FLAG(skb, TEAM_ATTR_PORT_LINKUP); - NLA_PUT_U32(skb, TEAM_ATTR_PORT_SPEED, port->speed); - NLA_PUT_U8(skb, TEAM_ATTR_PORT_DUPLEX, port->duplex); + if ((port->removed && + nla_put_flag(skb, TEAM_ATTR_PORT_REMOVED)) || + (port->state.linkup && + nla_put_flag(skb, TEAM_ATTR_PORT_LINKUP)) || + nla_put_u32(skb, TEAM_ATTR_PORT_SPEED, port->state.speed) || + nla_put_u8(skb, TEAM_ATTR_PORT_DUPLEX, port->state.duplex)) + goto nla_put_failure; nla_nest_end(skb, port_item); } @@ -1603,23 +1929,24 @@ static void __team_port_change_check(struct team_port *port, bool linkup) { int err; - if (!port->removed && port->linkup == linkup) + if (!port->removed && port->state.linkup == linkup) return; port->changed = true; - port->linkup = linkup; + port->state.linkup = linkup; + team_refresh_port_linkup(port); if (linkup) { struct ethtool_cmd ecmd; err = __ethtool_get_settings(port->dev, &ecmd); if (!err) { - port->speed = ethtool_cmd_speed(&ecmd); - port->duplex = ecmd.duplex; + port->state.speed = ethtool_cmd_speed(&ecmd); + port->state.duplex = ecmd.duplex; goto send_event; } } - port->speed = 0; - port->duplex = 0; + port->state.speed = 0; + port->state.duplex = 0; send_event: err = team_nl_send_event_port_list_get(port->team); diff --git a/drivers/net/team/team_mode_activebackup.c b/drivers/net/team/team_mode_activebackup.c index f4d960e82e2..fd6bd03aaa8 100644 --- a/drivers/net/team/team_mode_activebackup.c +++ b/drivers/net/team/team_mode_activebackup.c @@ -59,23 +59,21 @@ static void ab_port_leave(struct team *team, struct team_port *port) RCU_INIT_POINTER(ab_priv(team)->active_port, NULL); } -static int ab_active_port_get(struct team *team, void *arg) +static int ab_active_port_get(struct team *team, struct team_gsetter_ctx *ctx) { - u32 *ifindex = arg; - - *ifindex = 0; if (ab_priv(team)->active_port) - *ifindex = ab_priv(team)->active_port->dev->ifindex; + ctx->data.u32_val = ab_priv(team)->active_port->dev->ifindex; + else + ctx->data.u32_val = 0; return 0; } -static int ab_active_port_set(struct team *team, void *arg) +static int ab_active_port_set(struct team *team, struct team_gsetter_ctx *ctx) { - u32 *ifindex = arg; struct team_port *port; - list_for_each_entry_rcu(port, &team->port_list, list) { - if (port->dev->ifindex == *ifindex) { + list_for_each_entry(port, &team->port_list, list) { + if (port->dev->ifindex == ctx->data.u32_val) { rcu_assign_pointer(ab_priv(team)->active_port, port); return 0; } @@ -92,12 +90,12 @@ static const struct team_option ab_options[] = { }, }; -int ab_init(struct team *team) +static int ab_init(struct team *team) { return team_options_register(team, ab_options, ARRAY_SIZE(ab_options)); } -void ab_exit(struct team *team) +static void ab_exit(struct team *team) { team_options_unregister(team, ab_options, ARRAY_SIZE(ab_options)); } diff --git a/drivers/net/team/team_mode_loadbalance.c b/drivers/net/team/team_mode_loadbalance.c new file mode 100644 index 00000000000..86e8183c8e3 --- /dev/null +++ b/drivers/net/team/team_mode_loadbalance.c @@ -0,0 +1,174 @@ +/* + * drivers/net/team/team_mode_loadbalance.c - Load-balancing mode for team + * Copyright (c) 2012 Jiri Pirko <jpirko@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/filter.h> +#include <linux/if_team.h> + +struct lb_priv { + struct sk_filter __rcu *fp; + struct sock_fprog *orig_fprog; +}; + +static struct lb_priv *lb_priv(struct team *team) +{ + return (struct lb_priv *) &team->mode_priv; +} + +static bool lb_transmit(struct team *team, struct sk_buff *skb) +{ + struct sk_filter *fp; + struct team_port *port; + unsigned int hash; + int port_index; + + fp = rcu_dereference(lb_priv(team)->fp); + if (unlikely(!fp)) + goto drop; + hash = SK_RUN_FILTER(fp, skb); + port_index = hash % team->en_port_count; + port = team_get_port_by_index_rcu(team, port_index); + if (unlikely(!port)) + goto drop; + skb->dev = port->dev; + if (dev_queue_xmit(skb)) + return false; + return true; + +drop: + dev_kfree_skb_any(skb); + return false; +} + +static int lb_bpf_func_get(struct team *team, struct team_gsetter_ctx *ctx) +{ + if (!lb_priv(team)->orig_fprog) { + ctx->data.bin_val.len = 0; + ctx->data.bin_val.ptr = NULL; + return 0; + } + ctx->data.bin_val.len = lb_priv(team)->orig_fprog->len * + sizeof(struct sock_filter); + ctx->data.bin_val.ptr = lb_priv(team)->orig_fprog->filter; + return 0; +} + +static int __fprog_create(struct sock_fprog **pfprog, u32 data_len, + const void *data) +{ + struct sock_fprog *fprog; + struct sock_filter *filter = (struct sock_filter *) data; + + if (data_len % sizeof(struct sock_filter)) + return -EINVAL; + fprog = kmalloc(sizeof(struct sock_fprog), GFP_KERNEL); + if (!fprog) + return -ENOMEM; + fprog->filter = kmemdup(filter, data_len, GFP_KERNEL); + if (!fprog->filter) { + kfree(fprog); + return -ENOMEM; + } + fprog->len = data_len / sizeof(struct sock_filter); + *pfprog = fprog; + return 0; +} + +static void __fprog_destroy(struct sock_fprog *fprog) +{ + kfree(fprog->filter); + kfree(fprog); +} + +static int lb_bpf_func_set(struct team *team, struct team_gsetter_ctx *ctx) +{ + struct sk_filter *fp = NULL; + struct sock_fprog *fprog = NULL; + int err; + + if (ctx->data.bin_val.len) { + err = __fprog_create(&fprog, ctx->data.bin_val.len, + ctx->data.bin_val.ptr); + if (err) + return err; + err = sk_unattached_filter_create(&fp, fprog); + if (err) { + __fprog_destroy(fprog); + return err; + } + } + + if (lb_priv(team)->orig_fprog) { + /* Clear old filter data */ + __fprog_destroy(lb_priv(team)->orig_fprog); + sk_unattached_filter_destroy(lb_priv(team)->fp); + } + + rcu_assign_pointer(lb_priv(team)->fp, fp); + lb_priv(team)->orig_fprog = fprog; + return 0; +} + +static const struct team_option lb_options[] = { + { + .name = "bpf_hash_func", + .type = TEAM_OPTION_TYPE_BINARY, + .getter = lb_bpf_func_get, + .setter = lb_bpf_func_set, + }, +}; + +static int lb_init(struct team *team) +{ + return team_options_register(team, lb_options, + ARRAY_SIZE(lb_options)); +} + +static void lb_exit(struct team *team) +{ + team_options_unregister(team, lb_options, + ARRAY_SIZE(lb_options)); +} + +static const struct team_mode_ops lb_mode_ops = { + .init = lb_init, + .exit = lb_exit, + .transmit = lb_transmit, +}; + +static struct team_mode lb_mode = { + .kind = "loadbalance", + .owner = THIS_MODULE, + .priv_size = sizeof(struct lb_priv), + .ops = &lb_mode_ops, +}; + +static int __init lb_init_module(void) +{ + return team_mode_register(&lb_mode); +} + +static void __exit lb_cleanup_module(void) +{ + team_mode_unregister(&lb_mode); +} + +module_init(lb_init_module); +module_exit(lb_cleanup_module); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jiri Pirko <jpirko@redhat.com>"); +MODULE_DESCRIPTION("Load-balancing mode for team"); +MODULE_ALIAS("team-mode-loadbalance"); diff --git a/drivers/net/team/team_mode_roundrobin.c b/drivers/net/team/team_mode_roundrobin.c index a0e8f806331..6abfbdc96be 100644 --- a/drivers/net/team/team_mode_roundrobin.c +++ b/drivers/net/team/team_mode_roundrobin.c @@ -50,7 +50,7 @@ static bool rr_transmit(struct team *team, struct sk_buff *skb) struct team_port *port; int port_index; - port_index = rr_priv(team)->sent_packets++ % team->port_count; + port_index = rr_priv(team)->sent_packets++ % team->en_port_count; port = team_get_port_by_index_rcu(team, port_index); port = __get_first_port_up(team, port); if (unlikely(!port)) |