From: Maxim Anisimov Date: Thu, 31 Oct 2024 06:25:45 +0000 (+0300) Subject: device: add support for configuring vrf X-Git-Url: http://git.cdn.openwrt.org/?a=commitdiff_plain;h=d610d68c71b82b987c665a922c10084d5cf89f8c;p=project%2Fnetifd.git device: add support for configuring vrf Config example: ... config device option type 'vrf' option name 'dcn' option table '20' option mtu '1500' option ipv6 '0' list ports 'l2tp-wanvpn' list ports 'lan3' config interface 'dcn' option proto 'none' option device 'dcn' ... Note: using "ports" in config simplifies luci integration Tested-by: Paul Donald Signed-off-by: Maxim Anisimov Link: https://github.com/openwrt/netifd/pull/38 Signed-off-by: Robert Marko --- diff --git a/CMakeLists.txt b/CMakeLists.txt index a3eaf65..ab533fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ SET(SOURCES iprule.c proto.c proto-static.c proto-shell.c config.c device.c bridge.c veth.c vlan.c alias.c macvlan.c ubus.c vlandev.c wireless.c - extdev.c bonding.c) + extdev.c bonding.c vrf.c) FIND_LIBRARY(uci NAMES uci) diff --git a/config.c b/config.c index 5530050..0ea39b5 100644 --- a/config.c +++ b/config.c @@ -28,6 +28,7 @@ #include "wireless.h" #include "config.h" #include "ubus.h" +#include "system.h" bool config_init = false; @@ -537,6 +538,16 @@ config_init_globals(void) const char *ula_prefix = uci_lookup_option_string( uci_ctx, globals, "ula_prefix"); interface_ip_set_ula_prefix(ula_prefix); + + const char *tcp_l3mdev = uci_lookup_option_string( + uci_ctx, globals, "tcp_l3mdev"); + if (tcp_l3mdev) + system_tcp_l3mdev(!strcmp(tcp_l3mdev, "1")); + + const char *udp_l3mdev = uci_lookup_option_string( + uci_ctx, globals, "udp_l3mdev"); + if (udp_l3mdev) + system_udp_l3mdev(!strcmp(udp_l3mdev, "1")); } static void diff --git a/system-linux.c b/system-linux.c index 945ca4c..8fd0988 100644 --- a/system-linux.c +++ b/system-linux.c @@ -1063,6 +1063,115 @@ failure: return ret; } +int system_vrf_addvrf(struct device *vrf, unsigned int table) +{ + struct nlattr *linkinfo, *data; + struct nl_msg *msg; + int rv; + + msg = system_ifinfo_msg(vrf->ifname, RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL); + if (!msg) + return -1; + + if (!(linkinfo = nla_nest_start(msg, IFLA_LINKINFO))) + goto nla_put_failure; + + nla_put_string(msg, IFLA_INFO_KIND, "vrf"); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + nla_put_u32(msg, IFLA_VRF_TABLE, table); + + nla_nest_end(msg, data); + nla_nest_end(msg, linkinfo); + + rv = system_rtnl_call(msg); + if (rv) + D(SYSTEM, "Error adding vrf '%s': %d\n", vrf->ifname, rv); + + return rv; + +nla_put_failure: + nlmsg_free(msg); + return -ENOMEM; +} + +int system_vrf_delvrf(struct device *vrf) +{ + return system_link_del(vrf->ifname); +} + +static char *system_get_vrf(const char *name, char *buf, int buflen) +{ + char master[PATH_MAX]; + char *path; + ssize_t len = -1; + + if (snprintf(master, sizeof(master), "%s/devices/virtual/net/%s/master", sysfs_path, name) <= 0) + return NULL; + + len = readlink(master, buf, buflen); + if (len < 0) + return NULL; + + buf[len] = 0; + path = strrchr(buf, '/'); + if (!path) + return NULL; + + return path + 1; +} + +static int +system_vrf_if(int vrf_index, struct device *dev) +{ + struct nl_msg *msg; + + msg = __system_ifinfo_msg(AF_UNSPEC, dev->ifindex, NULL, RTM_SETLINK, NLM_F_REQUEST); + if (!msg) + return -1; + + nla_put_u32(msg, IFLA_MASTER, vrf_index); + return system_rtnl_call(msg); +} + +int system_vrf_addif(struct device *vrf, struct device *dev) +{ + char *oldvrf; + int tries = 0; + int ret; + +retry: + ret = 0; + oldvrf = system_get_vrf(dev->ifname, dev_buf, sizeof(dev_buf)); + if (!oldvrf || strcmp(oldvrf, vrf->ifname) != 0) { + ret = system_vrf_if(vrf->ifindex, dev); + tries++; + D(SYSTEM, "Failed to add device '%s' to vrf '%s' (tries=%d): %s\n", + dev->ifname, vrf->ifname, tries, strerror(errno)); + if (tries <= 3) + goto retry; + } + + return ret; +} + +int system_vrf_delif(struct device *vrf, struct device *dev) +{ + return system_vrf_if(0, dev); +} + +void system_tcp_l3mdev(bool enable) +{ + system_set_dev_sysctl("ipv4", "tcp_l3mdev_accept", ".", enable ? "1" : "0"); +} + +void system_udp_l3mdev(bool enable) +{ + system_set_dev_sysctl("ipv4", "udp_l3mdev_accept", ".", enable ? "1" : "0"); +} + int system_bonding_set_device(struct device *dev, struct bonding_config *cfg) { const char *ifname = dev->ifname; diff --git a/system.h b/system.h index 890966b..96bfd07 100644 --- a/system.h +++ b/system.h @@ -250,6 +250,14 @@ int system_bridge_vlan(const char *iface, uint16_t vid, int16_t vid_end, bool ad int system_bridge_vlan_check(struct device *dev, char *ifname); void system_bridge_set_stp_state(struct device *dev, bool val); +int system_vrf_addvrf(struct device *vrf, unsigned int table); +int system_vrf_delvrf(struct device *vrf); +int system_vrf_addif(struct device *vrf, struct device *dev); +int system_vrf_delif(struct device *vrf, struct device *dev); + +void system_tcp_l3mdev(bool enable); +void system_udp_l3mdev(bool enable); + int system_bonding_set_device(struct device *dev, struct bonding_config *cfg); int system_bonding_set_port(struct device *dev, struct device *port, bool add, bool primary); diff --git a/vrf.c b/vrf.c new file mode 100644 index 0000000..d27d5c3 --- /dev/null +++ b/vrf.c @@ -0,0 +1,687 @@ +#include +#include +#include +#include + +#include "netifd.h" +#include "device.h" +#include "system.h" + +enum { + VRF_ATTR_PORTS, + VRF_ATTR_TABLE, + __VRF_ATTR_MAX +}; + +static const struct blobmsg_policy vrf_attrs[__VRF_ATTR_MAX] = { + [VRF_ATTR_PORTS] = { "ports", BLOBMSG_TYPE_ARRAY }, + [VRF_ATTR_TABLE] = { "table", BLOBMSG_TYPE_STRING }, +}; + +static const struct uci_blob_param_info vrf_attr_info[__VRF_ATTR_MAX] = { + [VRF_ATTR_PORTS] = { .type = BLOBMSG_TYPE_STRING }, +}; + +static const struct uci_blob_param_list vrf_attr_list = { + .n_params = __VRF_ATTR_MAX, + .params = vrf_attrs, + .info = vrf_attr_info, + + .n_next = 1, + .next = { &device_attr_list }, +}; + +static struct device *vrf_create(const char *name, struct device_type *devtype, + struct blob_attr *attr); +static void vrf_config_init(struct device *dev); +static void vrf_free(struct device *dev); +static void vrf_dump_info(struct device *dev, struct blob_buf *b); +static enum dev_change_type +vrf_reload(struct device *dev, struct blob_attr *attr); + +static struct device_type vrf_state_type = { + .name = "vrf", + .config_params = &vrf_attr_list, + + .bridge_capability = true, + + .create = vrf_create, + .config_init = vrf_config_init, + .reload = vrf_reload, + .free = vrf_free, + .dump_info = vrf_dump_info, +}; + +struct vrf_state { + struct device dev; + device_state_cb set_state; + + struct blob_attr *config_data; + unsigned int table; + bool vrf_empty; + struct blob_attr *ports; + bool active; + bool force_active; + + struct uloop_timeout retry; + struct vrf_member *primary_port; + struct vlist_tree members; + int n_present; + int n_failed; +}; + +struct vrf_member { + struct vlist_node node; + struct vrf_state *vst; + struct device_user dev; + bool present; + bool active; + char name[]; +}; + +static void +vrf_reset_primary(struct vrf_state *vst) +{ + struct vrf_member *vm; + + if (!vst->primary_port && + (vst->dev.settings.flags & DEV_OPT_MACADDR)) + return; + + vst->primary_port = NULL; + vst->dev.settings.flags &= ~DEV_OPT_MACADDR; + vlist_for_each_element(&vst->members, vm, node) { + uint8_t *macaddr; + + if (!vm->present) + continue; + + vst->primary_port = vm; + if (vm->dev.dev->settings.flags & DEV_OPT_MACADDR) + macaddr = vm->dev.dev->settings.macaddr; + else + macaddr = vm->dev.dev->orig_settings.macaddr; + memcpy(vst->dev.settings.macaddr, macaddr, 6); + vst->dev.settings.flags |= DEV_OPT_MACADDR; + return; + } +} + +static int +vrf_disable_member(struct vrf_member *vm, bool keep_dev) +{ + struct vrf_state *vst = vm->vst; + + if (!vm->present || !vm->active) + return 0; + + vm->active = false; + + system_vrf_delif(&vst->dev, vm->dev.dev); + if (!keep_dev) + device_release(&vm->dev); + + device_broadcast_event(&vst->dev, DEV_EVENT_TOPO_CHANGE); + + return 0; +} + +static int +vrf_enable_interface(struct vrf_state *vst) +{ + int ret; + + if (vst->active) + return 0; + + ret = system_vrf_addvrf(&vst->dev, vst->table); + if (ret < 0) + return ret; + + vst->active = true; + return 0; +} + +static void +vrf_disable_interface(struct vrf_state *vst) +{ + if (!vst->active) + return; + + system_vrf_delvrf(&vst->dev); + vst->active = false; +} + +static int +vrf_enable_member(struct vrf_member *vm) +{ + struct vrf_state *vst = vm->vst; + struct device *dev; + int ret; + + if (!vm->present) + return 0; + + ret = vrf_enable_interface(vst); + if (ret) + goto error; + + /* Disable IPv6 for vrf ports */ + if (!(vm->dev.dev->settings.flags & DEV_OPT_IPV6)) { + vm->dev.dev->settings.ipv6 = 0; + vm->dev.dev->settings.flags |= DEV_OPT_IPV6; + } + + ret = device_claim(&vm->dev); + if (ret < 0) + goto error; + + dev = vm->dev.dev; + if (dev->settings.auth && !dev->auth_status) + return -1; + + if (vm->active) + return 0; + + ret = system_vrf_addif(&vst->dev, vm->dev.dev); + if (ret < 0) { + D(DEVICE, "Vrf device %s could not be added\n", vm->dev.dev->ifname); + goto error; + } + + vm->active = true; + device_set_present(&vst->dev, true); + device_broadcast_event(&vst->dev, DEV_EVENT_TOPO_CHANGE); + + return 0; + +error: + vst->n_failed++; + vm->present = false; + vst->n_present--; + device_release(&vm->dev); + + return ret; +} + +static void +vrf_remove_member(struct vrf_member *vm) +{ + struct vrf_state *vst = vm->vst; + + if (!vm->present) + return; + + if (vst->dev.active) + vrf_disable_member(vm, false); + + vm->present = false; + vm->vst->n_present--; + + if (vm == vst->primary_port) + vrf_reset_primary(vst); + + if (vst->vrf_empty) + return; + + vst->force_active = false; + if (vst->n_present == 0) + device_set_present(&vst->dev, false); +} + +static void +vrf_free_member(struct vrf_member *vm) +{ + struct device *dev = vm->dev.dev; + + vrf_remove_member(vm); + device_remove_user(&vm->dev); + + /* + * When reloading the config and moving a device from one vrf to + * another, the other vrf may have tried to claim this device + * before it was removed here. + * Ensure that claiming the device is retried by toggling its present + * state + */ + if (dev->present) { + device_set_present(dev, false); + device_set_present(dev, true); + } + + free(vm); +} + +static void +vrf_check_retry(struct vrf_state *vst) +{ + if (!vst->n_failed) + return; + + uloop_timeout_set(&vst->retry, 100); +} + +static void +vrf_member_cb(struct device_user *dep, enum device_event ev) +{ + struct vrf_member *vm = container_of(dep, struct vrf_member, dev); + struct vrf_state *vst = vm->vst; + struct device *dev = dep->dev; + + switch (ev) { + case DEV_EVENT_ADD: + assert(!vm->present); + + vm->present = true; + vst->n_present++; + + if (vst->n_present == 1) + device_set_present(&vst->dev, true); + fallthrough; + case DEV_EVENT_AUTH_UP: + if (!vst->dev.active) + break; + + if (vrf_enable_member(vm)) + break; + + /* + * Adding a vrf port can overwrite the vrf device mtu + * in the kernel, apply the vrf settings in case the + * vrf device mtu is set + */ + system_if_apply_settings(&vst->dev, &vst->dev.settings, + DEV_OPT_MTU | DEV_OPT_MTU6); + break; + case DEV_EVENT_LINK_DOWN: + if (!dev->settings.auth) + break; + + vrf_disable_member(vm, true); + break; + case DEV_EVENT_REMOVE: + if (dep->hotplug && !dev->sys_present) { + vlist_delete(&vst->members, &vm->node); + return; + } + + if (vm->present) + vrf_remove_member(vm); + + break; + default: + return; + } +} + +static int +vrf_set_down(struct vrf_state *vst) +{ + struct vrf_member *vm; + + vst->set_state(&vst->dev, false); + + vlist_for_each_element(&vst->members, vm, node) + vrf_disable_member(vm, false); + + vrf_disable_interface(vst); + + return 0; +} + +static int +vrf_set_up(struct vrf_state *vst) +{ + struct vrf_member *vm; + int ret; + + if (!vst->n_present) { + if (!vst->force_active) + return -ENOENT; + + ret = vrf_enable_interface(vst); + if (ret) + return ret; + } + + vst->n_failed = 0; + vlist_for_each_element(&vst->members, vm, node) + vrf_enable_member(vm); + vrf_check_retry(vst); + + if (!vst->force_active && !vst->n_present) { + /* initialization of all port member failed */ + vrf_disable_interface(vst); + device_set_present(&vst->dev, false); + return -ENOENT; + } + + vrf_reset_primary(vst); + ret = vst->set_state(&vst->dev, true); + if (ret < 0) + vrf_set_down(vst); + + return ret; +} + +static int +vrf_set_state(struct device *dev, bool up) +{ + struct vrf_state *vst; + + vst = container_of(dev, struct vrf_state, dev); + + if (up) + return vrf_set_up(vst); + else + return vrf_set_down(vst); +} + +static struct vrf_member * +vrf_create_member(struct vrf_state *vst, const char *name, + struct device *dev, bool hotplug) +{ + struct vrf_member *vm; + + vm = calloc(1, sizeof(*vm) + strlen(name) + 1); + if (!vm) + return NULL; + + vm->vst = vst; + vm->dev.cb = vrf_member_cb; + vm->dev.hotplug = hotplug; + strcpy(vm->name, name); + vm->dev.dev = dev; + vlist_add(&vst->members, &vm->node, vm->name); + /* + * Need to look up the vrf port again as the above + * created pointer will be freed in case the vrf port + * already existed + */ + vm = vlist_find(&vst->members, name, vm, node); + if (hotplug && vm) + vm->node.version = -1; + + return vm; +} + +static void +vrf_member_update(struct vlist_tree *tree, struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct vrf_member *vm; + struct device *dev; + + if (node_new) { + vm = container_of(node_new, struct vrf_member, node); + + if (node_old) { + free(vm); + return; + } + + dev = vm->dev.dev; + vm->dev.dev = NULL; + device_add_user(&vm->dev, dev); + } + + + if (node_old) { + vm = container_of(node_old, struct vrf_member, node); + vrf_free_member(vm); + } +} + +static void +vrf_add_member(struct vrf_state *vst, const char *name) +{ + struct device *dev; + + dev = device_get(name, true); + if (!dev) + return; + + vrf_create_member(vst, name, dev, false); +} + +static int +vrf_hotplug_add(struct device *dev, struct device *member, struct blob_attr *vlan) +{ + struct vrf_state *vst = container_of(dev, struct vrf_state, dev); + struct vrf_member *vm; + + vm = vlist_find(&vst->members, member->ifname, vm, node); + if (!vm) + vrf_create_member(vst, member->ifname, member, true); + + return 0; +} + +static int +vrf_hotplug_del(struct device *dev, struct device *member, struct blob_attr *vlan) +{ + struct vrf_state *vst = container_of(dev, struct vrf_state, dev); + struct vrf_member *vm; + + vm = vlist_find(&vst->members, member->ifname, vm, node); + if (!vm) + return UBUS_STATUS_NOT_FOUND; + + if (vm->dev.hotplug) + vlist_delete(&vst->members, &vm->node); + + return 0; +} + +static int +vrf_hotplug_prepare(struct device *dev, struct device **vrf_dev) +{ + struct vrf_state *vst; + + if (vrf_dev) + *vrf_dev = dev; + + vst = container_of(dev, struct vrf_state, dev); + vst->force_active = true; + device_set_present(&vst->dev, true); + + return 0; +} + +static const struct device_hotplug_ops vrf_ops = { + .prepare = vrf_hotplug_prepare, + .add = vrf_hotplug_add, + .del = vrf_hotplug_del +}; + +static void +vrf_free(struct device *dev) +{ + struct vrf_state *vst; + + vst = container_of(dev, struct vrf_state, dev); + vlist_flush_all(&vst->members); + free(vst->config_data); + free(vst); +} + +static void +vrf_dump_info(struct device *dev, struct blob_buf *b) +{ + struct vrf_state *vst; + struct vrf_member *vm; + void *list; + + vst = container_of(dev, struct vrf_state, dev); + + system_if_dump_info(dev, b); + list = blobmsg_open_array(b, "vrf-members"); + + vlist_for_each_element(&vst->members, vm, node) { + if (vm->dev.dev->hidden) + continue; + + blobmsg_add_string(b, NULL, vm->dev.dev->ifname); + } + + blobmsg_close_array(b, list); +} + +static void +vrf_config_init(struct device *dev) +{ + struct vrf_state *vst; + struct blob_attr *cur; + size_t rem; + + vst = container_of(dev, struct vrf_state, dev); + + if (vst->vrf_empty) { + vst->force_active = true; + device_set_present(&vst->dev, true); + } + + vst->n_failed = 0; + vlist_update(&vst->members); + if (vst->ports) { + blobmsg_for_each_attr(cur, vst->ports, rem) { + vrf_add_member(vst, blobmsg_data(cur)); + } + } + + vlist_flush(&vst->members); + vrf_check_retry(vst); +} + +static void +vrf_apply_settings(struct vrf_state *vst, struct blob_attr **tb) +{ + struct blob_attr *cur; + + vst->vrf_empty = true; + // default vrf routing table + vst->table = 10; + if ((cur = tb[VRF_ATTR_TABLE])) + system_resolve_rt_table(blobmsg_data(cur), &vst->table); +} + +static enum dev_change_type +vrf_reload(struct device *dev, struct blob_attr *attr) +{ + struct blob_attr *tb_dev[__DEV_ATTR_MAX]; + struct blob_attr *tb_v[__VRF_ATTR_MAX]; + enum dev_change_type ret = DEV_CONFIG_APPLIED; + struct vrf_state *vst; + unsigned long diff[2]; + + BUILD_BUG_ON(sizeof(diff) < __VRF_ATTR_MAX / BITS_PER_LONG); + BUILD_BUG_ON(sizeof(diff) < __DEV_ATTR_MAX / BITS_PER_LONG); + + vst = container_of(dev, struct vrf_state, dev); + attr = blob_memdup(attr); + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, tb_dev, + blob_data(attr), blob_len(attr)); + blobmsg_parse(vrf_attrs, __VRF_ATTR_MAX, tb_v, + blob_data(attr), blob_len(attr)); + + if (tb_dev[DEV_ATTR_MACADDR]) + vst->primary_port = NULL; + + vst->ports = tb_v[VRF_ATTR_PORTS]; + device_init_settings(dev, tb_dev); + vrf_apply_settings(vst, tb_v); + + if (vst->config_data) { + struct blob_attr *otb_dev[__DEV_ATTR_MAX]; + struct blob_attr *otb_v[__VRF_ATTR_MAX]; + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, otb_dev, + blob_data(vst->config_data), blob_len(vst->config_data)); + + diff[0] = diff[1] = 0; + uci_blob_diff(tb_dev, otb_dev, &device_attr_list, diff); + if (diff[0] | diff[1]) { + ret = DEV_CONFIG_RESTART; + D(DEVICE, "Vrf %s device attributes have changed, diff=[%lx %lx]\n", + dev->ifname, diff[1], diff[0]); + } + + blobmsg_parse(vrf_attrs, __VRF_ATTR_MAX, otb_v, + blob_data(vst->config_data), blob_len(vst->config_data)); + + diff[0] = diff[1] = 0; + uci_blob_diff(tb_v, otb_v, &vrf_attr_list, diff); + if (diff[0] & ~(1 << VRF_ATTR_PORTS)) { + ret = DEV_CONFIG_RESTART; + D(DEVICE, "Vrf %s attributes have changed, diff=[%lx %lx]\n", + dev->ifname, diff[1], diff[0]); + } + + vrf_config_init(dev); + } + + free(vst->config_data); + vst->config_data = attr; + return ret; +} + +static void +vrf_retry_members(struct uloop_timeout *timeout) +{ + struct vrf_state *vst = container_of(timeout, struct vrf_state, retry); + struct vrf_member *vm; + + vst->n_failed = 0; + vlist_for_each_element(&vst->members, vm, node) { + if (vm->present) + continue; + + if (!vm->dev.dev->present) + continue; + + vm->present = true; + vst->n_present++; + vrf_enable_member(vm); + } +} + +static struct device * +vrf_create(const char *name, struct device_type *devtype, + struct blob_attr *attr) +{ + struct vrf_state *vst; + struct device *dev = NULL; + + vst = calloc(1, sizeof(*vst)); + if (!vst) + return NULL; + + dev = &vst->dev; + + if (device_init(dev, devtype, name) < 0) { + device_cleanup(dev); + free(vst); + return NULL; + } + + dev->config_pending = true; + vst->retry.cb = vrf_retry_members; + + vst->set_state = dev->set_state; + dev->set_state = vrf_set_state; + + dev->hotplug_ops = &vrf_ops; + + vlist_init(&vst->members, avl_strcmp, vrf_member_update); + vst->members.keep_old = true; + + vrf_reload(dev, attr); + + return dev; +} + +static void __init vrf_state_type_init(void) +{ + device_type_add(&vrf_state_type); +}