diff options
-rw-r--r-- | drivers/net/team/Kconfig | 11 | ||||
-rw-r--r-- | drivers/net/team/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/team/team_mode_loadbalance.c | 188 |
3 files changed, 200 insertions, 0 deletions
diff --git a/drivers/net/team/Kconfig b/drivers/net/team/Kconfig index 248a144033ca..89024d5fc33a 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 85f2028a87af..fb9f4c1c51ff 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_mode_loadbalance.c b/drivers/net/team/team_mode_loadbalance.c new file mode 100644 index 000000000000..ed20f395be6f --- /dev/null +++ b/drivers/net/team/team_mode_loadbalance.c @@ -0,0 +1,188 @@ +/* + * 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->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, void *arg) +{ + struct team_option_binary *tbinary = team_optarg_tbinary(arg); + + memset(tbinary, 0, sizeof(*tbinary)); + if (!lb_priv(team)->orig_fprog) + return 0; + + tbinary->data_len = lb_priv(team)->orig_fprog->len * + sizeof(struct sock_filter); + tbinary->data = lb_priv(team)->orig_fprog->filter; + return 0; +} + +static int __fprog_create(struct sock_fprog **pfprog, u32 data_len, + 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, void *arg) +{ + struct team_option_binary *tbinary = team_optarg_tbinary(arg); + struct sk_filter *fp = NULL; + struct sock_fprog *fprog = NULL; + int err; + + if (tbinary->data_len) { + err = __fprog_create(&fprog, tbinary->data_len, + tbinary->data); + 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, + }, +}; + +int lb_init(struct team *team) +{ + return team_options_register(team, lb_options, + ARRAY_SIZE(lb_options)); +} + +void lb_exit(struct team *team) +{ + team_options_unregister(team, lb_options, + ARRAY_SIZE(lb_options)); +} + +static int lb_port_enter(struct team *team, struct team_port *port) +{ + return team_port_set_team_mac(port); +} + +static void lb_port_change_mac(struct team *team, struct team_port *port) +{ + team_port_set_team_mac(port); +} + +static const struct team_mode_ops lb_mode_ops = { + .init = lb_init, + .exit = lb_exit, + .transmit = lb_transmit, + .port_enter = lb_port_enter, + .port_change_mac = lb_port_change_mac, +}; + +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"); |