diff options
-rw-r--r-- | fastd.c | 193 | ||||
-rw-r--r-- | fastd.h | 6 |
2 files changed, 191 insertions, 8 deletions
@@ -43,31 +43,71 @@ #include <linux/module.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> #include <linux/errno.h> #include <linux/init.h> +#include <linux/ip.h> +#include <linux/ipv6.h> #include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/rculist.h> #include <net/genetlink.h> +#include <net/route.h> #include "fastd.h" +static LIST_HEAD(fastd_list); +static struct workqueue_struct *fastd_workqueue; + + +struct fastd_struct { + struct list_head list; + + struct net_device *dev; + u32 owner; + + u16 mode; + unsigned long flags; + + struct work_struct destroy_work; +}; + + static struct genl_family fastd_nl_family = { .id = GENL_ID_GENERATE, + .hdrsize = 0, .name = DRV_NAME, .version = 1, - .hdrsize = 0, .maxattr = FASTD_A_MAX, + .netnsok = true, + .parallel_ops = true, }; -static int fastd_nl_event(struct notifier_block *this, - unsigned long event, void *ptr) +static int fastd_nl_event(struct notifier_block *nb, + unsigned long event, void *ptr) { struct netlink_notify *n = ptr; + struct fastd_struct *entry; if (event != NETLINK_URELEASE || n->protocol != NETLINK_GENERIC) return NOTIFY_DONE; + rcu_read_lock(); + + list_for_each_entry_rcu(entry, &fastd_list, list) { + if (entry->owner == n->portid) { + /* allow registration of a new interface for this portid */ + ACCESS_ONCE(entry->owner) = 0; + + queue_work(fastd_workqueue, &entry->destroy_work); + } + } + + rcu_read_unlock(); + return NOTIFY_DONE; } @@ -76,16 +116,146 @@ static struct notifier_block nl_notifier = { }; +static const struct net_device_ops fastd_netdev_ops_eth = { +}; + +static const struct net_device_ops fastd_netdev_ops_ip = { +}; + + +static void fastd_ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +{ + struct fastd_struct *fastd = netdev_priv(dev); + + strcpy(info->driver, DRV_NAME); + strcpy(info->version, DRV_VERSION); + strcpy(info->fw_version, "N/A"); + + switch (fastd->mode) { + case FASTD_MODE_ETH: + strcpy(info->bus_info, "ethernet"); + break; + case FASTD_MODE_IP: + strcpy(info->bus_info, "ip"); + } +} + +static u32 fastd_ethtool_get_link(struct net_device *dev) +{ + return 1; +} + + +static const struct ethtool_ops fastd_ethtool_ops = { + .get_drvinfo = fastd_ethtool_get_drvinfo, + .get_link = fastd_ethtool_get_link, +}; + + +static void fastd_netdev_setup(struct net_device *dev) +{ + dev->ethtool_ops = &fastd_ethtool_ops; + dev->destructor = free_netdev; +} + +static void fastd_netdev_init(struct net_device *dev) +{ + struct fastd_struct *fastd = netdev_priv(dev); + + switch (fastd->mode) { + case FASTD_MODE_ETH: + dev->netdev_ops = &fastd_netdev_ops_eth; + + eth_hw_addr_random(dev); + ether_setup(dev); + + dev->hard_header_len = ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct udphdr) + ETH_HLEN; + + break; + + case FASTD_MODE_IP: + dev->netdev_ops = &fastd_netdev_ops_eth; + + dev->addr_len = 0; + dev->hard_header_len = ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct udphdr); + dev->mtu = 1500; + + dev->type = ARPHRD_NONE; + dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + } +} + +static void fastd_destroy_work(struct work_struct *work) +{ + struct fastd_struct *fastd = container_of(work, struct fastd_struct, + destroy_work); + + rtnl_lock(); + + list_del_rcu(&fastd->list); + unregister_netdevice(fastd->dev); + + rtnl_unlock(); +} + static int fastd_cmd_create(struct sk_buff *skb, struct genl_info *info) { - if (!capable(CAP_NET_ADMIN)) + struct net *net = genl_info_net(info); + struct net_device *dev; + struct fastd_struct *fastd; + u16 mode; + const char *name = "fastd%d"; + int err; + + if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) return -EPERM; - return -EINVAL; + if (!info->attrs[FASTD_A_MODE]) + return -EINVAL; + + mode = nla_get_u16(info->attrs[FASTD_A_MODE]); + if (mode != FASTD_MODE_ETH && mode != FASTD_MODE_IP) + return -EINVAL; + + if (info->attrs[FASTD_A_IFNAME]) + name = nla_data(info->attrs[FASTD_A_IFNAME]); + + dev = alloc_netdev(sizeof(struct fastd_struct), name, fastd_netdev_setup); + if (!dev) + return -ENOMEM; + + dev_net_set(dev, net); + + fastd = netdev_priv(dev); + fastd->dev = dev; + fastd->owner = info->snd_portid; + fastd->mode = mode; + INIT_WORK(&fastd->destroy_work, fastd_destroy_work); + + fastd_netdev_init(dev); + + rtnl_lock(); + err = register_netdevice(dev); + if (err < 0) { + rtnl_unlock(); + goto err_free_dev; + } + + list_add_tail_rcu(&fastd->list, &fastd_list); + + rtnl_unlock(); + + return 0; + +err_free_dev: + free_netdev(dev); + 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 }, }; static struct genl_ops fastd_nl_ops[] = { @@ -101,9 +271,14 @@ static int __init fastd_init(void) { int ret = 0; + fastd_workqueue = alloc_workqueue("fastd", 0, 0); + if (!fastd_workqueue) + return -ENOMEM; + netlink_register_notifier(&nl_notifier); - ret = genl_register_family_with_ops(&fastd_nl_family, fastd_nl_ops, ARRAY_SIZE(fastd_nl_ops)); + ret = genl_register_family_with_ops(&fastd_nl_family, fastd_nl_ops, + ARRAY_SIZE(fastd_nl_ops)); if (ret) goto unregister_notifier; @@ -111,8 +286,9 @@ static int __init fastd_init(void) return 0; - unregister_notifier: +unregister_notifier: netlink_unregister_notifier(&nl_notifier); + destroy_workqueue(fastd_workqueue); return ret; } @@ -121,6 +297,7 @@ static void fastd_cleanup(void) { genl_unregister_family(&fastd_nl_family); netlink_unregister_notifier(&nl_notifier); + destroy_workqueue(fastd_workqueue); } @@ -129,4 +306,4 @@ module_exit(fastd_cleanup); MODULE_DESCRIPTION(DRV_DESCRIPTION); MODULE_AUTHOR(DRV_COPYRIGHT); MODULE_LICENSE("Dual BSD/GPL"); -MODULE_ALIAS_GENL_FAMILY("fastd"); +MODULE_ALIAS_GENL_FAMILY(DRV_NAME); @@ -39,6 +39,10 @@ #ifndef __LINUX_FASTD_H #define __LINUX_FASTD_H +#define FASTD_MODE_ETH 0x0001 +#define FASTD_MODE_IP 0x0002 + + enum { FASTD_CMD_UNSPEC, FASTD_CMD_CREATE, @@ -47,6 +51,8 @@ enum { enum { FASTD_A_UNSPEC, + FASTD_A_IFNAME, + FASTD_A_MODE, __FASTD_A_MAX }; |