Discussion:
[OpenWrt-Devel] [RFC/RFT] netifd: Initial OpenVSwitch support
Helmut Schaa
2013-11-29 15:24:07 UTC
Permalink
Add a new device type "ovs" to define openvswitch (pseudo-) bridges.
For setting up oppenvswitch bridges the "ovs-vsctl" utility is used
within system-ovs.* and might be replaced with plain JSON-RPC in the
future.

Signed-off-by: Helmut Schaa <***@googlemail.com>
---

This is a first RFC of openvswitch integration in netifd based on
netifd trunk. In parts its quite similar to the bridge code but due
to pseudo bridge support there's not much code to share.

I'd appreciate any testing beside my own :D

I don't really like calling the ovs-vsctl utility (see system-ovs.c)
but using JSON-RPC would add much more code & complexity and there is
no "easy-to-use" library yet that we could utilize. In the long term
all these forks should be replaced by proper IPC but for now it seems
good enough.

As next step I'd add another device-type "ovs-port" or similar to allow
definition of internal ovs ports and more complex port configurations
(for example adding eth0 as trunk port for vlan 1,2 and 3).

Thoughts?

Thanks,
Helmut

CMakeLists.txt | 2 +-
config.c | 2 +
config/network | 15 ++
device.h | 1 +
ovs.c | 664 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
system-linux.c | 6 +-
system-ovs.c | 115 ++++++++++
system-ovs.h | 31 +++
system.h | 2 +
9 files changed, 836 insertions(+), 2 deletions(-)
create mode 100644 ovs.c
create mode 100644 system-ovs.c
create mode 100644 system-ovs.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 65da3cf..e4190a0 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
- macvlan.c ubus.c wireless.c)
+ macvlan.c ubus.c wireless.c ovs.c system-ovs.c)


find_library(json NAMES json-c json)
diff --git a/config.c b/config.c
index bb0cd05..132817d 100644
--- a/config.c
+++ b/config.c
@@ -169,6 +169,8 @@ config_init_devices(void)
devtype = &tunnel_device_type;
else if (!strcmp(type, "macvlan"))
devtype = &macvlan_device_type;
+ else if (!strcmp(type, "ovs"))
+ devtype = &ovs_device_type;
}

if (!devtype)
diff --git a/config/network b/config/network
index b2985d3..aee4851 100644
--- a/config/network
+++ b/config/network
@@ -72,3 +72,18 @@ config route
option gateway 192.168.5.2
option interface wan

+# Basic OpenVSwitch
+config device
+ option type ovs
+ option name br-ovs
+ option ifname eth0
+ option ovs_empty 1
+
+# Pseudo bridge on top of br-ovs (vlan 1000)
+config device
+ option type ovs
+ option name br-ovs1000
+ option ovs_tag 1000
+ option ovs_base br-ovs
+ option ifname eth1
+
diff --git a/device.h b/device.h
index dbcaf77..24299a1 100644
--- a/device.h
+++ b/device.h
@@ -153,6 +153,7 @@ 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;
+extern const struct device_type ovs_device_type;

void device_lock(void);
void device_unlock(void);
diff --git a/ovs.c b/ovs.c
new file mode 100644
index 0000000..29db26c
--- /dev/null
+++ b/ovs.c
@@ -0,0 +1,664 @@
+/*
+ * netifd - network interface daemon
+ * Copyright (C) 2013 Helmut Schaa <***@googlemail.com>
+ *
+ * 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 "netifd.h"
+#include "device.h"
+#include "interface.h"
+#include "system-ovs.h"
+
+enum {
+ OVS_ATTR_IFNAME,
+ OVS_ATTR_BASE,
+ OVS_ATTR_TAG,
+ OVS_ATTR_EMPTY,
+ __OVS_ATTR_MAX
+};
+
+static const struct blobmsg_policy ovs_attrs[__OVS_ATTR_MAX] = {
+ [OVS_ATTR_IFNAME] = { "ifname", BLOBMSG_TYPE_ARRAY },
+ [OVS_ATTR_BASE] = { "ovs_base", BLOBMSG_TYPE_STRING },
+ [OVS_ATTR_TAG] = { "ovs_tag", BLOBMSG_TYPE_INT32 },
+ [OVS_ATTR_EMPTY] = { "ovs_empty", BLOBMSG_TYPE_BOOL },
+};
+
+static const struct uci_blob_param_info ovs_attr_info[__OVS_ATTR_MAX] = {
+ [OVS_ATTR_IFNAME] = { .type = BLOBMSG_TYPE_STRING },
+};
+
+static const struct uci_blob_param_list ovs_attr_list = {
+ .n_params = __OVS_ATTR_MAX,
+ .params = ovs_attrs,
+ .info = ovs_attr_info,
+
+ .n_next = 1,
+ .next = { &device_attr_list },
+};
+
+static struct device *ovs_create(const char *name, struct blob_attr *attr);
+static void ovs_config_init(struct device *dev);
+static void ovs_free(struct device *dev);
+static void ovs_dump_info(struct device *dev, struct blob_buf *b);
+enum dev_change_type
+ovs_reload(struct device *dev, struct blob_attr *attr);
+
+const struct device_type ovs_device_type = {
+ .name = "OpenVSwitch",
+ .config_params = &ovs_attr_list,
+
+ .create = ovs_create,
+ .config_init = ovs_config_init,
+ .reload = ovs_reload,
+ .free = ovs_free,
+ .dump_info = ovs_dump_info,
+};
+
+struct ovs_state {
+ struct device dev;
+ device_state_cb set_state;
+
+ struct blob_attr *config_data;
+ struct ovs_config config;
+ struct blob_attr *ifnames;
+ bool active;
+ bool force_active;
+
+ struct vlist_tree ports;
+ int n_present;
+
+ struct ovs_base *base;
+};
+
+struct ovs_port {
+ struct vlist_node node;
+ struct ovs_state *ost;
+ struct device_user dev;
+ bool present;
+ char name[];
+};
+
+struct ovs_base {
+ struct ovs_state *ost;
+ struct device_user dev;
+ bool present;
+};
+
+static void ovs_set_present(struct ovs_state *ost);
+
+static int
+ovs_disable_base(struct ovs_base *ob)
+{
+ if (!ob->present)
+ return 0;
+
+ device_release(&ob->dev);
+
+ return 0;
+}
+
+static int
+ovs_enable_base(struct ovs_base *ob)
+{
+ int ret;
+
+ if (!ob->present)
+ return 0;
+
+ ret = device_claim(&ob->dev);
+ if (ret < 0)
+ goto error;
+
+ return 0;
+
+error:
+ ob->present = false;
+ return ret;
+}
+
+static void
+ovs_remove_base(struct ovs_base *ob)
+{
+ struct ovs_state *ost = ob->ost;
+
+ if (!ob->present)
+ return;
+
+ if (ost->dev.active)
+ ovs_disable_base(ob);
+
+ ob->present = false;
+ ovs_set_present(ost);
+}
+
+static void
+ovs_free_base(struct ovs_base *ob)
+{
+ ovs_remove_base(ob);
+ device_remove_user(&ob->dev);
+ free(ob);
+}
+
+static int
+ovs_disable_port(struct ovs_port *op)
+{
+ struct ovs_state *ost = op->ost;
+
+ if (!op->present)
+ return 0;
+
+ system_ovs_delport(&ost->dev, op->dev.dev);
+ device_release(&op->dev);
+
+ return 0;
+}
+
+static int
+ovs_enable_port(struct ovs_port *op)
+{
+ struct ovs_state *ost = op->ost;
+ int ret;
+
+ if (!op->present)
+ return 0;
+
+ ret = device_claim(&op->dev);
+ if (ret < 0)
+ goto error;
+
+ ret = system_ovs_addport(&ost->dev, op->dev.dev);
+ if (ret < 0) {
+ D(DEVICE, "Bridge device %s could not be added\n", op->dev.dev->ifname);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ op->present = false;
+ ost->n_present--;
+ return ret;
+}
+
+static void
+ovs_remove_port(struct ovs_port *op)
+{
+ struct ovs_state *ost = op->ost;
+
+ if (!op->present)
+ return;
+
+ if (ost->dev.active)
+ ovs_disable_port(op);
+
+ op->present = false;
+ op->ost->n_present--;
+
+ ovs_set_present(ost);
+}
+
+static void
+ovs_free_port(struct ovs_port *op)
+{
+ struct device *dev = op->dev.dev;
+
+ ovs_remove_port(op);
+ device_remove_user(&op->dev);
+
+ /*
+ * When reloading the config and moving a device from one bridge to
+ * another, the other bridge 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(op);
+}
+
+static void
+ovs_port_cb(struct device_user *dev, enum device_event ev)
+{
+ struct ovs_port *op = container_of(dev, struct ovs_port, dev);
+ struct ovs_state *ost = op->ost;
+
+ switch (ev) {
+ case DEV_EVENT_ADD:
+ assert(!op->present);
+
+ op->present = true;
+ ost->n_present++;
+
+ if (ost->dev.active)
+ ovs_enable_port(op);
+ else if (ost->n_present == 1)
+ ovs_set_present(ost);
+
+ break;
+ case DEV_EVENT_REMOVE:
+ if (dev->hotplug) {
+ vlist_delete(&ost->ports, &op->node);
+ return;
+ }
+
+ if (op->present)
+ ovs_remove_port(op);
+
+ break;
+ default:
+ return;
+ }
+}
+
+static void
+ovs_base_cb(struct device_user *dev, enum device_event ev)
+{
+ struct ovs_base *ob = container_of(dev, struct ovs_base, dev);
+ struct ovs_state *ost = ob->ost;
+
+
+ switch (ev) {
+ case DEV_EVENT_ADD:
+ ob->present = true;
+ ovs_enable_base(ob);
+ ovs_set_present(ost);
+
+ case DEV_EVENT_REMOVE:
+ if (ob->present)
+ ovs_remove_base(ob);
+
+ break;
+ default:
+ return;
+ }
+}
+
+static int
+ovs_set_down(struct ovs_state *ost)
+{
+ struct ovs_port *op;
+
+ ost->set_state(&ost->dev, false);
+
+ vlist_for_each_element(&ost->ports, op, node)
+ ovs_disable_port(op);
+
+ if (ost->base)
+ ovs_disable_base(ost->base);
+
+ system_ovs_delbr(&ost->dev);
+
+ return 0;
+}
+
+static int
+ovs_set_up(struct ovs_state *ost)
+{
+ struct ovs_port *op;
+ int ret;
+
+ if (!ost->force_active && !ost->n_present)
+ return -ENOENT;
+
+ if (ost->base)
+ ovs_enable_base(ost->base);
+
+ ret = system_ovs_addbr(&ost->dev, &ost->config);
+ if (ret < 0)
+ goto out;
+
+ vlist_for_each_element(&ost->ports, op, node)
+ ovs_enable_port(op);
+
+ if (!ost->force_active && !ost->n_present) {
+ /* initialization of all port interfaces failed */
+ system_ovs_delbr(&ost->dev);
+ ovs_set_present(ost);
+ return -ENOENT;
+ }
+
+ ret = ost->set_state(&ost->dev, true);
+ if (ret < 0)
+ ovs_set_down(ost);
+
+out:
+ return ret;
+}
+
+static int
+ovs_set_state(struct device *dev, bool up)
+{
+ struct ovs_state *ost;
+
+ ost = container_of(dev, struct ovs_state, dev);
+
+ if (up)
+ return ovs_set_up(ost);
+ else
+ return ovs_set_down(ost);
+}
+
+static struct ovs_port *
+ovs_create_port(struct ovs_state *ost, struct device *dev, bool hotplug)
+{
+ struct ovs_port *op;
+
+ op = calloc(1, sizeof(*op) + strlen(dev->ifname) + 1);
+ op->ost = ost;
+ op->dev.cb = ovs_port_cb;
+ op->dev.hotplug = hotplug;
+ strcpy(op->name, dev->ifname);
+ op->dev.dev = dev;
+ vlist_add(&ost->ports, &op->node, op->name);
+ if (hotplug)
+ op->node.version = -1;
+
+ return op;
+}
+
+static void
+ovs_port_update(struct vlist_tree *tree, struct vlist_node *node_new,
+ struct vlist_node *node_old)
+{
+ struct ovs_port *op;
+ struct device *dev;
+
+ if (node_new) {
+ op = container_of(node_new, struct ovs_port, node);
+
+ if (node_old) {
+ free(op);
+ return;
+ }
+
+ dev = op->dev.dev;
+ op->dev.dev = NULL;
+ device_add_user(&op->dev, dev);
+ }
+
+
+ if (node_old) {
+ op = container_of(node_old, struct ovs_port, node);
+ ovs_free_port(op);
+ }
+}
+
+
+static void
+ovs_add_port(struct ovs_state *ost, const char *name)
+{
+ struct device *dev;
+
+ dev = device_get(name, true);
+ if (!dev)
+ return;
+
+ ovs_create_port(ost, dev, false);
+}
+
+static struct ovs_base *
+ovs_create_base(struct ovs_state *ost, struct device *dev, bool hotplug)
+{
+ struct ovs_base *ob;
+
+ ob = calloc(1, sizeof(*ob));
+ if (!ob)
+ return NULL;
+
+ ob->ost = ost;
+ ob->dev.cb = ovs_base_cb;
+
+ device_add_user(&ob->dev, dev);
+
+ return ob;
+}
+static void
+ovs_add_base(struct ovs_state *ost, const char *name)
+{
+ struct device *dev;
+
+ dev = device_get(name, true);
+ if (!dev)
+ return;
+
+ ovs_create_base(ost, dev, false);
+}
+
+static int
+ovs_hotplug_add(struct device *dev, struct device *port)
+{
+ struct ovs_state *ost = container_of(dev, struct ovs_state, dev);
+
+ ovs_create_port(ost, port, true);
+
+ return 0;
+}
+
+static int
+ovs_hotplug_del(struct device *dev, struct device *port)
+{
+ struct ovs_state *ost = container_of(dev, struct ovs_state, dev);
+ struct ovs_port *op;
+
+ op = vlist_find(&ost->ports, port->ifname, op, node);
+ if (!op)
+ return UBUS_STATUS_NOT_FOUND;
+
+ vlist_delete(&ost->ports, &op->node);
+ return 0;
+}
+
+static int
+ovs_hotplug_prepare(struct device *dev)
+{
+ struct ovs_state *ost;
+
+ ost = container_of(dev, struct ovs_state, dev);
+ ost->force_active = true;
+ ovs_set_present(ost);
+
+ return 0;
+}
+
+static const struct device_hotplug_ops ovs_ops = {
+ .prepare = ovs_hotplug_prepare,
+ .add = ovs_hotplug_add,
+ .del = ovs_hotplug_del
+};
+
+static void
+ovs_free(struct device *dev)
+{
+ struct ovs_state *ost;
+ ost = container_of(dev, struct ovs_state, dev);
+
+ if (ost->base)
+ ovs_free_base(ost->base);
+ vlist_flush_all(&ost->ports);
+ free(ost);
+}
+
+static void
+ovs_dump_info(struct device *dev, struct blob_buf *b)
+{
+ struct ovs_state *ost;
+ struct ovs_port *op;
+ void *list;
+
+ ost = container_of(dev, struct ovs_state, dev);
+
+ system_if_dump_info(dev, b);
+ list = blobmsg_open_array(b, "ovs-ports");
+
+ vlist_for_each_element(&ost->ports, op, node)
+ blobmsg_add_string(b, NULL, op->dev.dev->ifname);
+
+ blobmsg_close_array(b, list);
+ if (ost->base)
+ blobmsg_add_string(b, "ovs_base", ost->base->dev.dev->ifname);
+}
+
+static void
+ovs_set_present(struct ovs_state *ost)
+{
+ bool present = false;
+ if (ost->base) {
+ /* Base device has to be available first */
+ if (!ost->base->present)
+ goto out;
+ }
+
+ if (ost->config.empty) {
+ present = true;
+ goto out;
+ }
+
+ ost->force_active = false;
+ if (ost->n_present == 0)
+ goto out;
+
+ present = true;
+out:
+ device_set_present(&ost->dev, present);
+}
+
+static void
+ovs_config_init(struct device *dev)
+{
+ struct ovs_state *ost;
+ struct blob_attr *cur;
+ int rem;
+
+ ost = container_of(dev, struct ovs_state, dev);
+
+ if (ost->config.empty) {
+ ost->force_active = true;
+ ovs_set_present(ost);
+ }
+
+ if (ost->config.base) {
+ /* Pseudo bridge, requires base */
+ ovs_add_base(ost, ost->config.base);
+ }
+
+ vlist_update(&ost->ports);
+ if (ost->ifnames) {
+ blobmsg_for_each_attr(cur, ost->ifnames, rem) {
+ ovs_add_port(ost, blobmsg_data(cur));
+ }
+ }
+ vlist_flush(&ost->ports);
+}
+
+static void
+ovs_apply_settings(struct ovs_state *ost, struct blob_attr **tb)
+{
+ struct ovs_config *cfg = &ost->config;
+
+ /* defaults */
+ cfg->tag = 0;
+ cfg->base = NULL;
+ cfg->empty = false;
+
+ if (tb[OVS_ATTR_TAG] && tb[OVS_ATTR_BASE] ) {
+ cfg->tag = blobmsg_get_u32(tb[OVS_ATTR_TAG]);
+ cfg->base = blobmsg_get_string(tb[OVS_ATTR_BASE]);
+ }
+
+ if (tb[OVS_ATTR_EMPTY])
+ cfg->empty = blobmsg_get_bool(tb[OVS_ATTR_EMPTY]);
+}
+
+enum dev_change_type
+ovs_reload(struct device *dev, struct blob_attr *attr)
+{
+ struct blob_attr *tb_dev[__DEV_ATTR_MAX];
+ struct blob_attr *tb_br[__OVS_ATTR_MAX];
+ enum dev_change_type ret = DEV_CONFIG_APPLIED;
+ unsigned long diff;
+ struct ovs_state *ost;
+
+ BUILD_BUG_ON(sizeof(diff) < __OVS_ATTR_MAX / 8);
+ BUILD_BUG_ON(sizeof(diff) < __DEV_ATTR_MAX / 8);
+
+ ost = container_of(dev, struct ovs_state, dev);
+
+ blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, tb_dev,
+ blob_data(attr), blob_len(attr));
+ blobmsg_parse(ovs_attrs, __OVS_ATTR_MAX, tb_br,
+ blob_data(attr), blob_len(attr));
+
+ ost->ifnames = tb_br[OVS_ATTR_IFNAME];
+ device_init_settings(dev, tb_dev);
+ ovs_apply_settings(ost, tb_br);
+
+ if (ost->config_data) {
+ struct blob_attr *otb_dev[__DEV_ATTR_MAX];
+ struct blob_attr *otb_br[__OVS_ATTR_MAX];
+
+ blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, otb_dev,
+ blob_data(ost->config_data), blob_len(ost->config_data));
+
+ diff = 0;
+ uci_blob_diff(tb_dev, otb_dev, &device_attr_list, &diff);
+ if (diff & ~(1 << DEV_ATTR_IFNAME))
+ ret = DEV_CONFIG_RESTART;
+
+ blobmsg_parse(ovs_attrs, __OVS_ATTR_MAX, otb_br,
+ blob_data(ost->config_data), blob_len(ost->config_data));
+
+ diff = 0;
+ uci_blob_diff(tb_br, otb_br, &ovs_attr_list, &diff);
+ if (diff & ~(1 << OVS_ATTR_IFNAME))
+ ret = DEV_CONFIG_RESTART;
+
+ ovs_config_init(dev);
+ }
+
+ ost->config_data = attr;
+ return ret;
+}
+
+static struct device *
+ovs_create(const char *name, struct blob_attr *attr)
+{
+ struct ovs_state *ost;
+ struct device *dev = NULL;
+
+ ost = calloc(1, sizeof(*ost));
+ if (!ost)
+ return NULL;
+
+ dev = &ost->dev;
+ device_init(dev, &ovs_device_type, name);
+ dev->config_pending = true;
+
+ ost->set_state = dev->set_state;
+ dev->set_state = ovs_set_state;
+
+ dev->hotplug_ops = &ovs_ops;
+
+ vlist_init(&ost->ports, avl_strcmp, ovs_port_update);
+ ost->ports.keep_old = true;
+ ovs_reload(dev, attr);
+
+ return dev;
+}
diff --git a/system-linux.c b/system-linux.c
index d01d7e3..f30fba9 100644
--- a/system-linux.c
+++ b/system-linux.c
@@ -54,6 +54,7 @@
#include "netifd.h"
#include "device.h"
#include "system.h"
+#include "system-ovs.h"

struct event_socket {
struct uloop_fd uloop;
@@ -166,7 +167,7 @@ static void system_set_dev_sysctl(const char *path, const char *device, const ch
system_set_sysctl(dev_buf, val);
}

-static void system_set_disable_ipv6(struct device *dev, const char *val)
+void system_set_disable_ipv6(struct device *dev, const char *val)
{
system_set_dev_sysctl("/proc/sys/net/ipv6/conf/%s/disable_ipv6", dev->ifname, val);
}
@@ -543,6 +544,9 @@ void system_if_clear_state(struct device *dev)

system_if_flags(dev->ifname, 0, IFF_UP);

+ /* Clear ovs state first to not conflict with the ovs bridge compat mode */
+ system_ovs_if_clear_state(dev);
+
if (system_is_bridge(dev->ifname, buf, sizeof(buf))) {
D(SYSTEM, "Delete existing bridge named '%s'\n", dev->ifname);
system_bridge_delbr(dev);
diff --git a/system-ovs.c b/system-ovs.c
new file mode 100644
index 0000000..81169fd
--- /dev/null
+++ b/system-ovs.c
@@ -0,0 +1,115 @@
+/*
+ * netifd - network interface daemon
+ * Copyright (C) 2013 Helmut Schaa <***@googlemail.com>
+ *
+ * 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 <sys/wait.h>
+#include "netifd.h"
+#include "system.h"
+#include "system-ovs.h"
+
+#define run_prog(p, ...) ({ \
+ int rc = -1, status; \
+ pid_t pid = fork(); \
+ if (!pid) \
+ exit(execl(p, p, ##__VA_ARGS__, NULL)); \
+ if (pid < 0) {\
+ rc = -1;\
+ } else {\
+ while ((rc = waitpid(pid, &status, 0)) == -1 && errno == EINTR); \
+ rc = (rc == pid && WIFEXITED(status)) ? WEXITSTATUS(status) : -1; \
+ }\
+ rc;\
+})
+
+static char *system_get_ovs(const char *name)
+{
+ FILE *f;
+ char cmd[64];
+ static char dev[64];
+
+ sprintf(cmd, "/usr/bin/ovs-vsctl iface-to-br %s", name);
+ f = popen(cmd, "r");
+ if (!f)
+ return NULL;
+ fgets(dev, sizeof(dev), f);
+ pclose(f);
+ return dev;
+}
+
+static bool system_ovs_isbr(const char *name)
+{
+ if (run_prog("/usr/bin/ovs-vsctl", "br-exists", name) == 0)
+ return true;
+ return false;
+}
+
+void system_ovs_if_clear_state(struct device *dev)
+{
+ char *ovs;
+
+ if (system_ovs_isbr(dev->ifname)) {
+ system_ovs_delbr(dev);
+ return;
+ }
+
+ ovs = system_get_ovs(dev->ifname);
+ if (ovs)
+ run_prog("/usr/bin/ovs-vsctl", "del-port", dev->ifname);
+}
+
+
+int system_ovs_delbr(struct device *ovs)
+{
+ if (run_prog("/usr/bin/ovs-vsctl", "del-br", ovs->ifname))
+ return -1;
+ return 0;
+}
+
+int system_ovs_addbr(struct device *ovs, struct ovs_config *cfg)
+{
+ char buf[16];
+ if (cfg->tag && cfg->base) {
+ /* Pseudo bridge on top of an openvswitch */
+ snprintf(buf, sizeof(buf), "%u", cfg->tag);
+ if (run_prog("/usr/bin/ovs-vsctl", "add-br", ovs->ifname, cfg->base, buf))
+ return -1;
+ return 0;
+ }
+ if (run_prog("/usr/bin/ovs-vsctl", "add-br", ovs->ifname))
+ return -1;
+ return 0;
+}
+
+int system_ovs_addport(struct device *ovs, struct device *dev)
+{
+ char *old_ovs;
+ system_set_disable_ipv6(dev, "1");
+
+ old_ovs = system_get_ovs(dev->ifname);
+ if (old_ovs && !strcmp(old_ovs, ovs->ifname))
+ return 0;
+
+ if (run_prog("/usr/bin/ovs-vsctl", "add-port", ovs->ifname, dev->ifname))
+ return -1;
+
+ return 0;
+}
+
+int system_ovs_delport(struct device *ovs, struct device *dev)
+{
+ system_set_disable_ipv6(dev, "0");
+ if (run_prog("/usr/bin/ovs-vsctl", "del-port", ovs->ifname, dev->ifname))
+ return -1;
+ return 0;
+}
diff --git a/system-ovs.h b/system-ovs.h
new file mode 100644
index 0000000..ad018d4
--- /dev/null
+++ b/system-ovs.h
@@ -0,0 +1,31 @@
+/*
+ * netifd - network interface daemon
+ * Copyright (C) 2013 Helmut Schaa <***@googlemail.com>
+ *
+ * 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.
+ */
+#ifndef __NETIFD_SYSTEM_OVS_H
+#define __NETIFD_SYSTEM_OVS_H
+
+#include "system.h"
+
+struct ovs_config {
+ bool empty;
+ int tag;
+ char *base;
+};
+
+void system_ovs_if_clear_state(struct device *dev);
+int system_ovs_delbr(struct device *ovs);
+int system_ovs_addbr(struct device *ovs, struct ovs_config *cfg);
+int system_ovs_addport(struct device *ovs, struct device *dev);
+int system_ovs_delport(struct device *ovs, struct device *dev);
+
+#endif
diff --git a/system.h b/system.h
index ad74156..4f27e3a 100644
--- a/system.h
+++ b/system.h
@@ -129,4 +129,6 @@ void system_fd_set_cloexec(int fd);

int system_update_ipv6_mtu(struct device *device, int mtu);

+void system_set_disable_ipv6(struct device *dev, const char *val);
+
#endif
--
1.8.1.4
Roberto Riggio
2014-02-02 20:01:12 UTC
Permalink
Hi,

I'm testing your patch with this configuration in /etc/config/network

config device
option name 'ovs'
option type 'ovs'
list ifname 'eth0'

config interface
option ifname 'ovs'
option proto 'dhcp'

However at boot the router enters in an infinite loop:

IPV6: addrconf eth0 link becomes ready
8021q adding vlan 0 to hw filter on device eth0
IPV6: addrconf eth0 link is not ready

R.
Post by Helmut Schaa
Add a new device type "ovs" to define openvswitch (pseudo-) bridges.
For setting up oppenvswitch bridges the "ovs-vsctl" utility is used
within system-ovs.* and might be replaced with plain JSON-RPC in the
future.
---
This is a first RFC of openvswitch integration in netifd based on
netifd trunk. In parts its quite similar to the bridge code but due
to pseudo bridge support there's not much code to share.
I'd appreciate any testing beside my own :D
I don't really like calling the ovs-vsctl utility (see system-ovs.c)
but using JSON-RPC would add much more code & complexity and there is
no "easy-to-use" library yet that we could utilize. In the long term
all these forks should be replaced by proper IPC but for now it seems
good enough.
As next step I'd add another device-type "ovs-port" or similar to allow
definition of internal ovs ports and more complex port configurations
(for example adding eth0 as trunk port for vlan 1,2 and 3).
Thoughts?
Thanks,
Helmut
CMakeLists.txt | 2 +-
config.c | 2 +
config/network | 15 ++
device.h | 1 +
ovs.c | 664 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
system-linux.c | 6 +-
system-ovs.c | 115 ++++++++++
system-ovs.h | 31 +++
system.h | 2 +
9 files changed, 836 insertions(+), 2 deletions(-)
create mode 100644 ovs.c
create mode 100644 system-ovs.c
create mode 100644 system-ovs.h
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 65da3cf..e4190a0 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
- macvlan.c ubus.c wireless.c)
+ macvlan.c ubus.c wireless.c ovs.c system-ovs.c)
find_library(json NAMES json-c json)
diff --git a/config.c b/config.c
index bb0cd05..132817d 100644
--- a/config.c
+++ b/config.c
@@ -169,6 +169,8 @@ config_init_devices(void)
devtype = &tunnel_device_type;
else if (!strcmp(type, "macvlan"))
devtype = &macvlan_device_type;
+ else if (!strcmp(type, "ovs"))
+ devtype = &ovs_device_type;
}
if (!devtype)
diff --git a/config/network b/config/network
index b2985d3..aee4851 100644
--- a/config/network
+++ b/config/network
@@ -72,3 +72,18 @@ config route
option gateway 192.168.5.2
option interface wan
+# Basic OpenVSwitch
+config device
+ option type ovs
+ option name br-ovs
+ option ifname eth0
+ option ovs_empty 1
+
+# Pseudo bridge on top of br-ovs (vlan 1000)
+config device
+ option type ovs
+ option name br-ovs1000
+ option ovs_tag 1000
+ option ovs_base br-ovs
+ option ifname eth1
+
diff --git a/device.h b/device.h
index dbcaf77..24299a1 100644
--- a/device.h
+++ b/device.h
@@ -153,6 +153,7 @@ 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;
+extern const struct device_type ovs_device_type;
void device_lock(void);
void device_unlock(void);
diff --git a/ovs.c b/ovs.c
new file mode 100644
index 0000000..29db26c
--- /dev/null
+++ b/ovs.c
@@ -0,0 +1,664 @@
+/*
+ * netifd - network interface daemon
+ *
+ * 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 "netifd.h"
+#include "device.h"
+#include "interface.h"
+#include "system-ovs.h"
+
+enum {
+ OVS_ATTR_IFNAME,
+ OVS_ATTR_BASE,
+ OVS_ATTR_TAG,
+ OVS_ATTR_EMPTY,
+ __OVS_ATTR_MAX
+};
+
+static const struct blobmsg_policy ovs_attrs[__OVS_ATTR_MAX] = {
+ [OVS_ATTR_IFNAME] = { "ifname", BLOBMSG_TYPE_ARRAY },
+ [OVS_ATTR_BASE] = { "ovs_base", BLOBMSG_TYPE_STRING },
+ [OVS_ATTR_TAG] = { "ovs_tag", BLOBMSG_TYPE_INT32 },
+ [OVS_ATTR_EMPTY] = { "ovs_empty", BLOBMSG_TYPE_BOOL },
+};
+
+static const struct uci_blob_param_info ovs_attr_info[__OVS_ATTR_MAX] = {
+ [OVS_ATTR_IFNAME] = { .type = BLOBMSG_TYPE_STRING },
+};
+
+static const struct uci_blob_param_list ovs_attr_list = {
+ .n_params = __OVS_ATTR_MAX,
+ .params = ovs_attrs,
+ .info = ovs_attr_info,
+
+ .n_next = 1,
+ .next = { &device_attr_list },
+};
+
+static struct device *ovs_create(const char *name, struct blob_attr *attr);
+static void ovs_config_init(struct device *dev);
+static void ovs_free(struct device *dev);
+static void ovs_dump_info(struct device *dev, struct blob_buf *b);
+enum dev_change_type
+ovs_reload(struct device *dev, struct blob_attr *attr);
+
+const struct device_type ovs_device_type = {
+ .name = "OpenVSwitch",
+ .config_params = &ovs_attr_list,
+
+ .create = ovs_create,
+ .config_init = ovs_config_init,
+ .reload = ovs_reload,
+ .free = ovs_free,
+ .dump_info = ovs_dump_info,
+};
+
+struct ovs_state {
+ struct device dev;
+ device_state_cb set_state;
+
+ struct blob_attr *config_data;
+ struct ovs_config config;
+ struct blob_attr *ifnames;
+ bool active;
+ bool force_active;
+
+ struct vlist_tree ports;
+ int n_present;
+
+ struct ovs_base *base;
+};
+
+struct ovs_port {
+ struct vlist_node node;
+ struct ovs_state *ost;
+ struct device_user dev;
+ bool present;
+ char name[];
+};
+
+struct ovs_base {
+ struct ovs_state *ost;
+ struct device_user dev;
+ bool present;
+};
+
+static void ovs_set_present(struct ovs_state *ost);
+
+static int
+ovs_disable_base(struct ovs_base *ob)
+{
+ if (!ob->present)
+ return 0;
+
+ device_release(&ob->dev);
+
+ return 0;
+}
+
+static int
+ovs_enable_base(struct ovs_base *ob)
+{
+ int ret;
+
+ if (!ob->present)
+ return 0;
+
+ ret = device_claim(&ob->dev);
+ if (ret < 0)
+ goto error;
+
+ return 0;
+
+ ob->present = false;
+ return ret;
+}
+
+static void
+ovs_remove_base(struct ovs_base *ob)
+{
+ struct ovs_state *ost = ob->ost;
+
+ if (!ob->present)
+ return;
+
+ if (ost->dev.active)
+ ovs_disable_base(ob);
+
+ ob->present = false;
+ ovs_set_present(ost);
+}
+
+static void
+ovs_free_base(struct ovs_base *ob)
+{
+ ovs_remove_base(ob);
+ device_remove_user(&ob->dev);
+ free(ob);
+}
+
+static int
+ovs_disable_port(struct ovs_port *op)
+{
+ struct ovs_state *ost = op->ost;
+
+ if (!op->present)
+ return 0;
+
+ system_ovs_delport(&ost->dev, op->dev.dev);
+ device_release(&op->dev);
+
+ return 0;
+}
+
+static int
+ovs_enable_port(struct ovs_port *op)
+{
+ struct ovs_state *ost = op->ost;
+ int ret;
+
+ if (!op->present)
+ return 0;
+
+ ret = device_claim(&op->dev);
+ if (ret < 0)
+ goto error;
+
+ ret = system_ovs_addport(&ost->dev, op->dev.dev);
+ if (ret < 0) {
+ D(DEVICE, "Bridge device %s could not be added\n", op->dev.dev->ifname);
+ goto error;
+ }
+
+ return 0;
+
+ op->present = false;
+ ost->n_present--;
+ return ret;
+}
+
+static void
+ovs_remove_port(struct ovs_port *op)
+{
+ struct ovs_state *ost = op->ost;
+
+ if (!op->present)
+ return;
+
+ if (ost->dev.active)
+ ovs_disable_port(op);
+
+ op->present = false;
+ op->ost->n_present--;
+
+ ovs_set_present(ost);
+}
+
+static void
+ovs_free_port(struct ovs_port *op)
+{
+ struct device *dev = op->dev.dev;
+
+ ovs_remove_port(op);
+ device_remove_user(&op->dev);
+
+ /*
+ * When reloading the config and moving a device from one bridge to
+ * another, the other bridge 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(op);
+}
+
+static void
+ovs_port_cb(struct device_user *dev, enum device_event ev)
+{
+ struct ovs_port *op = container_of(dev, struct ovs_port, dev);
+ struct ovs_state *ost = op->ost;
+
+ switch (ev) {
+ assert(!op->present);
+
+ op->present = true;
+ ost->n_present++;
+
+ if (ost->dev.active)
+ ovs_enable_port(op);
+ else if (ost->n_present == 1)
+ ovs_set_present(ost);
+
+ break;
+ if (dev->hotplug) {
+ vlist_delete(&ost->ports, &op->node);
+ return;
+ }
+
+ if (op->present)
+ ovs_remove_port(op);
+
+ break;
+ return;
+ }
+}
+
+static void
+ovs_base_cb(struct device_user *dev, enum device_event ev)
+{
+ struct ovs_base *ob = container_of(dev, struct ovs_base, dev);
+ struct ovs_state *ost = ob->ost;
+
+
+ switch (ev) {
+ ob->present = true;
+ ovs_enable_base(ob);
+ ovs_set_present(ost);
+
+ if (ob->present)
+ ovs_remove_base(ob);
+
+ break;
+ return;
+ }
+}
+
+static int
+ovs_set_down(struct ovs_state *ost)
+{
+ struct ovs_port *op;
+
+ ost->set_state(&ost->dev, false);
+
+ vlist_for_each_element(&ost->ports, op, node)
+ ovs_disable_port(op);
+
+ if (ost->base)
+ ovs_disable_base(ost->base);
+
+ system_ovs_delbr(&ost->dev);
+
+ return 0;
+}
+
+static int
+ovs_set_up(struct ovs_state *ost)
+{
+ struct ovs_port *op;
+ int ret;
+
+ if (!ost->force_active && !ost->n_present)
+ return -ENOENT;
+
+ if (ost->base)
+ ovs_enable_base(ost->base);
+
+ ret = system_ovs_addbr(&ost->dev, &ost->config);
+ if (ret < 0)
+ goto out;
+
+ vlist_for_each_element(&ost->ports, op, node)
+ ovs_enable_port(op);
+
+ if (!ost->force_active && !ost->n_present) {
+ /* initialization of all port interfaces failed */
+ system_ovs_delbr(&ost->dev);
+ ovs_set_present(ost);
+ return -ENOENT;
+ }
+
+ ret = ost->set_state(&ost->dev, true);
+ if (ret < 0)
+ ovs_set_down(ost);
+
+ return ret;
+}
+
+static int
+ovs_set_state(struct device *dev, bool up)
+{
+ struct ovs_state *ost;
+
+ ost = container_of(dev, struct ovs_state, dev);
+
+ if (up)
+ return ovs_set_up(ost);
+ else
+ return ovs_set_down(ost);
+}
+
+static struct ovs_port *
+ovs_create_port(struct ovs_state *ost, struct device *dev, bool hotplug)
+{
+ struct ovs_port *op;
+
+ op = calloc(1, sizeof(*op) + strlen(dev->ifname) + 1);
+ op->ost = ost;
+ op->dev.cb = ovs_port_cb;
+ op->dev.hotplug = hotplug;
+ strcpy(op->name, dev->ifname);
+ op->dev.dev = dev;
+ vlist_add(&ost->ports, &op->node, op->name);
+ if (hotplug)
+ op->node.version = -1;
+
+ return op;
+}
+
+static void
+ovs_port_update(struct vlist_tree *tree, struct vlist_node *node_new,
+ struct vlist_node *node_old)
+{
+ struct ovs_port *op;
+ struct device *dev;
+
+ if (node_new) {
+ op = container_of(node_new, struct ovs_port, node);
+
+ if (node_old) {
+ free(op);
+ return;
+ }
+
+ dev = op->dev.dev;
+ op->dev.dev = NULL;
+ device_add_user(&op->dev, dev);
+ }
+
+
+ if (node_old) {
+ op = container_of(node_old, struct ovs_port, node);
+ ovs_free_port(op);
+ }
+}
+
+
+static void
+ovs_add_port(struct ovs_state *ost, const char *name)
+{
+ struct device *dev;
+
+ dev = device_get(name, true);
+ if (!dev)
+ return;
+
+ ovs_create_port(ost, dev, false);
+}
+
+static struct ovs_base *
+ovs_create_base(struct ovs_state *ost, struct device *dev, bool hotplug)
+{
+ struct ovs_base *ob;
+
+ ob = calloc(1, sizeof(*ob));
+ if (!ob)
+ return NULL;
+
+ ob->ost = ost;
+ ob->dev.cb = ovs_base_cb;
+
+ device_add_user(&ob->dev, dev);
+
+ return ob;
+}
+static void
+ovs_add_base(struct ovs_state *ost, const char *name)
+{
+ struct device *dev;
+
+ dev = device_get(name, true);
+ if (!dev)
+ return;
+
+ ovs_create_base(ost, dev, false);
+}
+
+static int
+ovs_hotplug_add(struct device *dev, struct device *port)
+{
+ struct ovs_state *ost = container_of(dev, struct ovs_state, dev);
+
+ ovs_create_port(ost, port, true);
+
+ return 0;
+}
+
+static int
+ovs_hotplug_del(struct device *dev, struct device *port)
+{
+ struct ovs_state *ost = container_of(dev, struct ovs_state, dev);
+ struct ovs_port *op;
+
+ op = vlist_find(&ost->ports, port->ifname, op, node);
+ if (!op)
+ return UBUS_STATUS_NOT_FOUND;
+
+ vlist_delete(&ost->ports, &op->node);
+ return 0;
+}
+
+static int
+ovs_hotplug_prepare(struct device *dev)
+{
+ struct ovs_state *ost;
+
+ ost = container_of(dev, struct ovs_state, dev);
+ ost->force_active = true;
+ ovs_set_present(ost);
+
+ return 0;
+}
+
+static const struct device_hotplug_ops ovs_ops = {
+ .prepare = ovs_hotplug_prepare,
+ .add = ovs_hotplug_add,
+ .del = ovs_hotplug_del
+};
+
+static void
+ovs_free(struct device *dev)
+{
+ struct ovs_state *ost;
+ ost = container_of(dev, struct ovs_state, dev);
+
+ if (ost->base)
+ ovs_free_base(ost->base);
+ vlist_flush_all(&ost->ports);
+ free(ost);
+}
+
+static void
+ovs_dump_info(struct device *dev, struct blob_buf *b)
+{
+ struct ovs_state *ost;
+ struct ovs_port *op;
+ void *list;
+
+ ost = container_of(dev, struct ovs_state, dev);
+
+ system_if_dump_info(dev, b);
+ list = blobmsg_open_array(b, "ovs-ports");
+
+ vlist_for_each_element(&ost->ports, op, node)
+ blobmsg_add_string(b, NULL, op->dev.dev->ifname);
+
+ blobmsg_close_array(b, list);
+ if (ost->base)
+ blobmsg_add_string(b, "ovs_base", ost->base->dev.dev->ifname);
+}
+
+static void
+ovs_set_present(struct ovs_state *ost)
+{
+ bool present = false;
+ if (ost->base) {
+ /* Base device has to be available first */
+ if (!ost->base->present)
+ goto out;
+ }
+
+ if (ost->config.empty) {
+ present = true;
+ goto out;
+ }
+
+ ost->force_active = false;
+ if (ost->n_present == 0)
+ goto out;
+
+ present = true;
+ device_set_present(&ost->dev, present);
+}
+
+static void
+ovs_config_init(struct device *dev)
+{
+ struct ovs_state *ost;
+ struct blob_attr *cur;
+ int rem;
+
+ ost = container_of(dev, struct ovs_state, dev);
+
+ if (ost->config.empty) {
+ ost->force_active = true;
+ ovs_set_present(ost);
+ }
+
+ if (ost->config.base) {
+ /* Pseudo bridge, requires base */
+ ovs_add_base(ost, ost->config.base);
+ }
+
+ vlist_update(&ost->ports);
+ if (ost->ifnames) {
+ blobmsg_for_each_attr(cur, ost->ifnames, rem) {
+ ovs_add_port(ost, blobmsg_data(cur));
+ }
+ }
+ vlist_flush(&ost->ports);
+}
+
+static void
+ovs_apply_settings(struct ovs_state *ost, struct blob_attr **tb)
+{
+ struct ovs_config *cfg = &ost->config;
+
+ /* defaults */
+ cfg->tag = 0;
+ cfg->base = NULL;
+ cfg->empty = false;
+
+ if (tb[OVS_ATTR_TAG] && tb[OVS_ATTR_BASE] ) {
+ cfg->tag = blobmsg_get_u32(tb[OVS_ATTR_TAG]);
+ cfg->base = blobmsg_get_string(tb[OVS_ATTR_BASE]);
+ }
+
+ if (tb[OVS_ATTR_EMPTY])
+ cfg->empty = blobmsg_get_bool(tb[OVS_ATTR_EMPTY]);
+}
+
+enum dev_change_type
+ovs_reload(struct device *dev, struct blob_attr *attr)
+{
+ struct blob_attr *tb_dev[__DEV_ATTR_MAX];
+ struct blob_attr *tb_br[__OVS_ATTR_MAX];
+ enum dev_change_type ret = DEV_CONFIG_APPLIED;
+ unsigned long diff;
+ struct ovs_state *ost;
+
+ BUILD_BUG_ON(sizeof(diff) < __OVS_ATTR_MAX / 8);
+ BUILD_BUG_ON(sizeof(diff) < __DEV_ATTR_MAX / 8);
+
+ ost = container_of(dev, struct ovs_state, dev);
+
+ blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, tb_dev,
+ blob_data(attr), blob_len(attr));
+ blobmsg_parse(ovs_attrs, __OVS_ATTR_MAX, tb_br,
+ blob_data(attr), blob_len(attr));
+
+ ost->ifnames = tb_br[OVS_ATTR_IFNAME];
+ device_init_settings(dev, tb_dev);
+ ovs_apply_settings(ost, tb_br);
+
+ if (ost->config_data) {
+ struct blob_attr *otb_dev[__DEV_ATTR_MAX];
+ struct blob_attr *otb_br[__OVS_ATTR_MAX];
+
+ blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, otb_dev,
+ blob_data(ost->config_data), blob_len(ost->config_data));
+
+ diff = 0;
+ uci_blob_diff(tb_dev, otb_dev, &device_attr_list, &diff);
+ if (diff & ~(1 << DEV_ATTR_IFNAME))
+ ret = DEV_CONFIG_RESTART;
+
+ blobmsg_parse(ovs_attrs, __OVS_ATTR_MAX, otb_br,
+ blob_data(ost->config_data), blob_len(ost->config_data));
+
+ diff = 0;
+ uci_blob_diff(tb_br, otb_br, &ovs_attr_list, &diff);
+ if (diff & ~(1 << OVS_ATTR_IFNAME))
+ ret = DEV_CONFIG_RESTART;
+
+ ovs_config_init(dev);
+ }
+
+ ost->config_data = attr;
+ return ret;
+}
+
+static struct device *
+ovs_create(const char *name, struct blob_attr *attr)
+{
+ struct ovs_state *ost;
+ struct device *dev = NULL;
+
+ ost = calloc(1, sizeof(*ost));
+ if (!ost)
+ return NULL;
+
+ dev = &ost->dev;
+ device_init(dev, &ovs_device_type, name);
+ dev->config_pending = true;
+
+ ost->set_state = dev->set_state;
+ dev->set_state = ovs_set_state;
+
+ dev->hotplug_ops = &ovs_ops;
+
+ vlist_init(&ost->ports, avl_strcmp, ovs_port_update);
+ ost->ports.keep_old = true;
+ ovs_reload(dev, attr);
+
+ return dev;
+}
diff --git a/system-linux.c b/system-linux.c
index d01d7e3..f30fba9 100644
--- a/system-linux.c
+++ b/system-linux.c
@@ -54,6 +54,7 @@
#include "netifd.h"
#include "device.h"
#include "system.h"
+#include "system-ovs.h"
struct event_socket {
struct uloop_fd uloop;
@@ -166,7 +167,7 @@ static void system_set_dev_sysctl(const char *path, const char *device, const ch
system_set_sysctl(dev_buf, val);
}
-static void system_set_disable_ipv6(struct device *dev, const char *val)
+void system_set_disable_ipv6(struct device *dev, const char *val)
{
system_set_dev_sysctl("/proc/sys/net/ipv6/conf/%s/disable_ipv6", dev->ifname, val);
}
@@ -543,6 +544,9 @@ void system_if_clear_state(struct device *dev)
system_if_flags(dev->ifname, 0, IFF_UP);
+ /* Clear ovs state first to not conflict with the ovs bridge compat mode */
+ system_ovs_if_clear_state(dev);
+
if (system_is_bridge(dev->ifname, buf, sizeof(buf))) {
D(SYSTEM, "Delete existing bridge named '%s'\n", dev->ifname);
system_bridge_delbr(dev);
diff --git a/system-ovs.c b/system-ovs.c
new file mode 100644
index 0000000..81169fd
--- /dev/null
+++ b/system-ovs.c
@@ -0,0 +1,115 @@
+/*
+ * netifd - network interface daemon
+ *
+ * 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 <sys/wait.h>
+#include "netifd.h"
+#include "system.h"
+#include "system-ovs.h"
+
+#define run_prog(p, ...) ({ \
+ int rc = -1, status; \
+ pid_t pid = fork(); \
+ if (!pid) \
+ exit(execl(p, p, ##__VA_ARGS__, NULL)); \
+ if (pid < 0) {\
+ rc = -1;\
+ } else {\
+ while ((rc = waitpid(pid, &status, 0)) == -1 && errno == EINTR); \
+ rc = (rc == pid && WIFEXITED(status)) ? WEXITSTATUS(status) : -1; \
+ }\
+ rc;\
+})
+
+static char *system_get_ovs(const char *name)
+{
+ FILE *f;
+ char cmd[64];
+ static char dev[64];
+
+ sprintf(cmd, "/usr/bin/ovs-vsctl iface-to-br %s", name);
+ f = popen(cmd, "r");
+ if (!f)
+ return NULL;
+ fgets(dev, sizeof(dev), f);
+ pclose(f);
+ return dev;
+}
+
+static bool system_ovs_isbr(const char *name)
+{
+ if (run_prog("/usr/bin/ovs-vsctl", "br-exists", name) == 0)
+ return true;
+ return false;
+}
+
+void system_ovs_if_clear_state(struct device *dev)
+{
+ char *ovs;
+
+ if (system_ovs_isbr(dev->ifname)) {
+ system_ovs_delbr(dev);
+ return;
+ }
+
+ ovs = system_get_ovs(dev->ifname);
+ if (ovs)
+ run_prog("/usr/bin/ovs-vsctl", "del-port", dev->ifname);
+}
+
+
+int system_ovs_delbr(struct device *ovs)
+{
+ if (run_prog("/usr/bin/ovs-vsctl", "del-br", ovs->ifname))
+ return -1;
+ return 0;
+}
+
+int system_ovs_addbr(struct device *ovs, struct ovs_config *cfg)
+{
+ char buf[16];
+ if (cfg->tag && cfg->base) {
+ /* Pseudo bridge on top of an openvswitch */
+ snprintf(buf, sizeof(buf), "%u", cfg->tag);
+ if (run_prog("/usr/bin/ovs-vsctl", "add-br", ovs->ifname, cfg->base, buf))
+ return -1;
+ return 0;
+ }
+ if (run_prog("/usr/bin/ovs-vsctl", "add-br", ovs->ifname))
+ return -1;
+ return 0;
+}
+
+int system_ovs_addport(struct device *ovs, struct device *dev)
+{
+ char *old_ovs;
+ system_set_disable_ipv6(dev, "1");
+
+ old_ovs = system_get_ovs(dev->ifname);
+ if (old_ovs && !strcmp(old_ovs, ovs->ifname))
+ return 0;
+
+ if (run_prog("/usr/bin/ovs-vsctl", "add-port", ovs->ifname, dev->ifname))
+ return -1;
+
+ return 0;
+}
+
+int system_ovs_delport(struct device *ovs, struct device *dev)
+{
+ system_set_disable_ipv6(dev, "0");
+ if (run_prog("/usr/bin/ovs-vsctl", "del-port", ovs->ifname, dev->ifname))
+ return -1;
+ return 0;
+}
diff --git a/system-ovs.h b/system-ovs.h
new file mode 100644
index 0000000..ad018d4
--- /dev/null
+++ b/system-ovs.h
@@ -0,0 +1,31 @@
+/*
+ * netifd - network interface daemon
+ *
+ * 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.
+ */
+#ifndef __NETIFD_SYSTEM_OVS_H
+#define __NETIFD_SYSTEM_OVS_H
+
+#include "system.h"
+
+struct ovs_config {
+ bool empty;
+ int tag;
+ char *base;
+};
+
+void system_ovs_if_clear_state(struct device *dev);
+int system_ovs_delbr(struct device *ovs);
+int system_ovs_addbr(struct device *ovs, struct ovs_config *cfg);
+int system_ovs_addport(struct device *ovs, struct device *dev);
+int system_ovs_delport(struct device *ovs, struct device *dev);
+
+#endif
diff --git a/system.h b/system.h
index ad74156..4f27e3a 100644
--- a/system.h
+++ b/system.h
@@ -129,4 +129,6 @@ void system_fd_set_cloexec(int fd);
int system_update_ipv6_mtu(struct device *device, int mtu);
+void system_set_disable_ipv6(struct device *dev, const char *val);
+
#endif
--
--------------------------------------------------------
Roberto Riggio, Ph.D.
CREATE-NET
Network & Security Solutions for Pervasive Computing Systems (iNSPIRE)
Senior Researcher
Via alla Cascata 56/D - 38123 Povo Trento (Italy)
e-mail: ***@create-net.org
Tel: (+39) 0461 408400 - interno/extension 708
Fax: (+39) 0461 421157
www.create-net.org/~rriggio
--------------------------------------------------------

The information transmitted is intended only for the person or entity to
which it is addressed and may contain confidential and/or privileged
material. Any review, retransmission, dissemination or other use of, or
taking of any action in reliance upon, this information by persons or
entities other than the intended recipient is prohibited according to
the Italian Law 196/2003 of the Legislature. If you received this in
error, please contact the sender and delete the material from any
computer.

Le informazioni contenute in questo messaggio di posta elettronica e nei
file allegati sono da considerarsi strettamente riservate. Il loro
utilizzo e' consentito esclusivamente al destinatario del messaggio, per
le finalita' indicate nel messaggio stesso. Qualora riceveste questo
messaggio senza esserne il destinatario, Vi preghiamo cortesemente di
darcene notizia via e-mail e di procedere alla cancellazione del
messaggio stesso dal Vostro sistema. Trattenere il messaggio stesso,
divulgarlo anche in parte, distribuirlo ad altri soggetti, copiarlo,
od utilizzarlo per finalita' diverse, costituisce comportamento
contrario ai principi dettati dal D. Lgs. 196/2003.
Helmut Schaa
2014-02-03 15:57:57 UTC
Permalink
Hi Roberto,

On Sun, Feb 2, 2014 at 9:01 PM, Roberto Riggio
Post by Roberto Riggio
Hi,
I'm testing your patch with this configuration in /etc/config/network
config device
option name 'ovs'
option type 'ovs'
list ifname 'eth0'
config interface
option ifname 'ovs'
option proto 'dhcp'
What if you write the config file after boot and run /etc/init.d/network reload?
Does it also end up in a loop?
What version of ovs do you use?

Helmut
Ronaldo Zacarias Afonso
2014-02-03 17:03:48 UTC
Permalink
Hi,

I'm trying to build a fresh OpenWRT and didn't find that Open vSwitch
patch you mentioned.

Can you please point me where it is?

I have a Buffalo and a TP-Link router here and I can help doing some
tests.

Thanks ...
Post by Helmut Schaa
Hi Roberto,
On Sun, Feb 2, 2014 at 9:01 PM, Roberto Riggio
Post by Roberto Riggio
Hi,
I'm testing your patch with this configuration in /etc/config/network
config device
option name 'ovs'
option type 'ovs'
list ifname 'eth0'
config interface
option ifname 'ovs'
option proto 'dhcp'
What if you write the config file after boot and run /etc/init.d/network reload?
Does it also end up in a loop?
What version of ovs do you use?
Helmut
_______________________________________________
openwrt-devel mailing list
https://lists.openwrt.org/cgi-bin/mailman/listinfo/openwrt-devel
--
Ronaldo Afonso
Sistemas Embarcados
Oi: 55 (11) 95252-0484
Fixo: 55 (11) 3065-9949
www.oiwifi.com.br
Esta mensagem, incluindo seus anexos, pode conter informacoes privilegiadas e/ou de carater confidencial, nao podendo ser retransmitida sem autorizacao do remetente. Se voce nao e o destinatario ou pessoa autorizada a recebe-la, informamos que o seu uso, divulgacao, copia ou arquivamento sao proibidos. Portanto, se vocĂȘ recebeu esta mensagem por engano, por favor, nos informe respondendo imediatamente a este e-mail e em seguida apague-a.
Helmut Schaa
2014-02-04 08:11:36 UTC
Permalink
On Mon, Feb 3, 2014 at 6:03 PM, Ronaldo Zacarias Afonso
Post by Ronaldo Zacarias Afonso
I'm trying to build a fresh OpenWRT and didn't find that Open vSwitch
patch you mentioned.
Can you please point me where it is?
It's just an RFC for now but you can find a current version here:
https://github.com/hschaa/netifd/commit/faec8fe8ec23eb993189a22250c99652c067bf22

Helmut
Roberto Riggio
2014-02-03 17:53:33 UTC
Permalink
Post by Helmut Schaa
What if you write the config file after boot and run /etc/init.d/network reload?
Does it also end up in a loop?
Just tried, no, it does not loop. However I noticed that by running

/etc/init.d.network restart

I get:

Command failed: not found

and in the logread there is an error from ovs-vsctl about a missing interface
eth0.

The interface is there of course (down) if I run ifconfig eth0
Post by Helmut Schaa
What version of ovs do you use?
OVS 2.0 adapted from:

https://github.com/schuza/openvswitch
Post by Helmut Schaa
Helmut
--
--------------------------------------------------------
Roberto Riggio, Ph.D.
CREATE-NET
Network & Security Solutions for Pervasive Computing Systems (iNSPIRE)
Senior Researcher
Via alla Cascata 56/D - 38123 Povo Trento (Italy)
e-mail: ***@create-net.org
Tel: (+39) 0461 408400 - interno/extension 708
Fax: (+39) 0461 421157
www.create-net.org/~rriggio
--------------------------------------------------------

The information transmitted is intended only for the person or entity to
which it is addressed and may contain confidential and/or privileged
material. Any review, retransmission, dissemination or other use of, or
taking of any action in reliance upon, this information by persons or
entities other than the intended recipient is prohibited according to
the Italian Law 196/2003 of the Legislature. If you received this in
error, please contact the sender and delete the material from any
computer.

Le informazioni contenute in questo messaggio di posta elettronica e nei
file allegati sono da considerarsi strettamente riservate. Il loro
utilizzo e' consentito esclusivamente al destinatario del messaggio, per
le finalita' indicate nel messaggio stesso. Qualora riceveste questo
messaggio senza esserne il destinatario, Vi preghiamo cortesemente di
darcene notizia via e-mail e di procedere alla cancellazione del
messaggio stesso dal Vostro sistema. Trattenere il messaggio stesso,
divulgarlo anche in parte, distribuirlo ad altri soggetti, copiarlo,
od utilizzarlo per finalita' diverse, costituisce comportamento
contrario ai principi dettati dal D. Lgs. 196/2003.
Helmut Schaa
2014-02-04 08:09:44 UTC
Permalink
On Mon, Feb 3, 2014 at 6:53 PM, Roberto Riggio
Post by Roberto Riggio
Post by Helmut Schaa
What if you write the config file after boot and run /etc/init.d/network reload?
Does it also end up in a loop?
Just tried, no, it does not loop.
Ok, this helps a lot I guess.
Maybe OVS is not up and running yet during boot or not yet initialized?
Post by Roberto Riggio
However I noticed that by running
/etc/init.d.network restart
Command failed: not found
and in the logread there is an error from ovs-vsctl about a missing interface
eth0.
Yeah, this is a bit unfortunate but should not cause any harm ...
Post by Roberto Riggio
The interface is there of course (down) if I run ifconfig eth0
Post by Helmut Schaa
What version of ovs do you use?
https://github.com/schuza/openvswitch
Should be fine I guess, I pushed a modified version of the OVS package
to my github repo at [1]. Just in case you wanna look at it.
This version is much smaller due to dynamic linking (which is fixed in
openvswitch trunk but not in the 2.0 branch) and uses procd for
service setup.

Thanks for testing,
Helmut

[1] https://github.com/hschaa/openvswitch-feed
Roberto Riggio
2014-02-04 08:16:58 UTC
Permalink
Post by Helmut Schaa
Post by Roberto Riggio
Post by Helmut Schaa
What if you write the config file after boot and run /etc/init.d/network reload?
Does it also end up in a loop?
Just tried, no, it does not loop.
Ok, this helps a lot I guess.
Maybe OVS is not up and running yet during boot or not yet initialized?
Forgot to point out that it does not loop but it also does not work as
expected. basically the bridge is not created.

R.
Helmut Schaa
2014-02-04 09:31:42 UTC
Permalink
On Tue, Feb 4, 2014 at 9:16 AM, Roberto Riggio
Post by Roberto Riggio
Post by Helmut Schaa
Post by Roberto Riggio
Post by Helmut Schaa
What if you write the config file after boot and run /etc/init.d/network reload?
Does it also end up in a loop?
Just tried, no, it does not loop.
Ok, this helps a lot I guess.
Maybe OVS is not up and running yet during boot or not yet initialized?
Forgot to point out that it does not loop but it also does not work as
expected. basically the bridge is not created.
But it works if you create it manually with ovs-vsctl?
Roberto Riggio
2014-02-04 09:39:39 UTC
Permalink
Post by Helmut Schaa
But it works if you create it manually with ovs-vsctl?
Yes the manual workflow works fine: create bridge, add interfaces
set cotnroller.

R.
--
--------------------------------------------------------
Roberto Riggio, Ph.D.
CREATE-NET
Network & Security Solutions for Pervasive Computing Systems (iNSPIRE)
Senior Researcher
Via alla Cascata 56/D - 38123 Povo Trento (Italy)
e-mail: ***@create-net.org
Tel: (+39) 0461 408400 - interno/extension 708
Fax: (+39) 0461 421157
www.create-net.org/~rriggio
--------------------------------------------------------

The information transmitted is intended only for the person or entity to
which it is addressed and may contain confidential and/or privileged
material. Any review, retransmission, dissemination or other use of, or
taking of any action in reliance upon, this information by persons or
entities other than the intended recipient is prohibited according to
the Italian Law 196/2003 of the Legislature. If you received this in
error, please contact the sender and delete the material from any
computer.

Le informazioni contenute in questo messaggio di posta elettronica e nei
file allegati sono da considerarsi strettamente riservate. Il loro
utilizzo e' consentito esclusivamente al destinatario del messaggio, per
le finalita' indicate nel messaggio stesso. Qualora riceveste questo
messaggio senza esserne il destinatario, Vi preghiamo cortesemente di
darcene notizia via e-mail e di procedere alla cancellazione del
messaggio stesso dal Vostro sistema. Trattenere il messaggio stesso,
divulgarlo anche in parte, distribuirlo ad altri soggetti, copiarlo,
od utilizzarlo per finalita' diverse, costituisce comportamento
contrario ai principi dettati dal D. Lgs. 196/2003.
Outback Dingo
2014-02-08 01:36:49 UTC
Permalink
ok, excuse the ignorance, i see the patch, i see the git branch, hows this
branch get integrated to openwrt ?
Post by Helmut Schaa
On Tue, Feb 4, 2014 at 9:16 AM, Roberto Riggio
Post by Roberto Riggio
Post by Helmut Schaa
Post by Roberto Riggio
Post by Helmut Schaa
What if you write the config file after boot and run
/etc/init.d/network reload?
Post by Roberto Riggio
Post by Helmut Schaa
Post by Roberto Riggio
Post by Helmut Schaa
Does it also end up in a loop?
Just tried, no, it does not loop.
Ok, this helps a lot I guess.
Maybe OVS is not up and running yet during boot or not yet initialized?
Forgot to point out that it does not loop but it also does not work as
expected. basically the bridge is not created.
But it works if you create it manually with ovs-vsctl?
_______________________________________________
openwrt-devel mailing list
https://lists.openwrt.org/cgi-bin/mailman/listinfo/openwrt-devel
Roberto Riggio
2014-02-07 15:45:55 UTC
Permalink
Post by Helmut Schaa
I'd appreciate any testing beside my own :D
Hi, I did some testing on some alix boards. The config
I'm using is the following:

config device
option name br-ovs
option type ovs
list ifname eth0

config interface wan
option ifname br-ovs
option proto dhcp

Everything seems to work fine, the bridge is created
and the interface is added to it.

The only problem I;ve found is that if I change the device
name from br-ovs to (for example) ovs at the next reboot
the change is not applied.

Also, the device section could benefit from an ovs-controller
option to specify the controller for the switch.

Tested with OpenVSwitch 2.0.

R.
Helmut Schaa
2014-02-10 10:49:51 UTC
Permalink
On Fri, Feb 7, 2014 at 4:45 PM, Roberto Riggio
Post by Roberto Riggio
Post by Helmut Schaa
I'd appreciate any testing beside my own :D
Hi, I did some testing on some alix boards. The config
config device
option name br-ovs
option type ovs
list ifname eth0
config interface wan
option ifname br-ovs
option proto dhcp
Everything seems to work fine, the bridge is created
and the interface is added to it.
The only problem I;ve found is that if I change the device
name from br-ovs to (for example) ovs at the next reboot
the change is not applied.
Ok, not sure why this is ....

What did you change to get this working now?

Helmut
Roberto Riggio
2014-02-10 13:32:21 UTC
Permalink
This post might be inappropriate. Click to display it.
Helmut Schaa
2014-02-10 13:57:02 UTC
Permalink
On Mon, Feb 10, 2014 at 2:32 PM, Roberto Riggio
Post by Roberto Riggio
Post by Helmut Schaa
What did you change to get this working now?
I started from a clean build. In the previous version I played
a little bit with verious version of OVS and some hack to the
netidf script. Everything worked as expected on new image built
from scratch.
BTW the netifd was built from the upstream repo with your
patch (generated from your git branch).
Is the possibility to set the controller an option you are
considering?
This is untested but feel free to give it a try:

diff --git a/ovs.c b/ovs.c
index 29db26c..21056cb 100644
--- a/ovs.c
+++ b/ovs.c
@@ -35,6 +35,7 @@ static const struct blobmsg_policy
ovs_attrs[__OVS_ATTR_MAX] = {
[OVS_ATTR_BASE] = { "ovs_base", BLOBMSG_TYPE_STRING },
[OVS_ATTR_TAG] = { "ovs_tag", BLOBMSG_TYPE_INT32 },
[OVS_ATTR_EMPTY] = { "ovs_empty", BLOBMSG_TYPE_BOOL },
+ [OVS_ATTR_CONTROLLER] = { "ovs_controller", BLOBMSG_TYPE_STRING},
};

static const struct uci_blob_param_info ovs_attr_info[__OVS_ATTR_MAX] = {
@@ -581,6 +582,8 @@ ovs_apply_settings(struct ovs_state *ost, struct
blob_attr **tb)
if (tb[OVS_ATTR_TAG] && tb[OVS_ATTR_BASE] ) {
cfg->tag = blobmsg_get_u32(tb[OVS_ATTR_TAG]);
cfg->base = blobmsg_get_string(tb[OVS_ATTR_BASE]);
+ } else if (tb[OVS_ATTR_CONTROLLER]) {
+ cfg->controller = blobmsg_get_string(tb[OVS_ATTR_CONTROLLER]);
}

if (tb[OVS_ATTR_EMPTY])
diff --git a/system-ovs.c b/system-ovs.c
index 92f0f43..25a42b5 100644
--- a/system-ovs.c
+++ b/system-ovs.c
@@ -92,6 +92,12 @@ int system_ovs_addbr(struct device *ovs, struct
ovs_config *cfg)
return -1;
return 0;
}
+ if (cfg->controller) {
+ if (run_prog("/usr/bin/ovs-vsctl", "add-br", ovs->ifname,
"--", "set-controller", ovs->ifname, cfg->controller))
+ return -1;
+ return 0;
+ }
+
if (run_prog("/usr/bin/ovs-vsctl", "add-br", ovs->ifname))
return -1;
return 0;
diff --git a/system-ovs.h b/system-ovs.h
index ad018d4..f39ac44 100644
--- a/system-ovs.h
+++ b/system-ovs.h
@@ -20,6 +20,7 @@ struct ovs_config {
bool empty;
int tag;
char *base;
+ char *controller;
};

void system_ovs_if_clear_state(struct device *dev);
Ronaldo Zacarias Afonso
2014-02-10 13:57:28 UTC
Permalink
Hi Roberto,

I'm very interested in helping you with Open vSwitch.

Please, just tell me how I can git clone, or svn check out, that
branch you are working on.

I can do tests with some Buffalo and TP-Link routers.

Thanks ...
Post by Roberto Riggio
Post by Helmut Schaa
What did you change to get this working now?
I started from a clean build. In the previous version I played
a little bit with verious version of OVS and some hack to the
netidf script. Everything worked as expected on new image built
from scratch.
BTW the netifd was built from the upstream repo with your
patch (generated from your git branch).
Is the possibility to set the controller an option you are
considering?
R.
_______________________________________________
openwrt-devel mailing list
https://lists.openwrt.org/cgi-bin/mailman/listinfo/openwrt-devel
--
Ronaldo Afonso
Sistemas Embarcados
Oi: 55 (11) 95252-0484
Fixo: 55 (11) 3065-9949
www.oiwifi.com.br
Esta mensagem, incluindo seus anexos, pode conter informacoes privilegiadas e/ou de carater confidencial, nao podendo ser retransmitida sem autorizacao do remetente. Se voce nao e o destinatario ou pessoa autorizada a recebe-la, informamos que o seu uso, divulgacao, copia ou arquivamento sao proibidos. Portanto, se vocĂȘ recebeu esta mensagem por engano, por favor, nos informe respondendo imediatamente a este e-mail e em seguida apague-a.
Loading...