diff options
author | Jiri Pirko <jpirko@redhat.com> | 2012-04-04 16:16:27 +0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2012-04-05 04:30:40 +0400 |
commit | 01d7f30a9f962573b6c91ed520c73fb30658d826 (patch) | |
tree | 44103605b79d249f4e602ad97f59feccf33b7b05 /drivers/net/team/team_mode_loadbalance.c | |
parent | 2615598fc100451c71b83d06bdf5faead619a40e (diff) | |
download | linux-01d7f30a9f962573b6c91ed520c73fb30658d826.tar.xz |
team: add loadbalance mode
This patch introduces new team mode. It's TX port is selected by
user-set BPF hash function.
Signed-off-by: Jiri Pirko <jpirko@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/team/team_mode_loadbalance.c')
-rw-r--r-- | drivers/net/team/team_mode_loadbalance.c | 188 |
1 files changed, 188 insertions, 0 deletions
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"); |