diff options
author | Lubomir Rintel <lkundrak@v3.sk> | 2017-08-01 18:27:22 +0200 |
---|---|---|
committer | Lubomir Rintel <lkundrak@v3.sk> | 2017-08-24 11:28:17 +0200 |
commit | 33b3d6b71703b20405cbff6de076dcd5aa8a7bd8 (patch) | |
tree | b9cdad67690c30bf51e0e8caf976d0624427430b | |
parent | 75958ab0d1d93d74e47c45242ef9e7279ee86c6c (diff) | |
download | NetworkManager-33b3d6b71703b20405cbff6de076dcd5aa8a7bd8.tar.gz |
devices: add support for openvswitch devices
XXX needs more work
-rw-r--r-- | Makefile.am | 51 | ||||
-rw-r--r-- | clients/common/nm-meta-setting-desc.c | 7 | ||||
-rw-r--r-- | configure.ac | 14 | ||||
-rw-r--r-- | contrib/fedora/rpm/NetworkManager.spec | 20 | ||||
-rw-r--r-- | data/NetworkManager-openvswitch.conf | 2 | ||||
-rw-r--r-- | libnm-core/nm-dbus-interface.h | 121 | ||||
-rw-r--r-- | libnm-core/nm-setting.c | 2 | ||||
-rw-r--r-- | src/devices/nm-device.c | 2 | ||||
-rw-r--r-- | src/devices/openvswitch/nm-device-openvswitch.c | 273 | ||||
-rw-r--r-- | src/devices/openvswitch/nm-device-openvswitch.h | 35 | ||||
-rw-r--r-- | src/devices/openvswitch/nm-openvswitch-factory.c | 1005 | ||||
-rw-r--r-- | src/devices/openvswitch/nm-openvswitch-factory.h | 53 |
12 files changed, 1525 insertions, 60 deletions
diff --git a/Makefile.am b/Makefile.am index 2f399b88b8..27ef035772 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2749,6 +2749,57 @@ check_local += check-local-devices-team endif ############################################################################### +# src/devices/openvswitch +############################################################################### + +if WITH_OPENVSWITCH + +if HAVE_SYSTEMD + +systemdnmunitdir = $(systemdsystemunitdir)/NetworkManager.service.d +systemdnmunit_DATA = \ + data/NetworkManager-openvswitch.conf + +endif + +core_plugins += src/devices/openvswitch/libnm-device-plugin-openvswitch.la + +src_devices_openvswitch_libnm_device_plugin_openvswitch_la_SOURCES = \ + src/devices/openvswitch/nm-openvswitch-factory.c \ + src/devices/openvswitch/nm-device-openvswitch.c \ + src/devices/openvswitch/nm-device-openvswitch.h + +src_devices_openvswitch_libnm_device_plugin_openvswitch_la_CPPFLAGS = \ + -I$(srcdir)/src \ + -I$(builddir)/src \ + -I$(srcdir)/shared \ + -I$(builddir)/shared \ + -I$(builddir)/libnm-core \ + -I$(srcdir)/libnm-core \ + \ + -DG_LOG_DOMAIN=\""NetworkManager"\" \ + -DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON \ + -DRUNSTATEDIR=\"$(runstatedir)\" \ + \ + $(JANSSON_CFLAGS) \ + $(GLIB_CFLAGS) + +src_devices_openvswitch_libnm_device_plugin_openvswitch_la_LDFLAGS = \ + -module -avoid-version \ + -Wl,--version-script="$(srcdir)/linker-script-devices.ver" + +src_devices_openvswitch_libnm_device_plugin_openvswitch_la_LIBADD = \ + introspection/libnmdbus.la \ + $(JANSSON_LIBS) \ + $(GLIB_LIBS) + +check-local-devices-openvswitch: src/devices/openvswitch/libnm-device-plugin-openvswitch.la + $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/openvswitch/.libs/libnm-device-plugin-openvswitch.so "$(srcdir)/linker-script-devices.ver" + $(call check_so_symbols,$(builddir)/src/devices/openvswitch/.libs/libnm-device-plugin-openvswitch.so) + +endif + +############################################################################### # src/dnsmasq/tests ############################################################################### diff --git a/clients/common/nm-meta-setting-desc.c b/clients/common/nm-meta-setting-desc.c index 969873c33e..70b7ea062a 100644 --- a/clients/common/nm-meta-setting-desc.c +++ b/clients/common/nm-meta-setting-desc.c @@ -5078,7 +5078,8 @@ static const NMMetaPropertyInfo *const property_infos_CONNECTION[] = { .property_typ_data = DEFINE_PROPERTY_TYP_DATA ( .values_static = VALUES_STATIC (NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BRIDGE_SETTING_NAME, - NM_SETTING_TEAM_SETTING_NAME), + NM_SETTING_TEAM_SETTING_NAME, + NM_SETTING_OPENVSWITCH_SETTING_NAME), ), ), PROPERTY_INFO_WITH_DESC (NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES, @@ -7027,6 +7028,10 @@ nm_meta_setting_info_valid_parts_for_slave_type (const char *slave_type, const c NM_SET_OUT (out_slave_name, "team-slave"); return valid_settings_slave_team; } + if (nm_streq (slave_type, NM_SETTING_OPENVSWITCH_SETTING_NAME)) { + NM_SET_OUT (out_slave_name, "ovs-slave"); + return NM_PTRARRAY_EMPTY (const NMMetaSettingValidPartItem *); + } return NULL; } diff --git a/configure.ac b/configure.ac index 4080fd4837..91c7e1d3ee 100644 --- a/configure.ac +++ b/configure.ac @@ -836,6 +836,19 @@ else fi AM_CONDITIONAL(WITH_OFONO, test "${with_ofono}" = "yes") +# OpenVSwitch integration +AC_ARG_ENABLE(openvswitch, AS_HELP_STRING([--enable-openvswitch], [enable OpenVSwitch support])) +if test "${enable_openvswitch}" != "no"; then + enable_openvswitch='yes' + if test "$have_jansson" = "no"; then + AC_MSG_ERROR(Jansson is required for openvswitch support) + fi + AC_DEFINE(WITH_OPENVSWITCH, 1, [Define if you have OpenVSwitch support]) +else + AC_DEFINE(WITH_OPENVSWITCH, 0, [Define if you have OpenVSwitch support]) +fi +AM_CONDITIONAL(WITH_OPENVSWITCH, test "${enable_openvswitch}" = "yes") + # DHCP client support AC_ARG_WITH([dhclient], AS_HELP_STRING([--with-dhclient=yes|no|path], [Enable dhclient 4.x support])) @@ -1323,6 +1336,7 @@ echo " modemmanager-1: $with_modem_manager_1" echo " ofono: $with_ofono" echo " concheck: $enable_concheck" echo " libteamdctl: $enable_teamdctl" +echo " openvswitch: $enable_openvswitch" echo " libnm-glib: $with_libnm_glib" echo " nmcli: $build_nmcli" echo " nmtui: $build_nmtui" diff --git a/contrib/fedora/rpm/NetworkManager.spec b/contrib/fedora/rpm/NetworkManager.spec index 56638b4c66..84fcb82c83 100644 --- a/contrib/fedora/rpm/NetworkManager.spec +++ b/contrib/fedora/rpm/NetworkManager.spec @@ -48,6 +48,7 @@ %bcond_without wwan %bcond_without team %bcond_without wifi +%bcond_without openvswitch %bcond_without ppp %bcond_without nmtui %bcond_without regen_docs @@ -237,6 +238,19 @@ This package contains NetworkManager support for mobile broadband (WWAN) devices. %endif + +%if %{with openvswitch} +%package openvswitch +Summary: OpenVSwitch device plugin for NetworkManager +Group: System Environment/Base +Requires: %{name}%{?_isa} = %{epoch}:%{version}-%{release} +Requires: openvswitch + +%description openvswitch +This package contains NetworkManager support for OpenVSwitch bridges. +%endif + + %if %{with ppp} %package ppp Summary: PPP plugin for NetworkManager @@ -589,6 +603,12 @@ fi %{_libdir}/%{name}/libnm-wwan.so %endif +%if %{with openvswitch} +%files openvswitch +%{_libdir}/%{name}/libnm-device-plugin-openvswitch.so +%{systemd_dir}/NetworkManager.service.d/NetworkManager-openvswitch.conf +%endif + %if %{with ppp} %files ppp %{_libdir}/pppd/%{ppp_version}/nm-pppd-plugin.so diff --git a/data/NetworkManager-openvswitch.conf b/data/NetworkManager-openvswitch.conf new file mode 100644 index 0000000000..dde5ba5897 --- /dev/null +++ b/data/NetworkManager-openvswitch.conf @@ -0,0 +1,2 @@ +[Unit] +After=openvswitch.service diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h index b089365ccc..303c36cd6a 100644 --- a/libnm-core/nm-dbus-interface.h +++ b/libnm-core/nm-dbus-interface.h @@ -38,41 +38,42 @@ */ #define NM_DBUS_SERVICE "org.freedesktop.NetworkManager" -#define NM_DBUS_PATH "/org/freedesktop/NetworkManager" -#define NM_DBUS_INTERFACE "org.freedesktop.NetworkManager" -#define NM_DBUS_INTERFACE_DEVICE NM_DBUS_INTERFACE ".Device" -#define NM_DBUS_INTERFACE_DEVICE_WIRED NM_DBUS_INTERFACE_DEVICE ".Wired" -#define NM_DBUS_INTERFACE_DEVICE_ADSL NM_DBUS_INTERFACE_DEVICE ".Adsl" -#define NM_DBUS_INTERFACE_DEVICE_WIRELESS NM_DBUS_INTERFACE_DEVICE ".Wireless" -#define NM_DBUS_INTERFACE_DEVICE_BLUETOOTH NM_DBUS_INTERFACE_DEVICE ".Bluetooth" -#define NM_DBUS_INTERFACE_DEVICE_OLPC_MESH NM_DBUS_INTERFACE_DEVICE ".OlpcMesh" -#define NM_DBUS_PATH_ACCESS_POINT NM_DBUS_PATH "/AccessPoint" -#define NM_DBUS_INTERFACE_ACCESS_POINT NM_DBUS_INTERFACE ".AccessPoint" -#define NM_DBUS_INTERFACE_DEVICE_MODEM NM_DBUS_INTERFACE_DEVICE ".Modem" -#define NM_DBUS_INTERFACE_DEVICE_WIMAX NM_DBUS_INTERFACE_DEVICE ".WiMax" -#define NM_DBUS_INTERFACE_WIMAX_NSP NM_DBUS_INTERFACE ".WiMax.Nsp" -#define NM_DBUS_PATH_WIMAX_NSP NM_DBUS_PATH "/Nsp" -#define NM_DBUS_INTERFACE_ACTIVE_CONNECTION NM_DBUS_INTERFACE ".Connection.Active" -#define NM_DBUS_INTERFACE_IP4_CONFIG NM_DBUS_INTERFACE ".IP4Config" -#define NM_DBUS_INTERFACE_DHCP4_CONFIG NM_DBUS_INTERFACE ".DHCP4Config" -#define NM_DBUS_INTERFACE_IP6_CONFIG NM_DBUS_INTERFACE ".IP6Config" -#define NM_DBUS_INTERFACE_DHCP6_CONFIG NM_DBUS_INTERFACE ".DHCP6Config" -#define NM_DBUS_INTERFACE_DEVICE_INFINIBAND NM_DBUS_INTERFACE_DEVICE ".Infiniband" -#define NM_DBUS_INTERFACE_DEVICE_BOND NM_DBUS_INTERFACE_DEVICE ".Bond" -#define NM_DBUS_INTERFACE_DEVICE_DUMMY NM_DBUS_INTERFACE_DEVICE ".Dummy" -#define NM_DBUS_INTERFACE_DEVICE_TEAM NM_DBUS_INTERFACE_DEVICE ".Team" -#define NM_DBUS_INTERFACE_DEVICE_VLAN NM_DBUS_INTERFACE_DEVICE ".Vlan" -#define NM_DBUS_INTERFACE_DEVICE_BRIDGE NM_DBUS_INTERFACE_DEVICE ".Bridge" -#define NM_DBUS_INTERFACE_DEVICE_GENERIC NM_DBUS_INTERFACE_DEVICE ".Generic" -#define NM_DBUS_INTERFACE_DEVICE_VETH NM_DBUS_INTERFACE_DEVICE ".Veth" -#define NM_DBUS_INTERFACE_DEVICE_TUN NM_DBUS_INTERFACE_DEVICE ".Tun" -#define NM_DBUS_INTERFACE_DEVICE_MACSEC NM_DBUS_INTERFACE_DEVICE ".Macsec" -#define NM_DBUS_INTERFACE_DEVICE_MACVLAN NM_DBUS_INTERFACE_DEVICE ".Macvlan" -#define NM_DBUS_INTERFACE_DEVICE_PPP NM_DBUS_INTERFACE_DEVICE ".Ppp" -#define NM_DBUS_INTERFACE_DEVICE_VXLAN NM_DBUS_INTERFACE_DEVICE ".Vxlan" -#define NM_DBUS_INTERFACE_DEVICE_GRE NM_DBUS_INTERFACE_DEVICE ".Gre" -#define NM_DBUS_INTERFACE_DEVICE_IP_TUNNEL NM_DBUS_INTERFACE_DEVICE ".IPTunnel" -#define NM_DBUS_INTERFACE_DEVICE_STATISTICS NM_DBUS_INTERFACE_DEVICE ".Statistics" +#define NM_DBUS_PATH "/org/freedesktop/NetworkManager" +#define NM_DBUS_INTERFACE "org.freedesktop.NetworkManager" +#define NM_DBUS_INTERFACE_DEVICE NM_DBUS_INTERFACE ".Device" +#define NM_DBUS_INTERFACE_DEVICE_WIRED NM_DBUS_INTERFACE_DEVICE ".Wired" +#define NM_DBUS_INTERFACE_DEVICE_ADSL NM_DBUS_INTERFACE_DEVICE ".Adsl" +#define NM_DBUS_INTERFACE_DEVICE_WIRELESS NM_DBUS_INTERFACE_DEVICE ".Wireless" +#define NM_DBUS_INTERFACE_DEVICE_BLUETOOTH NM_DBUS_INTERFACE_DEVICE ".Bluetooth" +#define NM_DBUS_INTERFACE_DEVICE_OLPC_MESH NM_DBUS_INTERFACE_DEVICE ".OlpcMesh" +#define NM_DBUS_INTERFACE_DEVICE_OPENVSWITCH NM_DBUS_INTERFACE_DEVICE ".Openvswitch" +#define NM_DBUS_PATH_ACCESS_POINT NM_DBUS_PATH "/AccessPoint" +#define NM_DBUS_INTERFACE_ACCESS_POINT NM_DBUS_INTERFACE ".AccessPoint" +#define NM_DBUS_INTERFACE_DEVICE_MODEM NM_DBUS_INTERFACE_DEVICE ".Modem" +#define NM_DBUS_INTERFACE_DEVICE_WIMAX NM_DBUS_INTERFACE_DEVICE ".WiMax" +#define NM_DBUS_INTERFACE_WIMAX_NSP NM_DBUS_INTERFACE ".WiMax.Nsp" +#define NM_DBUS_PATH_WIMAX_NSP NM_DBUS_PATH "/Nsp" +#define NM_DBUS_INTERFACE_ACTIVE_CONNECTION NM_DBUS_INTERFACE ".Connection.Active" +#define NM_DBUS_INTERFACE_IP4_CONFIG NM_DBUS_INTERFACE ".IP4Config" +#define NM_DBUS_INTERFACE_DHCP4_CONFIG NM_DBUS_INTERFACE ".DHCP4Config" +#define NM_DBUS_INTERFACE_IP6_CONFIG NM_DBUS_INTERFACE ".IP6Config" +#define NM_DBUS_INTERFACE_DHCP6_CONFIG NM_DBUS_INTERFACE ".DHCP6Config" +#define NM_DBUS_INTERFACE_DEVICE_INFINIBAND NM_DBUS_INTERFACE_DEVICE ".Infiniband" +#define NM_DBUS_INTERFACE_DEVICE_BOND NM_DBUS_INTERFACE_DEVICE ".Bond" +#define NM_DBUS_INTERFACE_DEVICE_DUMMY NM_DBUS_INTERFACE_DEVICE ".Dummy" +#define NM_DBUS_INTERFACE_DEVICE_TEAM NM_DBUS_INTERFACE_DEVICE ".Team" +#define NM_DBUS_INTERFACE_DEVICE_VLAN NM_DBUS_INTERFACE_DEVICE ".Vlan" +#define NM_DBUS_INTERFACE_DEVICE_BRIDGE NM_DBUS_INTERFACE_DEVICE ".Bridge" +#define NM_DBUS_INTERFACE_DEVICE_GENERIC NM_DBUS_INTERFACE_DEVICE ".Generic" +#define NM_DBUS_INTERFACE_DEVICE_VETH NM_DBUS_INTERFACE_DEVICE ".Veth" +#define NM_DBUS_INTERFACE_DEVICE_TUN NM_DBUS_INTERFACE_DEVICE ".Tun" +#define NM_DBUS_INTERFACE_DEVICE_MACSEC NM_DBUS_INTERFACE_DEVICE ".Macsec" +#define NM_DBUS_INTERFACE_DEVICE_MACVLAN NM_DBUS_INTERFACE_DEVICE ".Macvlan" +#define NM_DBUS_INTERFACE_DEVICE_PPP NM_DBUS_INTERFACE_DEVICE ".Ppp" +#define NM_DBUS_INTERFACE_DEVICE_VXLAN NM_DBUS_INTERFACE_DEVICE ".Vxlan" +#define NM_DBUS_INTERFACE_DEVICE_GRE NM_DBUS_INTERFACE_DEVICE ".Gre" +#define NM_DBUS_INTERFACE_DEVICE_IP_TUNNEL NM_DBUS_INTERFACE_DEVICE ".IPTunnel" +#define NM_DBUS_INTERFACE_DEVICE_STATISTICS NM_DBUS_INTERFACE_DEVICE ".Statistics" #define NM_DBUS_INTERFACE_SETTINGS "org.freedesktop.NetworkManager.Settings" #define NM_DBUS_PATH_SETTINGS "/org/freedesktop/NetworkManager/Settings" @@ -205,35 +206,37 @@ typedef enum { * @NM_DEVICE_TYPE_MACSEC: a MACsec interface * @NM_DEVICE_TYPE_DUMMY: a dummy interface * @NM_DEVICE_TYPE_PPP: a PPP interface + * @NM_DEVICE_TYPE_OPENVSWITCH: a OpenVSwitch interface * * #NMDeviceType values indicate the type of hardware represented by a * device object. **/ typedef enum { - NM_DEVICE_TYPE_UNKNOWN = 0, - NM_DEVICE_TYPE_ETHERNET = 1, - NM_DEVICE_TYPE_WIFI = 2, - NM_DEVICE_TYPE_UNUSED1 = 3, - NM_DEVICE_TYPE_UNUSED2 = 4, - NM_DEVICE_TYPE_BT = 5, /* Bluetooth */ - NM_DEVICE_TYPE_OLPC_MESH = 6, - NM_DEVICE_TYPE_WIMAX = 7, - NM_DEVICE_TYPE_MODEM = 8, - NM_DEVICE_TYPE_INFINIBAND = 9, - NM_DEVICE_TYPE_BOND = 10, - NM_DEVICE_TYPE_VLAN = 11, - NM_DEVICE_TYPE_ADSL = 12, - NM_DEVICE_TYPE_BRIDGE = 13, - NM_DEVICE_TYPE_GENERIC = 14, - NM_DEVICE_TYPE_TEAM = 15, - NM_DEVICE_TYPE_TUN = 16, - NM_DEVICE_TYPE_IP_TUNNEL = 17, - NM_DEVICE_TYPE_MACVLAN = 18, - NM_DEVICE_TYPE_VXLAN = 19, - NM_DEVICE_TYPE_VETH = 20, - NM_DEVICE_TYPE_MACSEC = 21, - NM_DEVICE_TYPE_DUMMY = 22, - NM_DEVICE_TYPE_PPP = 23, + NM_DEVICE_TYPE_UNKNOWN = 0, + NM_DEVICE_TYPE_ETHERNET = 1, + NM_DEVICE_TYPE_WIFI = 2, + NM_DEVICE_TYPE_UNUSED1 = 3, + NM_DEVICE_TYPE_UNUSED2 = 4, + NM_DEVICE_TYPE_BT = 5, /* Bluetooth */ + NM_DEVICE_TYPE_OLPC_MESH = 6, + NM_DEVICE_TYPE_WIMAX = 7, + NM_DEVICE_TYPE_MODEM = 8, + NM_DEVICE_TYPE_INFINIBAND = 9, + NM_DEVICE_TYPE_BOND = 10, + NM_DEVICE_TYPE_VLAN = 11, + NM_DEVICE_TYPE_ADSL = 12, + NM_DEVICE_TYPE_BRIDGE = 13, + NM_DEVICE_TYPE_GENERIC = 14, + NM_DEVICE_TYPE_TEAM = 15, + NM_DEVICE_TYPE_TUN = 16, + NM_DEVICE_TYPE_IP_TUNNEL = 17, + NM_DEVICE_TYPE_MACVLAN = 18, + NM_DEVICE_TYPE_VXLAN = 19, + NM_DEVICE_TYPE_VETH = 20, + NM_DEVICE_TYPE_MACSEC = 21, + NM_DEVICE_TYPE_DUMMY = 22, + NM_DEVICE_TYPE_PPP = 23, + NM_DEVICE_TYPE_OPENVSWITCH = 24, } NMDeviceType; /** diff --git a/libnm-core/nm-setting.c b/libnm-core/nm-setting.c index 3e2ee3debc..eed23c3e76 100644 --- a/libnm-core/nm-setting.c +++ b/libnm-core/nm-setting.c @@ -260,6 +260,8 @@ _nm_setting_slave_type_is_valid (const char *slave_type, const char **out_port_t port_type = NM_SETTING_BRIDGE_PORT_SETTING_NAME; else if (!strcmp (slave_type, NM_SETTING_TEAM_SETTING_NAME)) port_type = NM_SETTING_TEAM_PORT_SETTING_NAME; + else if (!strcmp (slave_type, NM_SETTING_OPENVSWITCH_SETTING_NAME)) + ; else found = FALSE; diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 48078a3902..22faea2e40 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -1587,6 +1587,8 @@ nm_device_get_priority (NMDevice *self) return 410; case NM_DEVICE_TYPE_BRIDGE: return 425; + case NM_DEVICE_TYPE_OPENVSWITCH: + return 430; case NM_DEVICE_TYPE_TUN: return 450; case NM_DEVICE_TYPE_PPP: diff --git a/src/devices/openvswitch/nm-device-openvswitch.c b/src/devices/openvswitch/nm-device-openvswitch.c new file mode 100644 index 0000000000..0f2e14d623 --- /dev/null +++ b/src/devices/openvswitch/nm-device-openvswitch.c @@ -0,0 +1,273 @@ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-device-openvswitch.h" +#include "nm-openvswitch-factory.h" + +#include "devices/nm-device-private.h" +#include "nm-setting-openvswitch.h" + +#include "introspection/org.freedesktop.NetworkManager.Device.Openvswitch.h" + +#include "devices/nm-device-logging.h" +_LOG_DECLARE_SELF(NMDeviceOpenvswitch); + +/*****************************************************************************/ + +struct _NMDeviceOpenvswitch { + NMDevice parent; +}; + +struct _NMDeviceOpenvswitchClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE (NMDeviceOpenvswitch, nm_device_openvswitch, NM_TYPE_DEVICE) + +/*****************************************************************************/ + +static void +link_changed (NMDevice *device, const NMPlatformLink *pllink) +{ + NMDeviceOpenvswitch *self = NM_DEVICE_OPENVSWITCH (device); + + NM_DEVICE_CLASS (nm_device_openvswitch_parent_class)->link_changed (device, pllink); + + if (pllink && nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG) { + _LOGD (LOGD_DEVICE, "the link appeared, continuing activation"); + nm_device_activate_schedule_stage2_device_config (device); + } +} + +static void +add_br_cb (GError *error, gpointer user_data) +{ + NMDeviceOpenvswitch *self = user_data; + + if (error) { + _LOGW (LOGD_DEVICE, "%s", error->message); + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_UNKNOWN); + } + + g_object_unref (self); +} + +static gboolean +create_and_realize (NMDevice *device, + NMConnection *connection, + NMDevice *parent, + const NMPlatformLink **out_plink, + GError **error) +{ + NMDeviceFactory *factory; + + factory = nm_device_factory_manager_find_factory_for_link_type (NM_LINK_TYPE_OPENVSWITCH); + g_return_val_if_fail (factory, FALSE); + + nm_openvswitch_factory_add_br (NM_OPENVSWITCH_FACTORY (factory), + nm_device_get_iface (device), + add_br_cb, + g_object_ref (device)); + + /* We don't have a plink yet, since the device is eventually instantiated + * by ovs-vswitchd asynchronously. Manager knows and manager is fine with that. */ + + return TRUE; +} + +static void +del_br_cb (GError *error, gpointer user_data) +{ + NMDeviceOpenvswitch *self = user_data; + + if (error) { + _LOGW (LOGD_DEVICE, "%s", error->message); + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_UNKNOWN); + } + + g_object_unref (self); +} + +static gboolean +unrealize (NMDevice *device, GError **error) +{ + NMDeviceFactory *factory; + + factory = nm_device_factory_manager_find_factory_for_link_type (NM_LINK_TYPE_OPENVSWITCH); + g_return_val_if_fail (factory, FALSE); + + nm_openvswitch_factory_del_br (NM_OPENVSWITCH_FACTORY (factory), + nm_device_get_iface (device), + del_br_cb, + g_object_ref (device)); + + return TRUE; +} + +static NMDeviceCapabilities +get_generic_capabilities (NMDevice *device) +{ + return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE; +} + + +static gboolean +check_connection_compatible (NMDevice *device, NMConnection *connection) +{ + NMSettingOpenvswitch *s_openvswitch; + + if (!NM_DEVICE_CLASS (nm_device_openvswitch_parent_class)->check_connection_compatible (device, connection)) + return FALSE; + + s_openvswitch = nm_connection_get_setting_openvswitch (connection); + if (!s_openvswitch) + return FALSE; + + return TRUE; +} + +static NMActStageReturn +act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceOpenvswitch *self = NM_DEVICE_OPENVSWITCH (device); + + if (nm_device_get_ifindex (device)) { + return NM_ACT_STAGE_RETURN_SUCCESS; + } else { + _LOGD (LOGD_DEVICE, "the link is not there, waiting for it to appear"); + return NM_ACT_STAGE_RETURN_POSTPONE; + } +} + +static void +update_connection (NMDevice *device, NMConnection *connection) +{ + NMSettingOpenvswitch *s_openvswitch = nm_connection_get_setting_openvswitch (connection); + + if (!s_openvswitch) { + s_openvswitch = (NMSettingOpenvswitch *) nm_setting_openvswitch_new (); + nm_connection_add_setting (connection, (NMSetting *) s_openvswitch); + } +} + +static void +add_port_cb (GError *error, gpointer user_data) +{ + NMDevice *slave = user_data; + + if (error) { + nm_log_warn (LOGD_DEVICE, "device %s could not be added to a ovs port: %s", + nm_device_get_iface (slave), error->message); + nm_device_state_changed (slave, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_UNKNOWN); + } + + g_object_unref (slave); +} + +static gboolean +enslave_slave (NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure) +{ + NMDeviceFactory *factory; + + factory = nm_device_factory_manager_find_factory_for_link_type (NM_LINK_TYPE_OPENVSWITCH); + g_return_val_if_fail (factory, FALSE); + + if (configure) { + nm_openvswitch_factory_add_port (NM_OPENVSWITCH_FACTORY (factory), + nm_device_get_iface (device), + nm_device_get_iface (slave), + add_port_cb, + g_object_ref (slave)); + } + + return TRUE; +} + +static void +del_port_cb (GError *error, gpointer user_data) +{ + NMDevice *slave = user_data; + + if (error) { + nm_log_warn (LOGD_DEVICE, "device %s could not be removed from a ovs port: %s", + nm_device_get_iface (slave), error->message); + nm_device_state_changed (slave, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_UNKNOWN); + } + + g_object_unref (slave); +} + +static void +release_slave (NMDevice *device, NMDevice *slave, gboolean configure) +{ + NMDeviceFactory *factory; + + factory = nm_device_factory_manager_find_factory_for_link_type (NM_LINK_TYPE_OPENVSWITCH); + g_return_if_fail (factory); + + if (configure) { + nm_openvswitch_factory_del_port (NM_OPENVSWITCH_FACTORY (factory), + nm_device_get_iface (device), + nm_device_get_iface (slave), + del_port_cb, + g_object_ref (slave)); + } +} + +/*****************************************************************************/ + +static void +nm_device_openvswitch_init (NMDeviceOpenvswitch *self) +{ +} + +static void +nm_device_openvswitch_class_init (NMDeviceOpenvswitchClass *klass) +{ + NMDeviceClass *device_class = NM_DEVICE_CLASS (klass); + + NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_OPENVSWITCH) + + device_class->connection_type = NM_SETTING_OPENVSWITCH_SETTING_NAME; + device_class->is_master = TRUE; + + device_class->link_changed = link_changed; + device_class->create_and_realize = create_and_realize; + device_class->unrealize = unrealize; + device_class->get_generic_capabilities = get_generic_capabilities; + device_class->check_connection_compatible = check_connection_compatible; + device_class->act_stage2_config = act_stage2_config; + device_class->update_connection = update_connection; + device_class->enslave_slave = enslave_slave; + device_class->release_slave = release_slave; + + nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass), + NMDBUS_TYPE_DEVICE_OPENVSWITCH_SKELETON, + NULL); +} diff --git a/src/devices/openvswitch/nm-device-openvswitch.h b/src/devices/openvswitch/nm-device-openvswitch.h new file mode 100644 index 0000000000..b2d24f2eb8 --- /dev/null +++ b/src/devices/openvswitch/nm-device-openvswitch.h @@ -0,0 +1,35 @@ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2017 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DEVICE_OPENVSWITCH_H__ +#define __NETWORKMANAGER_DEVICE_OPENVSWITCH_H__ + +#define NM_TYPE_DEVICE_OPENVSWITCH (nm_device_openvswitch_get_type ()) +#define NM_DEVICE_OPENVSWITCH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_OPENVSWITCH, NMDeviceOpenvswitch)) +#define NM_DEVICE_OPENVSWITCH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEVICE_OPENVSWITCH, NMDeviceOpenvswitchClass)) +#define NM_IS_DEVICE_OPENVSWITCH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_OPENVSWITCH)) +#define NM_IS_DEVICE_OPENVSWITCH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_OPENVSWITCH)) +#define NM_DEVICE_OPENVSWITCH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_OPENVSWITCH, NMDeviceOpenvswitchClass)) + +typedef struct _NMDeviceOpenvswitch NMDeviceOpenvswitch; +typedef struct _NMDeviceOpenvswitchClass NMDeviceOpenvswitchClass; + +GType nm_device_openvswitch_get_type (void); + +#endif /* __NETWORKMANAGER_DEVICE_OPENVSWITCH_H__ */ diff --git a/src/devices/openvswitch/nm-openvswitch-factory.c b/src/devices/openvswitch/nm-openvswitch-factory.c new file mode 100644 index 0000000000..af08928f71 --- /dev/null +++ b/src/devices/openvswitch/nm-openvswitch-factory.c @@ -0,0 +1,1005 @@ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include <string.h> +#include <jansson.h> +#include <gmodule.h> +#include <gio/gunixsocketaddress.h> + +#include "nm-openvswitch-factory.h" +#include "nm-device-openvswitch.h" +#include "platform/nm-platform.h" +#include "nm-default.h" +#include "nm-core-internal.h" + +/*****************************************************************************/ + +typedef struct { + char *name; + GPtrArray *interfaces; /* interface uuids */ +} OpenvswitchPort; + +typedef struct { + char *name; + GPtrArray *ports; /* port uuids */ +} OpenvswitchBridge; + +typedef struct { + GSocketClient *client; + GSocketConnection *conn; + GCancellable *cancellable; + char buf[4096]; /* Input buffer */ + size_t bufp; /* Last decoded byte in the input buffer. */ + GString *input; /* JSON stream waiting for decoding. */ + GString *output; /* JSON stream to be sent. */ + guint64 seq; + GArray *calls; /* Method calls waiting for a response. */ + GHashTable *interfaces; /* interface uuid => interface name */ + GHashTable *ports; /* port uuid => OpenvswitchBridge */ + GHashTable *bridges; /* bridge uuid => OpenvswitchBridge */ + const char *db_uuid; +} NMOpenvswitchFactoryPrivate; + +struct _NMOpenvswitchFactory { + NMDeviceFactory parent; + NMOpenvswitchFactoryPrivate _priv; +}; + +struct _NMOpenvswitchFactoryClass { + NMDeviceFactoryClass parent; +}; + +G_DEFINE_TYPE (NMOpenvswitchFactory, nm_openvswitch_factory, NM_TYPE_DEVICE_FACTORY) + +#define NM_OPENVSWITCH_FACTORY_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMOpenvswitchFactory, NM_IS_OPENVSWITCH_FACTORY) + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_DEVICE +#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "openvswitch", __VA_ARGS__) + +/*****************************************************************************/ + +static void ovsdb_try_connect (NMOpenvswitchFactory *self); +static void ovsdb_disconnect (NMOpenvswitchFactory *self); +static void ovsdb_read (NMOpenvswitchFactory *self); +static void ovsdb_write (NMOpenvswitchFactory *self); +static void ovsdb_next_command (NMOpenvswitchFactory *self); + +/*****************************************************************************/ + +/* ovsdb command abstraction. */ + +typedef void (*OvsdbMethodCallback) (NMOpenvswitchFactory *self, json_t *response, + GError *error, gpointer user_data); + +typedef enum { + OVSDB_MONITOR, + OVSDB_ADD_BR, + OVSDB_DEL_BR, + OVSDB_ADD_PORT, + OVSDB_DEL_PORT, +} OvsdbCommand; + +typedef struct { + guint64 id; + OvsdbMethodCallback callback; + gpointer user_data; + OvsdbCommand command; + char bridge_iface[IFNAMSIZ + 1]; /* Used by add and del commands. */ + char port_iface[IFNAMSIZ + 1]; /* Used by port commands. */ +} OvsdbMethodCall; + +/** + * ovsdb_call_method: + * + * Queues the ovsdb command. Eventually fires the command right away if + * there's no command pending completion. + */ +static void +ovsdb_call_method (NMOpenvswitchFactory *self, OvsdbCommand command, + const char *bridge_iface, const char *port_iface, + OvsdbMethodCallback callback, gpointer user_data) +{ + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + OvsdbMethodCall *call; + + /* Ensure we're not unsynchronized before we queue the method call. */ + ovsdb_try_connect (self); + + g_array_set_size (priv->calls, priv->calls->len + 1); + call = &g_array_index (priv->calls, OvsdbMethodCall, priv->calls->len - 1); + call->id = priv->seq++; + call->command = command; + if (bridge_iface) { + /* The add and del commands use a bridge_iface parameter. */ + strcpy (call->bridge_iface, bridge_iface); + } + if (port_iface) { + /* The port commands use a port_iface parameter. */ + strcpy (call->port_iface, port_iface); + } + call->callback = callback; + call->user_data = user_data; + + if (priv->calls->len == 1) { + /* There was no command waiting for completion -- we're free + * to ahead and proceed serializing and write this one without + * waiting for a command to complete. */ + ovsdb_next_command (self); + } +} + +/*****************************************************************************/ + +/* Create and process the JSON-RPC messages from ovsdb. */ + +/* + * _fill_ports: + * + * Put set of all ports of @bridge_iface into @items and all but + * @exclude_port_iface into @new_items. + */ +static void +_fill_ports (NMOpenvswitchFactory *self, + const char *bridge_iface, const char *exclude_port_iface, + json_t **items, json_t **new_items) +{ + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + GHashTableIter iter; + char *bridge_uuid; + char *port_uuid; + OpenvswitchBridge *ovs_bridge; + OpenvswitchPort *ovs_port; + int i; + + *items = json_array (); + *new_items = json_array (); + + g_hash_table_iter_init (&iter, priv->bridges); + while (g_hash_table_iter_next (&iter, (gpointer) &bridge_uuid, (gpointer) &ovs_bridge)) { + if (g_strcmp0 (ovs_bridge->name, bridge_iface) != 0) + continue; + for (i = 0; i < ovs_bridge->ports->len; i++) { + port_uuid = g_ptr_array_index (ovs_bridge->ports, i); + json_array_append_new (*items, json_pack ("[s,s]", "uuid", port_uuid)); + + ovs_port = g_hash_table_lookup (priv->ports, port_uuid); + if (!ovs_port) + continue; + if (g_strcmp0 (exclude_port_iface, ovs_port->name) == 0) + continue; + json_array_append_new (*new_items, json_pack ("[s,s]", "uuid", port_uuid)); + } + } +} + +/** + * _fill_bridges: + * + * Put set of all bridges into @items and all but @exclude_bridge_iface into + * @new_items. + */ +static void +_fill_bridges (NMOpenvswitchFactory *self, const char *exclude_bridge_iface, + json_t **items, json_t **new_items) +{ + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + GHashTableIter iter; + char *bridge_uuid; + OpenvswitchBridge *ovs_bridge; + + *items = json_array (); + *new_items = json_array (); + + g_hash_table_iter_init (&iter, priv->bridges); + while (g_hash_table_iter_next (&iter, (gpointer) &bridge_uuid, (gpointer) &ovs_bridge)) { + json_array_append_new (*items, json_pack ("[s,s]", "uuid", bridge_uuid)); + if (g_strcmp0 (exclude_bridge_iface, ovs_bridge->name) != 0) + json_array_append_new (*new_items, json_pack ("[s,s]", "uuid", bridge_uuid)); + } +} + +/** + * _expect_ports: + * + * Return a command that will fail the transaction if the actual set of + * ports in @bridge_iface doesn't match @ports. This is a way of detecting + * race conditions with other ovsdb clients that might be adding or removing + * bridge ports at the same time. + */ +static json_t * +_expect_ports (const char *bridge_iface, const json_t *ports) +{ + return json_pack ("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, o]}], s:[[s, s, s]]}", + "op", "wait", "table", "Bridge", + "timeout", 0, "columns", "ports", + "until", "==", "rows", "ports", "set", ports, + "where", "name", "==", bridge_iface); +} + +/** + * _set_ports: + * + * Return a command that will update the list of ports of @bridge_iface + * to @ports. + */ +static json_t * +_set_ports (const char *bridge_iface, const json_t *ports) +{ + return json_pack ("{s:s, s:s, s:{s:[s, o]}, s:[[s, s, s]]}", + "op", "update", "table", "Bridge", + "row", "ports", "set", ports, + "where", "name", "==", bridge_iface); +} + +/** + * _expect_bridges: + * + * Return a command that will fail the transaction if the actual set of + * bridges doesn't match @bridges. This is a way of detecting race conditions + * with other ovsdb clients that might be adding or removing bridges + * at the same time. + */ +static json_t * +_expect_bridges (json_t *bridges, const char *db_uuid) +{ + return json_pack ("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, o]}], s:[[s, s, [s, s]]]}", + "op", "wait", "table", "Open_vSwitch", + "timeout", 0, "columns", "bridges", + "until", "==", "rows", "bridges", "set", bridges, + "where", "_uuid", "==", "uuid", db_uuid); +} + +/** + * _set_bridges: + * + * Return a command that will update the list of bridges in @db_uuid + * database to @bridges. + */ +static json_t * +_set_bridges (const json_t *bridges, const char *db_uuid) +{ + return json_pack ("{s:s, s:s, s:{s:[s, o]}, s:[[s, s, [s, s]]]}", + "op", "update", "table", "Open_vSwitch", + "row", "bridges", "set", bridges, + "where", "_uuid", "==", "uuid", db_uuid); +} + +/** + * _inc_next_cfg: + * + * Returns an mutate commands that bumps next_cfg upon successful completion + * of the transaction it is in. + */ +static json_t * +_inc_next_cfg (const char *db_uuid) +{ + return json_pack ("{s:s, s:s, s:[[s, s, i]], s:[[s, s, [s, s]]]}", + "op", "mutate", "table", "Open_vSwitch", + "mutations", "next_cfg", "+=", 1, + "where", "_uuid", "==", "uuid", db_uuid); +} + +/** + * ovsdb_next_command: + * + * Translates a higher level operation (add/remove bridge/port) to a RFC 7047 + * command serialized into JSON ands sends it over to the database. + + * Only called when no command is waiting for a response, since the serialized + * command might depend on result of a previous one (add and remove need to + * include an up to date bridge list in their transactions to rule out races). + */ +static void +ovsdb_next_command (NMOpenvswitchFactory *self) +{ + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + OvsdbMethodCall *call = NULL; + char *cmd; + json_t *msg; + json_t *items, *new_items; + + if (!priv->conn) + return; + if (!priv->calls->len) + return; + call = &g_array_index (priv->calls, OvsdbMethodCall, 0); + + switch (call->command) { + case OVSDB_MONITOR: + msg = json_pack ("{s:i, s:s, s:[s, n, {" + " s:[{s:[s, s]}]," + " s:[{s:[s, s]}]," + " s:[{s:[s]}]," + " s:[{s:[]}]" + "}]}", + "id", call->id, + "method", "monitor", "params", "Open_vSwitch", + "Bridge", "columns", "name", "ports", + "Port", "columns", "name", "interfaces", + "Interface", "columns", "name", + "Open_vSwitch", "columns"); + break; + case OVSDB_ADD_BR: + _fill_bridges (self, call->bridge_iface, &items, &new_items); + json_array_append_new (new_items, json_pack ("[s,s]", "named-uuid", "rowBridge")); + + msg = json_pack ("{s:i, s:s, s:[s,o,o,o" + " {s:s, s:s, s:{s:s, s:s}, s:s}, " /* insert interface */ + " {s:s, s:s, s:{s:s, s:[s, s]}, s:s}," /* insert port */ + " {s:s, s:s, s:{s:s, s:[s, s]}, s:s}," /* insert bridge */ + "]}", + "id", call->id, + "method", "transact", "params", "Open_vSwitch", + _expect_bridges (items, priv->db_uuid), + _set_bridges (new_items, priv->db_uuid), + _inc_next_cfg (priv->db_uuid), + "op", "insert", "table", "Interface", "row", "name", call->bridge_iface, + "type", "internal", "uuid-name", "rowIntf", + "op", "insert", "table", "Port", "row", "name", call->bridge_iface, + "interfaces", "named-uuid", "rowIntf", "uuid-name", "rowPort", + "op", "insert", "table", "Bridge", "row", "name", call->bridge_iface, + "ports", "named-uuid", "rowPort", "uuid-name", "rowBridge"); + break; + case OVSDB_DEL_BR: + _fill_bridges (self, call->bridge_iface, &items, &new_items); + + msg = json_pack ("{s:i, s:s, s:[s,o,o,o]}", + "id", call->id, + "method", "transact", "params", "Open_vSwitch", + _expect_bridges (items, priv->db_uuid), + _set_bridges (new_items, priv->db_uuid), + _inc_next_cfg (priv->db_uuid)); + break; + case OVSDB_ADD_PORT: + _fill_ports (self, call->bridge_iface, call->port_iface, &items, &new_items); + json_array_append_new (new_items, json_pack ("[s,s]", "named-uuid", "rowPort")); + + msg = json_pack ("{s:i, s:s, s:[s,o,o,o" + " {s:s, s:s, s:{s:s}, s:s}, " /* insert interface */ + " {s:s, s:s, s:{s:s, s:[s, s]}, s:s}," /* insert port */ + "]}", + "id", call->id, + "method", "transact", "params", "Open_vSwitch", + _expect_ports (call->bridge_iface, items), + _set_ports (call->bridge_iface, new_items), + _inc_next_cfg (priv->db_uuid), + "op", "insert", "table", "Interface", "row", "name", call->port_iface, + "uuid-name", "rowIntf", + "op", "insert", "table", "Port", "row", "name", call->port_iface, + "interfaces", "named-uuid", "rowIntf", "uuid-name", "rowPort"); + break; + case OVSDB_DEL_PORT: + _fill_ports (self, call->bridge_iface, call->port_iface, &items, &new_items); + + msg = json_pack ("{s:i, s:s, s:[s,o,o,o]}", + "id", call->id, + "method", "transact", "params", "Open_vSwitch", + _expect_ports (call->bridge_iface, items), + _set_ports (call->bridge_iface, new_items), + _inc_next_cfg (priv->db_uuid)); + break; + } + + cmd = json_dumps (msg, 0); + g_string_append (priv->output, cmd); + json_decref (msg); + free (cmd); + + ovsdb_write (self); +} + +static void +_uuids_to_array (GPtrArray *array, const json_t *items) +{ + const char *key; + json_t *value; + size_t index = 0; + json_t *set_value; + size_t set_index; + + while (index < json_array_size (items)) { + key = json_string_value (json_array_get (items, index)); + index++; + value = json_array_get (items, index); + index++; + + if (!value) + return; + + if (g_strcmp0 (key, "uuid") == 0 && json_is_string (value)) { + g_ptr_array_add (array, g_strdup (json_string_value (value))); + } else if (g_strcmp0 (key, "set") == 0 && json_is_array (value)) { + json_array_foreach (value, set_index, set_value) { + _uuids_to_array (array, set_value); + } + } + } +} + +/** + * ovsdb_got_update: + * + * Called when we've got an "update" method call (we asked for it with the monitor + * command). We use it to maintain a consistent view of bridge list regardless of + * whether the changes are done by us or externally. + */ +static void +ovsdb_got_update (NMOpenvswitchFactory *self, json_t *msg) +{ + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + json_t *ovs = NULL; + json_t *bridge = NULL; + json_t *port = NULL; + json_t *interface = NULL; + json_t *items; + json_error_t json_error = { 0, }; + void *iter; + const char *name; + const char *key; + json_t *value; + OpenvswitchBridge *ovs_bridge; + OpenvswitchPort *ovs_port; + + if (json_unpack_ex (msg, &json_error, 0, "{s?:o, s?:o, s?:o, s?:o}}", + "Open_vSwitch", &ovs, + "Bridge", &bridge, + "Port", &port, + "Interface", &interface) == -1) { + /* This doesn't really have to be an error; the key might + * be missing if there really are no bridges present. */ + _LOGD ("Bad update: %s", json_error.text); + } + + if (ovs) { + iter = json_object_iter (ovs); + priv->db_uuid = g_strdup (iter ? json_object_iter_key (iter) : NULL); + } + + json_object_foreach (port, key, value) { + if (json_unpack (value, "{s:{}}", "old") == 0) { + _LOGT ("removed a port: %s", name); + g_hash_table_remove (priv->ports, key); + } + if (json_unpack (value, "{s:{s?:s, s?:o}}", "new", "name", &name, "interfaces", &items) == 0) { + _LOGT ("added a port: %s", name); + ovs_port = g_slice_new (OpenvswitchPort); + ovs_port->name = g_strdup (name); + ovs_port->interfaces = g_ptr_array_new_with_free_func (g_free); + _uuids_to_array (ovs_port->interfaces, items); + g_hash_table_insert (priv->ports, g_strdup (key), ovs_port); + } + } + + json_object_foreach (bridge, key, value) { + if (json_unpack (value, "{s:{}}", "old") == 0) { + _LOGT ("removed a bridge: %s", name); + g_hash_table_remove (priv->bridges, key); + } + if (json_unpack (value, "{s:{s?:s, s?:o}}", "new", "name", &name, "ports", &items) == 0) { + _LOGT ("added a bridge: %s", name); + ovs_bridge = g_slice_new (OpenvswitchBridge); + ovs_bridge->name = g_strdup (name); + ovs_bridge->ports = g_ptr_array_new_with_free_func (g_free); + _uuids_to_array (ovs_bridge->ports, items); + g_hash_table_insert (priv->bridges, g_strdup (key), ovs_bridge); + } + } +} + +/** + * ovsdb_got_msg:: + * + * Called when when a complete JSON object was seen and unmarshalled. + * Either finishes a method call or processes a method call. + */ +static void +ovsdb_got_msg (NMOpenvswitchFactory *self, json_t *msg) +{ + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + json_t *json_id = NULL; + guint64 id; + json_t *json_method = NULL; + const char *method = NULL; + OvsdbMethodCall *call = NULL; + json_error_t json_error = { 0, }; + json_t *params = NULL; + + json_id = json_object_get (msg, "id"); + if (json_is_number (json_id)) { + /* This is a response to a method call. */ + id = json_integer_value (json_id); + + if (!priv->calls->len) { + _LOGE ("there are no queued calls expecting response %ld", id); + ovsdb_disconnect (self); + return; + } + call = &g_array_index (priv->calls, OvsdbMethodCall, 0); + if (call->id != id) { + _LOGE ("expected a response to call %ld, not %ld", call->id, id); + ovsdb_disconnect (self); + return; + } + + /* Cool, we found a corresponsing call. Finish it. */ + call->callback (self, msg, NULL, call->user_data); + g_array_remove_index (priv->calls, 0); + + /* Now we're free to serialize and send the next command, if any. */ + ovsdb_next_command (self); + return; + } + + json_method = json_object_get (msg, "method"); + if (json_is_string (json_method)) + method = json_string_value (json_method); + if (g_strcmp0 (method, "update") == 0) { + /* This is a update method call. */ + if (json_unpack_ex (msg, &json_error, 0, "{s:[n,o]}", + "params", ¶ms) == -1) { + _LOGD ("a update call with no params: %s", json_error.text); + ovsdb_disconnect (self); + return; + } + ovsdb_got_update (self, params); + return; + } + + /* This is a message we are not interested in. */ + _LOGD ("got an unknown message, ignoring"); +} + +/*****************************************************************************/ + +/* Lower level marshalling and demarshalling of the JSON-RPC traffic on the + * ovsdb socket. */ + +static size_t +_json_callback (void *buffer, size_t buflen, void *user_data) +{ + NMOpenvswitchFactory *self = NM_OPENVSWITCH_FACTORY (user_data); + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + + if (priv->bufp == priv->input->len) { + /* No more bytes buffered for decoding. */ + return 0; + } + + /* Pass one more byte to the JSON decoder. */ + *(char *)buffer = priv->input->str[priv->bufp]; + priv->bufp++; + + return (size_t)1; +} + +/** + * ovsdb_read_cb: + * + * Read out the data available from the ovsdb socket and try to deserialize + * the JSON. If we see a complete object, pass it upwards to ovsdb_got_msg(). + */ +static void +ovsdb_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + NMOpenvswitchFactory *self = NM_OPENVSWITCH_FACTORY (user_data); + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + GInputStream *stream = G_INPUT_STREAM (source_object); + GError *error = NULL; + gssize size; + json_t *msg; + json_error_t json_error = { 0, }; + + size = g_input_stream_read_finish (stream, res, &error); + if (size == -1) { + _LOGW ("short read from ovsdb: %s", error->message); + g_clear_error (&error); + ovsdb_disconnect (self); + return; + } + + g_string_append_len (priv->input, priv->buf, size); + do { + priv->bufp = 0; + /* The callback always eats up only up to a single byte. This makes + * it possible for us to identify complete JSON objects in spite of + * us not knowing the length in advance. */ + msg = json_load_callback (_json_callback, self, JSON_DISABLE_EOF_CHECK, &json_error); + if (msg) { + ovsdb_got_msg (self, msg); + g_string_erase (priv->input, 0, priv->bufp); + } + json_decref (msg); + } while (msg); + + if (size) + ovsdb_read (self); +} + +static void +ovsdb_read (NMOpenvswitchFactory *self) +{ + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + + g_input_stream_read_async (g_io_stream_get_input_stream (G_IO_STREAM (priv->conn)), + priv->buf, sizeof(priv->buf), + G_PRIORITY_DEFAULT, NULL, ovsdb_read_cb, self); +} + +static void +ovsdb_write_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GOutputStream *stream = G_OUTPUT_STREAM (source_object); + NMOpenvswitchFactory *self = NM_OPENVSWITCH_FACTORY (user_data); + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + GError *error = NULL; + gssize size; + + size = g_output_stream_write_finish (stream, res, &error); + if (size == -1) { + _LOGW ("short write to ovsdb: %s", error->message); + g_clear_error (&error); + ovsdb_disconnect (self); + return; + } + + g_string_erase (priv->output, 0, size); + + ovsdb_write (self); +} + +static void +ovsdb_write (NMOpenvswitchFactory *self) +{ + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + GOutputStream *stream; + + if (!priv->output->len) + return; + + stream = g_io_stream_get_output_stream (G_IO_STREAM (priv->conn)); + if (g_output_stream_has_pending (stream)) + return; + + g_output_stream_write_async (stream, + priv->output->str, priv->output->len, + G_PRIORITY_DEFAULT, NULL, ovsdb_write_cb, self); +} +/*****************************************************************************/ + +/* Routines to maintain the ovsdb connection. */ + +/** + * ovsdb_disconnect: + * + * Clean up the internal state to the point equivalent to before connecting. + * Apart from clean shutdown this is a good response to unexpected trouble, + * since the next method call attempt a will trigger reconnect which hopefully + * puts us back in sync. + */ +static void +ovsdb_disconnect (NMOpenvswitchFactory *self) +{ + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + OvsdbMethodCall *call; + GError *error; + + _LOGD ("disconnecting from ovsdb"); + + while (priv->calls->len) { + error = NULL; + call = &g_array_index (priv->calls, OvsdbMethodCall, priv->calls->len - 1); + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled"); + call->callback (self, NULL, error, call->user_data); + g_array_remove_index (priv->calls, priv->calls->len - 1); + } + + g_string_truncate (priv->input, 0); + g_string_truncate (priv->output, 0); + g_clear_object (&priv->client); + g_clear_object (&priv->conn); + g_clear_pointer (&priv->db_uuid, g_free); +} + +static void +_monitor_bridges_cb (NMOpenvswitchFactory *self, json_t *response, GError *error, gpointer user_data) +{ + json_t *result = NULL; + json_error_t json_error = { 0, }; + + if (error) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + _LOGI ("%s", error->message); + ovsdb_disconnect (self); + } + + g_clear_error (&error); + return; + } + + if (json_unpack_ex (response, &json_error, 0, "{s:o}", + "result", &result) == -1) { + _LOGW ("monitor_bridges finished with no result: %s", json_error.text); + ovsdb_disconnect (self); + return; + } + + /* Treat the first response the same as the subsequent "update" + * messages we eventually get. */ + ovsdb_got_update (self, result); +} + +static void +_client_connect_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GSocketClient *client = G_SOCKET_CLIENT (source_object); + NMOpenvswitchFactory *self = NM_OPENVSWITCH_FACTORY (user_data); + NMOpenvswitchFactoryPrivate *priv; + GError *error = NULL; + GSocketConnection *conn; + + conn = g_socket_client_connect_finish (client, res, &error); + if (conn == NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + _LOGI ("%s", error->message); + + ovsdb_disconnect (self); + g_clear_error (&error); + return; + } + + priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + priv->conn = conn; + g_clear_object (&priv->cancellable); + + ovsdb_read (self); + ovsdb_next_command (self); +} + +/** + * ovsdb_try_connect: + * + * Establish a connection to ovsdb unless it's already established or being + * established. Queues a monitor command as a very first one so that we're in + * sync when other commands are issued. + */ +static void +ovsdb_try_connect (NMOpenvswitchFactory *self) +{ + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + GSocketAddress *addr; + + if (priv->client) + return; + + /* XXX: This should probably be made configurable via NetworkManager.conf */ + addr = g_unix_socket_address_new (RUNSTATEDIR "/openvswitch/db.sock"); + + priv->client = g_socket_client_new (); + priv->cancellable = g_cancellable_new (); + g_socket_client_connect_async (priv->client, G_SOCKET_CONNECTABLE (addr), + priv->cancellable, _client_connect_cb, self); + g_object_unref (addr); + + /* Queue a monitor call before any other command, ensuring that we have an up + * to date view of existing bridged that we need for add and remove ops. */ + ovsdb_call_method (self, OVSDB_MONITOR, NULL, NULL, _monitor_bridges_cb, NULL); +} + +/*****************************************************************************/ + +/* Public functions useful for NMDeviceOpenvswitch to maintain the life cycle of + * their ovsdb entries without having to deal with ovsdb complexities themselves. */ + +typedef struct { + NMOpenvswitchFactoryCallback callback; + gpointer user_data; +} OpenvswitchFactoryCall; + +static void +_transact_cb (NMOpenvswitchFactory *self, json_t *response, GError *error, gpointer user_data) +{ + OpenvswitchFactoryCall *call = user_data; + json_error_t json_error = { 0, }; + const char *err; + const char *err_details; + json_t *result; + size_t index; + json_t *value; + + if (error) + goto out; + + if (json_unpack_ex (response, &json_error, 0, "{s:o}", "result", &result) == -1) { + g_set_error (&error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED, + "Bad response from ovsdb: %s", json_error.text); + goto out; + } + + json_array_foreach (result, index, value) { + if (json_unpack (value, "{s:s, s:s}", "error", &err, "details", &err_details) == 0) { + g_set_error (&error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED, + "Error running the transaction: %s: %s", err, err_details); + goto out; + } + } + +out: + call->callback (error, call->user_data); + g_slice_free (OpenvswitchFactoryCall, call); +} + +static void +_transact_call (NMOpenvswitchFactory *self, OvsdbCommand command, + const char *bridge_iface, const char *port_iface, + NMOpenvswitchFactoryCallback callback, gpointer user_data) +{ + OpenvswitchFactoryCall *call; + + call = g_slice_new (OpenvswitchFactoryCall); + call->callback = callback; + call->user_data = user_data; + + ovsdb_call_method (self, command, bridge_iface, port_iface, _transact_cb, call); +} + +void +nm_openvswitch_factory_add_br (NMOpenvswitchFactory *self, const char *bridge_iface, + NMOpenvswitchFactoryCallback callback, gpointer user_data) +{ + _transact_call (self, OVSDB_ADD_BR, bridge_iface, NULL, + callback, user_data); +} + +void +nm_openvswitch_factory_del_br (NMOpenvswitchFactory *self, const char *bridge_iface, + NMOpenvswitchFactoryCallback callback, gpointer user_data) +{ + _transact_call (self, OVSDB_DEL_BR, bridge_iface, NULL, + callback, user_data); +} + +void +nm_openvswitch_factory_add_port (NMOpenvswitchFactory *self, + const char *bridge_iface, const char *port_iface, + NMOpenvswitchFactoryCallback callback, gpointer user_data) +{ + _transact_call (self, OVSDB_ADD_PORT, bridge_iface, port_iface, + callback, user_data); +} + +void +nm_openvswitch_factory_del_port (NMOpenvswitchFactory *self, + const char *bridge_iface, const char *port_iface, + NMOpenvswitchFactoryCallback callback, gpointer user_data) +{ + _transact_call (self, OVSDB_DEL_PORT, bridge_iface, port_iface, + callback, user_data); +} + +/*****************************************************************************/ + +NM_DEVICE_FACTORY_DECLARE_TYPES ( + NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_OPENVSWITCH) + NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_OPENVSWITCH_SETTING_NAME) +) + +G_MODULE_EXPORT NMDeviceFactory * +nm_device_factory_create (GError **error) +{ + return (NMDeviceFactory *) g_object_new (NM_TYPE_OPENVSWITCH_FACTORY, NULL); +} + +static void +start (NMDeviceFactory *factory) +{ + NMOpenvswitchFactory *self = NM_OPENVSWITCH_FACTORY (factory); + + ovsdb_try_connect (self); +} + +static NMDevice * +create_device (NMDeviceFactory *factory, + const char *iface, + const NMPlatformLink *plink, + NMConnection *connection, + gboolean *out_ignore) +{ + if (g_strcmp0 (iface, "ovs-system") == 0) + return NULL; + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_OPENVSWITCH, + NM_DEVICE_IFACE, iface, + NM_DEVICE_TYPE_DESC, "OpenVSwitch", + NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_OPENVSWITCH, + NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_OPENVSWITCH, + NULL); +} + +static void +_free_port (gpointer data) +{ + OpenvswitchPort *ovs_port = data; + + g_ptr_array_free (ovs_port->interfaces, TRUE); + g_slice_free (OpenvswitchPort, ovs_port); +} + +static void +_free_bridge (gpointer data) +{ + OpenvswitchBridge *ovs_bridge = data; + + g_ptr_array_free (ovs_bridge->ports, TRUE); + g_slice_free (OpenvswitchBridge, ovs_bridge); +} + +static void +nm_openvswitch_factory_init (NMOpenvswitchFactory *self) +{ + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + + priv->calls = g_array_new (FALSE, TRUE, sizeof (OvsdbMethodCall)); + priv->input = g_string_new (NULL); + priv->output = g_string_new (NULL); + priv->ports = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, _free_port); + priv->bridges = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, _free_bridge); +} + +static void +dispose (GObject *object) +{ + NMOpenvswitchFactory *self = NM_OPENVSWITCH_FACTORY (object); + NMOpenvswitchFactoryPrivate *priv = NM_OPENVSWITCH_FACTORY_GET_PRIVATE (self); + + ovsdb_disconnect (self); + + g_string_free (priv->input, TRUE); + priv->input = NULL; + g_string_free (priv->output, TRUE); + priv->output = NULL; + + g_array_free (priv->calls, TRUE); + priv->calls = NULL; + + g_clear_pointer (&priv->ports, g_hash_table_destroy); + g_clear_pointer (&priv->bridges, g_hash_table_destroy); + + g_cancellable_cancel (priv->cancellable); + g_clear_object (&priv->cancellable); + + /* Chain up to the parent class */ + G_OBJECT_CLASS (nm_openvswitch_factory_parent_class)->dispose (object); +} + +static void +nm_openvswitch_factory_class_init (NMOpenvswitchFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS (klass); + + object_class->dispose = dispose; + + factory_class->get_supported_types = get_supported_types; + factory_class->start = start; + factory_class->create_device = create_device; +} diff --git a/src/devices/openvswitch/nm-openvswitch-factory.h b/src/devices/openvswitch/nm-openvswitch-factory.h new file mode 100644 index 0000000000..dbc8db84dd --- /dev/null +++ b/src/devices/openvswitch/nm-openvswitch-factory.h @@ -0,0 +1,53 @@ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2017 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_OPENVSWITCH_FACTORY_H__ +#define __NETWORKMANAGER_OPENVSWITCH_FACTORY_H__ + +#include "devices/nm-device-factory.h" + +#define NM_TYPE_OPENVSWITCH_FACTORY (nm_openvswitch_factory_get_type ()) +#define NM_OPENVSWITCH_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_OPENVSWITCH_FACTORY, NMOpenvswitchFactory)) +#define NM_OPENVSWITCH_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_OPENVSWITCH_FACTORY, NMOpenvswitchFactoryClass)) +#define NM_IS_OPENVSWITCH_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_OPENVSWITCH_FACTORY)) +#define NM_IS_OPENVSWITCH_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_OPENVSWITCH_FACTORY)) +#define NM_OPENVSWITCH_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_OPENVSWITCH_FACTORY, NMOpenvswitchFactoryClass)) + +typedef struct _NMOpenvswitchFactory NMOpenvswitchFactory; +typedef struct _NMOpenvswitchFactoryClass NMOpenvswitchFactoryClass; + +typedef void (*NMOpenvswitchFactoryCallback)(GError *error, gpointer user_data); + +GType nm_openvswitch_factory_get_type (void); + +void nm_openvswitch_factory_add_br (NMOpenvswitchFactory *self, const char *iface, + NMOpenvswitchFactoryCallback callback, gpointer user_data); + +void nm_openvswitch_factory_del_br (NMOpenvswitchFactory *self, const char *iface, + NMOpenvswitchFactoryCallback callback, gpointer user_data); + +void nm_openvswitch_factory_add_port (NMOpenvswitchFactory *self, + const char *bridge_iface, const char *port_iface, + NMOpenvswitchFactoryCallback callback, gpointer user_data); + +void nm_openvswitch_factory_del_port (NMOpenvswitchFactory *self, + const char *bridge_iface, const char *port_iface, + NMOpenvswitchFactoryCallback callback, gpointer user_data); + +#endif /* __NETWORKMANAGER_OPENVSWITCH_FACTORY_H__ */ |