Files
netifd/vrf.c
Felix Fietkau 6ead304877 global: use blobmsg_parse_attr
Avoids mismatch in blob vs blobmsg parsing, simplifies code.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2025-08-12 20:14:05 +02:00

686 lines
13 KiB
C

#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_attr(device_attr_list.params, __DEV_ATTR_MAX, tb_dev, attr);
blobmsg_parse_attr(vrf_attrs, __VRF_ATTR_MAX, tb_v, 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_attr(device_attr_list.params, __DEV_ATTR_MAX, otb_dev,
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_attr(vrf_attrs, __VRF_ATTR_MAX, otb_v,
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);
}