device: add support for configuring vrf
authorMaxim Anisimov <maxim.anisimov.ua@gmail.com>
Thu, 31 Oct 2024 06:25:45 +0000 (09:25 +0300)
committerRobert Marko <robimarko@gmail.com>
Sat, 17 May 2025 09:32:17 +0000 (11:32 +0200)
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 <newtwen+github@gmail.com>
Signed-off-by: Maxim Anisimov <maxim.anisimov.ua@gmail.com>
Link: https://github.com/openwrt/netifd/pull/38
Signed-off-by: Robert Marko <robimarko@gmail.com>
CMakeLists.txt
config.c
system-linux.c
system.h
vrf.c [new file with mode: 0644]

index a3eaf65e6c81ad605d9746a0433bd5fa19a43d53..ab533fa948115a3ccaedbc29ca04a0e91e91b7d2 100644 (file)
@@ -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)
index 5530050b080af97de979e96005bffa9703ff1b24..0ea39b5a05c50aa4064a46461985d991fe85f255 100644 (file)
--- 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
index 945ca4cef178912a918482f604eaa24aec8deecf..8fd0988efa61ce2054c41ae87854d5cd1bd82fee 100644 (file)
@@ -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;
index 890966b26e3b14654bfcdef0ff8c9e37e03a76d5..96bfd070bfecfa17a3f3c7116985a136a9b4343a 100644 (file)
--- 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 (file)
index 0000000..d27d5c3
--- /dev/null
+++ b/vrf.c
@@ -0,0 +1,687 @@
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+
+#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);
+}