summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--fastd.c125
-rw-r--r--fastd.h6
2 files changed, 122 insertions, 9 deletions
diff --git a/fastd.c b/fastd.c
index 1ce476f..d660c35 100644
--- a/fastd.c
+++ b/fastd.c
@@ -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,
+ },
};
diff --git a/fastd.h b/fastd.h
index 8b89130..55878cc 100644
--- a/fastd.h
+++ b/fastd.h
@@ -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
};