diff options
author | Felix Fietkau <nbd@openwrt.org> | 2013-10-03 16:50:51 +0200 |
---|---|---|
committer | Felix Fietkau <nbd@openwrt.org> | 2013-10-03 16:51:05 +0200 |
commit | 5dd87fd8ad3a42c71d90a8fecc893af973e552bf (patch) | |
tree | e0255fd31ccfa52e96df6c6f9ca2a02d3b7d4702 | |
parent | 9585227f1141d9da5a3186df5e10c4521e65083e (diff) |
device: add macvlan support
Signed-off-by: Felix Fietkau <nbd@openwrt.org>
Signed-off-by: Jo-Philipp Wich <jow@openwrt.org>
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | config.c | 2 | ||||
-rw-r--r-- | device.h | 3 | ||||
-rw-r--r-- | macvlan.c | 254 | ||||
-rw-r--r-- | system-dummy.c | 10 | ||||
-rw-r--r-- | system-linux.c | 88 | ||||
-rw-r--r-- | system.h | 14 |
7 files changed, 371 insertions, 2 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index ca201dc..777a8e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ SET(SOURCES interface.c interface-ip.c interface-event.c iprule.c proto.c proto-static.c proto-shell.c config.c device.c bridge.c vlan.c alias.c - ubus.c) + macvlan.c ubus.c) find_library(json NAMES json-c json) @@ -166,6 +166,8 @@ config_init_devices(void) devtype = &bridge_device_type; else if (!strcmp(type, "tunnel")) devtype = &tunnel_device_type; + else if (!strcmp(type, "macvlan")) + devtype = &macvlan_device_type; } if (!devtype) @@ -103,7 +103,7 @@ struct device_settings { uint8_t macaddr[6]; }; -/* +/* * link layer device. typically represents a linux network device. * can be used to support VLANs as well */ @@ -152,6 +152,7 @@ extern const struct uci_blob_param_list device_attr_list; extern const struct device_type simple_device_type; extern const struct device_type bridge_device_type; extern const struct device_type tunnel_device_type; +extern const struct device_type macvlan_device_type; void device_lock(void); void device_unlock(void); diff --git a/macvlan.c b/macvlan.c new file mode 100644 index 0000000..bf81340 --- /dev/null +++ b/macvlan.c @@ -0,0 +1,254 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org> + * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <errno.h> +#include <netinet/ether.h> + +#include "netifd.h" +#include "device.h" +#include "interface.h" +#include "system.h" + +enum { + MACVLAN_ATTR_IFNAME, + MACVLAN_ATTR_MACADDR, + MACVLAN_ATTR_MODE, + __MACVLAN_ATTR_MAX +}; + +static const struct blobmsg_policy macvlan_attrs[__MACVLAN_ATTR_MAX] = { + [MACVLAN_ATTR_IFNAME] = { "ifname", BLOBMSG_TYPE_STRING }, + [MACVLAN_ATTR_MACADDR] = { "macaddr", BLOBMSG_TYPE_STRING }, + [MACVLAN_ATTR_MODE] = { "mode", BLOBMSG_TYPE_STRING }, +}; + +static const struct uci_blob_param_list macvlan_attr_list = { + .n_params = __MACVLAN_ATTR_MAX, + .params = macvlan_attrs, +}; + +struct macvlan_device { + struct device dev; + struct device_user parent; + + device_state_cb set_state; + + struct blob_attr *config_data; + struct blob_attr *ifname; + struct macvlan_config config; +}; + +static void +macvlan_base_cb(struct device_user *dev, enum device_event ev) +{ + struct macvlan_device *mvdev = container_of(dev, struct macvlan_device, parent); + + switch (ev) { + case DEV_EVENT_ADD: + device_set_present(&mvdev->dev, true); + break; + case DEV_EVENT_REMOVE: + device_set_present(&mvdev->dev, false); + break; + default: + return; + } +} + +static int +macvlan_set_down(struct macvlan_device *mvdev) +{ + mvdev->set_state(&mvdev->dev, false); + system_macvlan_del(&mvdev->dev); + device_release(&mvdev->parent); + + return 0; +} + +static int +macvlan_set_up(struct macvlan_device *mvdev) +{ + int ret; + + ret = device_claim(&mvdev->parent); + if (ret < 0) + return ret; + + ret = system_macvlan_add(&mvdev->dev, mvdev->parent.dev, &mvdev->config); + if (ret < 0) + goto release; + + ret = mvdev->set_state(&mvdev->dev, true); + if (ret) + goto delete; + + return 0; + +delete: + system_macvlan_del(&mvdev->dev); +release: + device_release(&mvdev->parent); + return ret; +} + +static int +macvlan_set_state(struct device *dev, bool up) +{ + struct macvlan_device *mvdev; + + D(SYSTEM, "macvlan_set_state(%s, %u)\n", dev->ifname, up); + + mvdev = container_of(dev, struct macvlan_device, dev); + if (up) + return macvlan_set_up(mvdev); + else + return macvlan_set_down(mvdev); +} + +static void +macvlan_free(struct device *dev) +{ + struct macvlan_device *mvdev; + + mvdev = container_of(dev, struct macvlan_device, dev); + device_remove_user(&mvdev->parent); + free(mvdev); +} + +static void +macvlan_dump_info(struct device *dev, struct blob_buf *b) +{ + struct macvlan_device *mvdev; + + mvdev = container_of(dev, struct macvlan_device, dev); + blobmsg_add_string(b, "parent", mvdev->parent.dev->ifname); + system_if_dump_info(dev, b); +} + +static void +macvlan_config_init(struct device *dev) +{ + struct macvlan_device *mvdev; + struct device *basedev = NULL; + + mvdev = container_of(dev, struct macvlan_device, dev); + if (mvdev->ifname) + basedev = device_get(blobmsg_data(mvdev->ifname), true); + + device_add_user(&mvdev->parent, basedev); +} + +static void +macvlan_apply_settings(struct macvlan_device *mvdev, struct blob_attr **tb) +{ + struct macvlan_config *cfg = &mvdev->config; + struct blob_attr *cur; + struct ether_addr *ea; + + cfg->flags = 0; + cfg->mode = NULL; + + if ((cur = tb[MACVLAN_ATTR_MACADDR])) { + ea = ether_aton(blobmsg_data(cur)); + if (ea) { + memcpy(cfg->macaddr, ea, 6); + cfg->flags |= MACVLAN_OPT_MACADDR; + } + } + + if ((cur = tb[MACVLAN_ATTR_MODE])) + cfg->mode = blobmsg_data(cur); +} + +static enum dev_change_type +macvlan_reload(struct device *dev, struct blob_attr *attr) +{ + struct blob_attr *tb_dev[__DEV_ATTR_MAX]; + struct blob_attr *tb_mv[__MACVLAN_ATTR_MAX]; + enum dev_change_type ret = DEV_CONFIG_APPLIED; + struct macvlan_device *mvdev; + + mvdev = container_of(dev, struct macvlan_device, dev); + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, tb_dev, + blob_data(attr), blob_len(attr)); + blobmsg_parse(macvlan_attrs, __MACVLAN_ATTR_MAX, tb_mv, + blob_data(attr), blob_len(attr)); + + device_init_settings(dev, tb_dev); + macvlan_apply_settings(mvdev, tb_mv); + mvdev->ifname = tb_mv[MACVLAN_ATTR_IFNAME]; + + if (mvdev->config_data) { + struct blob_attr *otb_dev[__DEV_ATTR_MAX]; + struct blob_attr *otb_mv[__MACVLAN_ATTR_MAX]; + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, otb_dev, + blob_data(mvdev->config_data), blob_len(mvdev->config_data)); + + if (uci_blob_diff(tb_dev, otb_dev, &device_attr_list, NULL)) + ret = DEV_CONFIG_RESTART; + + blobmsg_parse(macvlan_attrs, __MACVLAN_ATTR_MAX, otb_mv, + blob_data(mvdev->config_data), blob_len(mvdev->config_data)); + + if (uci_blob_diff(tb_mv, otb_mv, &macvlan_attr_list, NULL)) + ret = DEV_CONFIG_RESTART; + + macvlan_config_init(dev); + } + + mvdev->config_data = attr; + return ret; +} + +static struct device * +macvlan_create(const char *name, struct blob_attr *attr) +{ + struct macvlan_device *mvdev; + struct device *dev = NULL; + + mvdev = calloc(1, sizeof(*mvdev)); + if (!mvdev) + return NULL; + + dev = &mvdev->dev; + device_init(dev, &macvlan_device_type, name); + dev->config_pending = true; + + mvdev->set_state = dev->set_state; + dev->set_state = macvlan_set_state; + + dev->hotplug_ops = NULL; + mvdev->parent.cb = macvlan_base_cb; + + macvlan_reload(dev, attr); + + return dev; +} + +const struct device_type macvlan_device_type = { + .name = "MAC VLAN", + .config_params = &macvlan_attr_list, + + .create = macvlan_create, + .config_init = macvlan_config_init, + .reload = macvlan_reload, + .free = macvlan_free, + .dump_info = macvlan_dump_info, +}; diff --git a/system-dummy.c b/system-dummy.c index 95546bf..c8379ff 100644 --- a/system-dummy.c +++ b/system-dummy.c @@ -242,3 +242,13 @@ int system_update_ipv6_mtu(struct device *dev, int mtu) { return 0; } + +int system_macvlan_add(struct device *macvlan, struct device *dev, struct macvlan_config *cfg) +{ + return 0; +} + +int system_macvlan_del(struct device *macvlan) +{ + return 0; +} diff --git a/system-linux.c b/system-linux.c index 4299a61..e5364e0 100644 --- a/system-linux.c +++ b/system-linux.c @@ -29,6 +29,7 @@ #include <linux/rtnetlink.h> #include <linux/sockios.h> #include <linux/ip.h> +#include <linux/if_link.h> #include <linux/if_vlan.h> #include <linux/if_bridge.h> #include <linux/if_tunnel.h> @@ -611,6 +612,93 @@ int system_bridge_addbr(struct device *bridge, struct bridge_config *cfg) return 0; } +int system_macvlan_add(struct device *macvlan, struct device *dev, struct macvlan_config *cfg) +{ + struct nl_msg *msg; + struct nlattr *linkinfo, *data; + struct ifinfomsg iim = { .ifi_family = AF_INET }; + int ifindex = system_if_resolve(dev); + int i, rv; + static const struct { + const char *name; + enum macvlan_mode val; + } modes[] = { + { "private", MACVLAN_MODE_PRIVATE }, + { "vepa", MACVLAN_MODE_VEPA }, + { "bridge", MACVLAN_MODE_BRIDGE }, + { "passthru", MACVLAN_MODE_PASSTHRU }, + }; + + if (ifindex == 0) + return -ENOENT; + + msg = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL); + + if (!msg) + return -1; + + nlmsg_append(msg, &iim, sizeof(iim), 0); + + if (cfg->flags & MACVLAN_OPT_MACADDR) + nla_put(msg, IFLA_ADDRESS, sizeof(cfg->macaddr), cfg->macaddr); + nla_put(msg, IFLA_IFNAME, IFNAMSIZ, macvlan->ifname); + nla_put_u32(msg, IFLA_LINK, ifindex); + + if (!(linkinfo = nla_nest_start(msg, IFLA_LINKINFO))) + goto nla_put_failure; + + nla_put(msg, IFLA_INFO_KIND, strlen("macvlan"), "macvlan"); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (cfg->mode) { + for (i = 0; i < ARRAY_SIZE(modes); i++) { + if (strcmp(cfg->mode, modes[i].name) != 0) + continue; + + nla_put_u32(msg, IFLA_MACVLAN_MODE, modes[i].val); + break; + } + } + + nla_nest_end(msg, data); + nla_nest_end(msg, linkinfo); + + rv = system_rtnl_call(msg); + if (rv) + D(SYSTEM, "Error adding macvlan '%s' over '%s': %d\n", macvlan->ifname, dev->ifname, rv); + + return rv; + +nla_put_failure: + nlmsg_free(msg); + return -ENOMEM; +} + +int system_macvlan_del(struct device *macvlan) +{ + struct nl_msg *msg; + struct ifinfomsg iim; + + iim.ifi_family = AF_INET; + iim.ifi_index = 0; + + msg = nlmsg_alloc_simple(RTM_DELLINK, 0); + + if (!msg) + return -1; + + nlmsg_append(msg, &iim, sizeof(iim), 0); + + nla_put(msg, IFLA_INFO_KIND, strlen("macvlan"), "macvlan"); + nla_put(msg, IFLA_IFNAME, sizeof(macvlan->ifname), macvlan->ifname); + + system_rtnl_call(msg); + + return 0; +} + static int system_vlan(struct device *dev, int id) { struct vlan_ioctl_args ifr = { @@ -54,6 +54,17 @@ struct bridge_config { int max_age; }; +enum macvlan_opt { + MACVLAN_OPT_MACADDR = (1 << 0), +}; + +struct macvlan_config { + const char *mode; + + enum macvlan_opt flags; + unsigned char macaddr[6]; +}; + static inline int system_get_addr_family(unsigned int flags) { if ((flags & DEVADDR_FAMILY) == DEVADDR_INET6) @@ -77,6 +88,9 @@ int system_bridge_delbr(struct device *bridge); int system_bridge_addif(struct device *bridge, struct device *dev); int system_bridge_delif(struct device *bridge, struct device *dev); +int system_macvlan_add(struct device *macvlan, struct device *dev, struct macvlan_config *cfg); +int system_macvlan_del(struct device *macvlan); + int system_vlan_add(struct device *dev, int id); int system_vlan_del(struct device *dev); |