diff options
-rw-r--r-- | fastd.c | 125 | ||||
-rw-r--r-- | fastd.h | 6 |
2 files changed, 122 insertions, 9 deletions
@@ -75,6 +75,9 @@ static struct workqueue_struct *fastd_workqueue; struct fastd_struct { struct list_head list; + /* protects changes of sockets (and setting owner to 0) */ + spinlock_t lock; + struct net_device *dev; struct net *net; @@ -83,9 +86,31 @@ struct fastd_struct { u16 mode; unsigned long flags; + struct list_head sockets; + struct work_struct destroy_work; }; +struct fastd_socket { + struct list_head list; + + struct socket *sock; +}; + + +/* must be called under rcu lock or rtnl */ +static inline struct fastd_struct *fastd_find(struct net *net, u32 portid) +{ + struct fastd_struct *entry; + + list_for_each_entry_rcu(entry, &fastd_list, list) { + if (net_eq(entry->net, net) && entry->owner == portid) + return entry; + } + + return NULL; +} + static struct genl_family fastd_nl_family = { .id = GENL_ID_GENERATE, @@ -102,19 +127,21 @@ static int fastd_nl_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct netlink_notify *n = ptr; - struct fastd_struct *entry; + struct fastd_struct *fastd; if (event != NETLINK_URELEASE || n->protocol != NETLINK_GENERIC) return NOTIFY_DONE; rcu_read_lock(); - list_for_each_entry_rcu(entry, &fastd_list, list) { - if (net_eq(entry->net, n->net) && entry->owner == n->portid) { - entry->owner = 0; + fastd = fastd_find(n->net, n->portid); - queue_work(fastd_workqueue, &entry->destroy_work); - } + if (fastd) { + spin_lock_bh(&fastd->lock); + fastd->owner = 0; + spin_unlock_bh(&fastd->lock); + + queue_work(fastd_workqueue, &fastd->destroy_work); } rcu_read_unlock(); @@ -180,6 +207,13 @@ static const struct ethtool_ops fastd_ethtool_ops = { static void fastd_netdev_free(struct net_device *dev) { struct fastd_struct *fastd = netdev_priv(dev); + struct fastd_socket *socket, *next; + + list_for_each_entry_safe(socket, next, &fastd->sockets, list) { + list_del(&socket->list); + sock_release(socket->sock); + kfree(socket); + } put_net(fastd->net); free_netdev(dev); @@ -283,10 +317,12 @@ static int fastd_cmd_create(struct sk_buff *skb, struct genl_info *info) dev_net_set(dev, net); fastd = netdev_priv(dev); + spin_lock_init(&fastd->lock); fastd->dev = dev; fastd->net = get_net(net); fastd->owner = info->snd_portid; fastd->mode = mode; + INIT_LIST_HEAD(&fastd->sockets); INIT_WORK(&fastd->destroy_work, fastd_destroy_work); fastd_netdev_init(dev); @@ -299,7 +335,7 @@ static int fastd_cmd_create(struct sk_buff *skb, struct genl_info *info) goto err_free_dev; } - list_add_tail_rcu(&fastd->list, &fastd_list); + list_add_rcu(&fastd->list, &fastd_list); rtnl_unlock(); @@ -314,11 +350,81 @@ err_module_put: return err; } +static int fastd_cmd_bind(struct sk_buff *skb, struct genl_info *info) +{ + int err; + sa_family_t af; + struct fastd_socket *socket; + struct fastd_struct *fastd; + + if (!info->attrs[FASTD_A_LOCALADDR] || + nla_len(info->attrs[FASTD_A_LOCALADDR]) < sizeof(sa_family_t)) + return -EINVAL; + + af = *(sa_family_t *)nla_data(info->attrs[FASTD_A_LOCALADDR]); + if (af != AF_INET && af != AF_INET6) + return -EAFNOSUPPORT; + + socket = kmalloc(sizeof(*socket), GFP_KERNEL); + if (!socket) + return -ENOMEM; + + err = sock_create_kern(af, SOCK_DGRAM, 0, &socket->sock); + if (err) + goto err_free_socket; + + err = kernel_bind(socket->sock, + nla_data(info->attrs[FASTD_A_LOCALADDR]), + nla_len(info->attrs[FASTD_A_LOCALADDR])); + if (err) + goto err_release_sock; + + rcu_read_lock(); + + fastd = fastd_find(genl_info_net(info), info->snd_portid); + if (!fastd) { + err = -EINVAL; + goto err_unlock_rcu; + } + + spin_lock_bh(&fastd->lock); + + if (!fastd->owner) { + err = -EINVAL; + goto err_unlock; + } + + socket->sock->sk->sk_user_data = fastd; + list_add_tail_rcu(&socket->list, &fastd->sockets); + + spin_unlock_bh(&fastd->lock); + + rcu_read_unlock(); + + return 0; + +err_unlock: + spin_unlock_bh(&fastd->lock); + +err_unlock_rcu: + rcu_read_unlock(); + +err_release_sock: + sock_release(socket->sock); + +err_free_socket: + kfree(socket); + + return err; +} + static struct nla_policy fastd_nl_policy[__FASTD_A_MAX] = { [FASTD_A_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ-1 }, [FASTD_A_MODE] = { .type = NLA_U16 }, [FASTD_A_MTU] = { .type = NLA_U16 }, + [FASTD_A_LOCALADDR] = { .type = NLA_UNSPEC }, + [FASTD_A_REMOTEADDR] = { .type = NLA_UNSPEC }, }; static struct genl_ops fastd_nl_ops[] = { @@ -327,6 +433,11 @@ static struct genl_ops fastd_nl_ops[] = { .doit = fastd_cmd_create, .policy = fastd_nl_policy, }, + { + .cmd = FASTD_CMD_BIND, + .doit = fastd_cmd_bind, + .policy = fastd_nl_policy, + }, }; @@ -40,14 +40,14 @@ #define __LINUX_FASTD_H enum { - FASTD_MODE_UNSPEC, - FASTD_MODE_ETH, + FASTD_MODE_ETH = 1, FASTD_MODE_IP }; enum { FASTD_CMD_UNSPEC, FASTD_CMD_CREATE, + FASTD_CMD_BIND, __FASTD_CMD_MAX }; @@ -56,6 +56,8 @@ enum { FASTD_A_IFNAME, FASTD_A_MODE, FASTD_A_MTU, + FASTD_A_LOCALADDR, + FASTD_A_REMOTEADDR, __FASTD_A_MAX }; |