/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2017 Red Hat, Inc. */ #include "src/core/nm-default-daemon.h" #include "nm-ovsdb.h" #include #include #include "libnm-glib-aux/nm-jansson.h" #include "libnm-glib-aux/nm-str-buf.h" #include "libnm-glib-aux/nm-io-utils.h" #include "nm-core-utils.h" #include "libnm-core-intern/nm-core-internal.h" #include "devices/nm-device.h" #include "nm-manager.h" #include "nm-setting-ovs-external-ids.h" #include "nm-setting-ovs-other-config.h" #include "nm-priv-helper-call.h" #include "libnm-platform/nm-platform.h" /*****************************************************************************/ #define OVSDB_MAX_FAILURES 3 #define OTHER_CONFIG_HWADDR "hwaddr" /*****************************************************************************/ #if JANSSON_VERSION_HEX < 0x020400 #warning "requires at least libjansson 2.4" #endif typedef enum { STRDICT_TYPE_EXTERNAL_IDS, STRDICT_TYPE_OTHER_CONFIG, } StrdictType; typedef struct { char *port_uuid; char *name; char *connection_uuid; GPtrArray *interfaces; /* interface uuids */ GArray *external_ids; GArray *other_config; } OpenvswitchPort; typedef struct { char *bridge_uuid; char *name; char *connection_uuid; GPtrArray *ports; /* port uuids */ GArray *external_ids; GArray *other_config; } OpenvswitchBridge; typedef struct { char *interface_uuid; char *name; char *type; char *connection_uuid; GArray *external_ids; GArray *other_config; } OpenvswitchInterface; /*****************************************************************************/ typedef void (*OvsdbMethodCallback)(NMOvsdb *self, json_t *response, GError *error, gpointer user_data); typedef enum { OVSDB_MONITOR, OVSDB_ADD_INTERFACE, OVSDB_DEL_INTERFACE, OVSDB_SET_INTERFACE_MTU, OVSDB_SET_REAPPLY, } OvsdbCommand; #define CALL_ID_UNSPEC G_MAXUINT64 typedef union { struct { } monitor; struct { NMConnection *bridge; NMConnection *port; NMConnection *interface; NMDevice *bridge_device; NMDevice *interface_device; } add_interface; struct { char *ifname; } del_interface; struct { char *ifname; guint32 mtu; } set_interface_mtu; struct { NMDeviceType device_type; char *ifname; NMConnection *connection; GHashTable *external_ids_old; GHashTable *external_ids_new; GHashTable *other_config_old; GHashTable *other_config_new; } set_reapply; } OvsdbMethodPayload; typedef struct { NMOvsdb *self; CList calls_lst; guint64 call_id; OvsdbCommand command; OvsdbMethodCallback callback; gpointer user_data; OvsdbMethodPayload payload; GObject *shutdown_wait_obj; } OvsdbMethodCall; /*****************************************************************************/ enum { DEVICE_ADDED, DEVICE_REMOVED, INTERFACE_FAILED, READY, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL] = {0}; typedef struct { NMPlatform *platform; int conn_fd; GSource *conn_fd_in_source; GSource *conn_fd_out_source; GCancellable *conn_cancellable; NMStrBuf input_buf; NMStrBuf output_buf; GSource *input_timeout_source; guint64 call_id_counter; CList calls_lst_head; GHashTable *interfaces; /* interface uuid => OpenvswitchInterface */ GHashTable *ports; /* port uuid => OpenvswitchPort */ GHashTable *bridges; /* bridge uuid => OpenvswitchBridge */ char *db_uuid; guint num_failures; bool ready : 1; struct { GPtrArray *interfaces; /* Interface names we are waiting to go away */ GSource *timeout_source; /* After all deletions complete, wait this * timeout for interfaces to disappear */ gulong link_changed_id; /* Platform link-changed signal handle */ guint num_pending_del; /* Number of ovsdb deletions pending */ } cleanup; } NMOvsdbPrivate; struct _NMOvsdb { GObject parent; NMOvsdbPrivate _priv; }; struct _NMOvsdbClass { GObjectClass parent; }; G_DEFINE_TYPE(NMOvsdb, nm_ovsdb, G_TYPE_OBJECT) #define NM_OVSDB_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMOvsdb, NM_IS_OVSDB) NM_DEFINE_SINGLETON_GETTER(NMOvsdb, nm_ovsdb_get, NM_TYPE_OVSDB); /*****************************************************************************/ static void ovsdb_try_connect(NMOvsdb *self); static void ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing); static void ovsdb_read(NMOvsdb *self); static void ovsdb_write_try(NMOvsdb *self); static gboolean ovsdb_write_cb(int fd, GIOCondition condition, gpointer user_data); static void ovsdb_next_command(NMOvsdb *self); static void cleanup_check_ready(NMOvsdb *self); /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_DEVICE #define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "ovsdb", __VA_ARGS__) #define _NMLOG_call(level, call, ...) \ _NMLOG((level), \ "call[" NM_HASH_OBFUSCATE_PTR_FMT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ NM_HASH_OBFUSCATE_PTR((call)) _NM_UTILS_MACRO_REST(__VA_ARGS__)) #define _LOGT_call(call, ...) _NMLOG_call(LOGL_TRACE, (call), __VA_ARGS__) /*****************************************************************************/ #define OVSDB_METHOD_PAYLOAD_MONITOR() \ (&((const OvsdbMethodPayload) { \ .monitor = {}, \ })) #define OVSDB_METHOD_PAYLOAD_ADD_INTERFACE(xbridge, \ xport, \ xinterface, \ xbridge_device, \ xinterface_device) \ (&((const OvsdbMethodPayload) { \ .add_interface = \ { \ .bridge = (xbridge), \ .port = (xport), \ .interface = (xinterface), \ .bridge_device = (xbridge_device), \ .interface_device = (xinterface_device), \ }, \ })) #define OVSDB_METHOD_PAYLOAD_DEL_INTERFACE(xifname) \ (&((const OvsdbMethodPayload) { \ .del_interface = \ { \ .ifname = (char *) NM_CONSTCAST(char, (xifname)), \ }, \ })) #define OVSDB_METHOD_PAYLOAD_SET_INTERFACE_MTU(xifname, xmtu) \ (&((const OvsdbMethodPayload) { \ .set_interface_mtu = \ { \ .ifname = (char *) NM_CONSTCAST(char, (xifname)), \ .mtu = (xmtu), \ }, \ })) #define OVSDB_METHOD_PAYLOAD_SET_REAPPLY(xdevice_type, \ xifname, \ xconnection, \ xexternal_ids_old, \ xexternal_ids_new, \ xother_config_old, \ xother_config_new) \ (&((const OvsdbMethodPayload) { \ .set_reapply = {.device_type = xdevice_type, \ .ifname = (char *) NM_CONSTCAST(char, (xifname)), \ .connection = (xconnection), \ .external_ids_old = (xexternal_ids_old), \ .external_ids_new = (xexternal_ids_new), \ .other_config_old = (xother_config_old), \ .other_config_new = (xother_config_new)}, \ })) /*****************************************************************************/ static NM_UTILS_LOOKUP_STR_DEFINE(_device_type_to_table, NMDeviceType, NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(NULL), NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_TYPE_OVS_BRIDGE, "Bridge"), NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_TYPE_OVS_PORT, "Port"), NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_TYPE_OVS_INTERFACE, "Interface"), NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), ); /*****************************************************************************/ static void _call_complete(OvsdbMethodCall *call, json_t *response, GError *error) { g_clear_object(&call->shutdown_wait_obj); if (response) { gs_free char *str = NULL; str = json_dumps(response, 0); if (error) _LOGT_call(call, "completed: %s ; error: %s", str, error->message); else _LOGT_call(call, "completed: %s", str); } else { nm_assert(error); _LOGT_call(call, "completed: error: %s", error->message); } c_list_unlink_stale(&call->calls_lst); if (call->callback) call->callback(call->self, response, error, call->user_data); switch (call->command) { case OVSDB_MONITOR: break; case OVSDB_ADD_INTERFACE: g_clear_object(&call->payload.add_interface.bridge); g_clear_object(&call->payload.add_interface.port); g_clear_object(&call->payload.add_interface.interface); g_clear_object(&call->payload.add_interface.bridge_device); g_clear_object(&call->payload.add_interface.interface_device); break; case OVSDB_DEL_INTERFACE: nm_clear_g_free(&call->payload.del_interface.ifname); break; case OVSDB_SET_INTERFACE_MTU: nm_clear_g_free(&call->payload.set_interface_mtu.ifname); break; case OVSDB_SET_REAPPLY: nm_clear_g_free(&call->payload.set_reapply.ifname); nm_clear_g_object(&call->payload.set_reapply.connection); nm_clear_pointer(&call->payload.set_reapply.external_ids_old, g_hash_table_destroy); nm_clear_pointer(&call->payload.set_reapply.external_ids_new, g_hash_table_destroy); nm_clear_pointer(&call->payload.set_reapply.other_config_old, g_hash_table_destroy); nm_clear_pointer(&call->payload.set_reapply.other_config_new, g_hash_table_destroy); break; } nm_g_slice_free(call); } /*****************************************************************************/ static void _free_bridge(OpenvswitchBridge *ovs_bridge) { g_free(ovs_bridge->bridge_uuid); g_free(ovs_bridge->name); g_free(ovs_bridge->connection_uuid); g_ptr_array_free(ovs_bridge->ports, TRUE); nm_g_array_unref(ovs_bridge->external_ids); nm_g_array_unref(ovs_bridge->other_config); nm_g_slice_free(ovs_bridge); } static void _free_port(OpenvswitchPort *ovs_port) { g_free(ovs_port->port_uuid); g_free(ovs_port->name); g_free(ovs_port->connection_uuid); g_ptr_array_free(ovs_port->interfaces, TRUE); nm_g_array_unref(ovs_port->external_ids); nm_g_array_unref(ovs_port->other_config); nm_g_slice_free(ovs_port); } static void _free_interface(OpenvswitchInterface *ovs_interface) { g_free(ovs_interface->interface_uuid); g_free(ovs_interface->name); g_free(ovs_interface->connection_uuid); g_free(ovs_interface->type); nm_g_array_unref(ovs_interface->external_ids); nm_g_array_unref(ovs_interface->other_config); nm_g_slice_free(ovs_interface); } /*****************************************************************************/ static void _signal_emit_device_added(NMOvsdb *self, const char *name, NMDeviceType device_type, const char *device_subtype) { g_signal_emit(self, signals[DEVICE_ADDED], 0, name, (guint) device_type, device_subtype); } static void _signal_emit_device_removed(NMOvsdb *self, const char *name, NMDeviceType device_type, const char *device_subtype) { g_signal_emit(self, signals[DEVICE_REMOVED], 0, name, (guint) device_type, device_subtype); } static void _signal_emit_interface_failed(NMOvsdb *self, const char *name, const char *connection_uuid, const char *error) { g_signal_emit(self, signals[INTERFACE_FAILED], 0, name, connection_uuid, error); } /*****************************************************************************/ /** * 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(NMOvsdb *self, OvsdbMethodCallback callback, gpointer user_data, gboolean add_first, OvsdbCommand command, const OvsdbMethodPayload *payload) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); OvsdbMethodCall *call; /* FIXME(shutdown): this function should accept a cancellable to * interrupt the operation. */ /* Ensure we're not unsynchronized before we queue the method call. */ ovsdb_try_connect(self); call = g_slice_new(OvsdbMethodCall); *call = (OvsdbMethodCall) { .self = self, .call_id = CALL_ID_UNSPEC, .command = command, .callback = callback, .user_data = user_data, .shutdown_wait_obj = g_object_new(G_TYPE_OBJECT, NULL), }; nm_shutdown_wait_obj_register_object(call->shutdown_wait_obj, "ovsdb-call"); if (add_first) c_list_link_front(&priv->calls_lst_head, &call->calls_lst); else c_list_link_tail(&priv->calls_lst_head, &call->calls_lst); /* Migrate the arguments from @payload to @call->payload. Technically, * this is not a plain copy, because * - call->payload is not initialized (thus no need to free the previous data). * - payload does not own the data. It is merely initialized using the * OVSDB_METHOD_PAYLOAD_*() macros. */ switch (command) { case OVSDB_MONITOR: _LOGT_call(call, "new: monitor"); break; case OVSDB_ADD_INTERFACE: /* FIXME(applied-connection-immutable): we should not modify the applied * connection, consequently there is no need to clone the connections. */ call->payload.add_interface.bridge = nm_simple_connection_new_clone(payload->add_interface.bridge); call->payload.add_interface.port = nm_simple_connection_new_clone(payload->add_interface.port); call->payload.add_interface.interface = nm_simple_connection_new_clone(payload->add_interface.interface); call->payload.add_interface.bridge_device = g_object_ref(payload->add_interface.bridge_device); call->payload.add_interface.interface_device = g_object_ref(payload->add_interface.interface_device); _LOGT_call(call, "new: add-interface bridge=%s port=%s interface=%s", nm_connection_get_interface_name(call->payload.add_interface.bridge), nm_connection_get_interface_name(call->payload.add_interface.port), nm_device_get_iface(call->payload.add_interface.interface_device)); break; case OVSDB_DEL_INTERFACE: call->payload.del_interface.ifname = g_strdup(payload->del_interface.ifname); _LOGT_call(call, "new: del-interface interface=%s", call->payload.del_interface.ifname); break; case OVSDB_SET_INTERFACE_MTU: call->payload.set_interface_mtu.ifname = g_strdup(payload->set_interface_mtu.ifname); call->payload.set_interface_mtu.mtu = payload->set_interface_mtu.mtu; _LOGT_call(call, "new: set-interface-mtu interface=%s mtu=%u", call->payload.set_interface_mtu.ifname, call->payload.set_interface_mtu.mtu); break; case OVSDB_SET_REAPPLY: call->payload.set_reapply.device_type = payload->set_reapply.device_type; call->payload.set_reapply.ifname = g_strdup(payload->set_reapply.ifname); call->payload.set_reapply.connection = payload->set_reapply.connection; call->payload.set_reapply.external_ids_old = nm_g_hash_table_ref(payload->set_reapply.external_ids_old); call->payload.set_reapply.external_ids_new = nm_g_hash_table_ref(payload->set_reapply.external_ids_new); call->payload.set_reapply.other_config_old = nm_g_hash_table_ref(payload->set_reapply.other_config_old); call->payload.set_reapply.other_config_new = nm_g_hash_table_ref(payload->set_reapply.other_config_new); _LOGT_call(call, "new: reapply con-uuid=%s, interface=%s", nm_connection_get_uuid(payload->set_reapply.connection), call->payload.set_reapply.ifname); break; } ovsdb_next_command(self); } /*****************************************************************************/ /* Create and process the JSON-RPC messages from ovsdb. */ /** * _expect_ovs_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 void _expect_ovs_bridges(json_t *params, const char *db_uuid, json_t *bridges) { json_array_append_new( params, 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_ovs_bridges: * * Return a command that will update the list of bridges in @db_uuid * database to @new_bridges. */ static void _set_ovs_bridges(json_t *params, const char *db_uuid, json_t *new_bridges) { json_array_append_new(params, json_pack("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, [s, s]]]}", "op", "update", "table", "Open_vSwitch", "row", "bridges", "set", new_bridges, "where", "_uuid", "==", "uuid", db_uuid)); } /** * _expect_bridge_ports: * * Return a command that will fail the transaction if the actual set of * ports in bridge @ifname 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 void _expect_bridge_ports(json_t *params, const char *ifname, json_t *ports) { json_array_append_new(params, 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", "==", ifname)); } /** * _set_bridge_ports: * * Return a command that will update the list of ports of bridge * @ifname to @new_ports. */ static void _set_bridge_ports(json_t *params, const char *ifname, json_t *new_ports) { json_array_append_new(params, json_pack("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, s]]}", "op", "update", "table", "Bridge", "row", "ports", "set", new_ports, "where", "name", "==", ifname)); } static void _set_bridge_mac(json_t *params, const char *ifname, const char *mac) { json_array_append_new( params, json_pack("{s:s, s:s, s:[[s, s, [s, [s]]], [s, s, [s, [[s, s]]]]], s:[[s, s, s]]}", "op", "mutate", "table", "Bridge", "mutations", "other_config", "delete", "set", OTHER_CONFIG_HWADDR, "other_config", "insert", "map", OTHER_CONFIG_HWADDR, mac, "where", "name", "==", ifname)); } /** * _expect_port_interfaces: * * Return a command that will fail the transaction if the actual set of * interfaces in port @ifname doesn't match @interfaces. This is a way of * detecting race conditions with other ovsdb clients that might be adding * or removing port interfaces at the same time. */ static void _expect_port_interfaces(json_t *params, const char *ifname, json_t *interfaces) { json_array_append_new(params, json_pack("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, s]]}", "op", "wait", "table", "Port", "timeout", 0, "columns", "interfaces", "until", "==", "rows", "interfaces", "set", interfaces, "where", "name", "==", ifname)); } /** * _set_port_interfaces: * * Return a command that will update the list of interfaces of port @ifname * to @new_interfaces. */ static void _set_port_interfaces(json_t *params, const char *ifname, json_t *new_interfaces) { json_array_append_new(params, json_pack("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, s]]}", "op", "update", "table", "Port", "row", "interfaces", "set", new_interfaces, "where", "name", "==", ifname)); } static json_t * _j_create_strdict_new(NMConnection *connection, StrdictType strdict_type, const char *other_config_hwaddr) { NMSettingOvsOtherConfig *s_other_config = NULL; NMSettingOvsExternalIDs *s_external_ids = NULL; json_t *array; const char *const *strv = NULL; guint n_strv = 0; guint i; const char *uuid; nm_assert(NM_IS_CONNECTION(connection)); nm_assert(NM_IN_SET(strdict_type, STRDICT_TYPE_EXTERNAL_IDS, STRDICT_TYPE_OTHER_CONFIG)); array = json_array(); if (strdict_type == STRDICT_TYPE_EXTERNAL_IDS) { uuid = nm_connection_get_uuid(connection); nm_assert(uuid); json_array_append_new(array, json_pack("[s, s]", NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID, uuid)); } else { if (other_config_hwaddr) { json_array_append_new(array, json_pack("[s, s]", OTHER_CONFIG_HWADDR, other_config_hwaddr)); } } if (strdict_type == STRDICT_TYPE_EXTERNAL_IDS) { s_external_ids = _nm_connection_get_setting(connection, NM_TYPE_SETTING_OVS_EXTERNAL_IDS); if (s_external_ids) strv = nm_setting_ovs_external_ids_get_data_keys(s_external_ids, &n_strv); } else { s_other_config = _nm_connection_get_setting(connection, NM_TYPE_SETTING_OVS_OTHER_CONFIG); if (s_other_config) strv = nm_setting_ovs_other_config_get_data_keys(s_other_config, &n_strv); } for (i = 0; i < n_strv; i++) { const char *k = strv[i]; if (strdict_type == STRDICT_TYPE_OTHER_CONFIG && other_config_hwaddr && nm_streq(k, OTHER_CONFIG_HWADDR)) { /* "hwaddr" is explicitly overwritten. */ continue; } json_array_append_new( array, json_pack("[s, s]", k, strdict_type == STRDICT_TYPE_EXTERNAL_IDS ? nm_setting_ovs_external_ids_get_data(s_external_ids, k) : nm_setting_ovs_other_config_get_data(s_other_config, k))); } return json_pack("[s, o]", "map", array); } static void _j_create_strv_array_update(json_t *mutations, StrdictType strdict_type, const char *connection_uuid, GHashTable *hash_old, GHashTable *hash_new) { GHashTableIter iter; json_t *array; const char *key; const char *val; /* This is called during reapply. We accept reapplying all settings, * except other_config:hwaddr. That one cannot change and is specially * handled below. The reason is that we knew the correct "hwaddr" during * _j_create_strdict_new(), but we don't do now. At least not easily, * and it's not clear that reapply of the MAC address is really useful. */ nm_assert((!!connection_uuid) == (strdict_type == STRDICT_TYPE_EXTERNAL_IDS)); nm_assert(NM_IN_SET(strdict_type, STRDICT_TYPE_EXTERNAL_IDS, STRDICT_TYPE_OTHER_CONFIG)); array = NULL; if (hash_old) { g_hash_table_iter_init(&iter, hash_old); while (g_hash_table_iter_next(&iter, (gpointer *) &key, NULL)) { if (strdict_type == STRDICT_TYPE_OTHER_CONFIG && nm_streq(key, OTHER_CONFIG_HWADDR)) continue; if (!array) array = json_array(); json_array_append_new(array, json_string(key)); } } if (hash_new) { g_hash_table_iter_init(&iter, hash_new); while (g_hash_table_iter_next(&iter, (gpointer *) &key, NULL)) { if (strdict_type == STRDICT_TYPE_OTHER_CONFIG && nm_streq(key, OTHER_CONFIG_HWADDR)) continue; if (nm_g_hash_table_contains(hash_old, key)) continue; if (!array) array = json_array(); json_array_append_new(array, json_string(key)); } } if (strdict_type == STRDICT_TYPE_EXTERNAL_IDS) { if (!nm_g_hash_table_contains(hash_old, NM_OVS_EXTERNAL_ID_NM_PREFIX) && !nm_g_hash_table_contains(hash_new, NM_OVS_EXTERNAL_ID_NM_PREFIX)) { if (!array) array = json_array(); json_array_append_new(array, json_string(NM_OVS_EXTERNAL_ID_NM_PREFIX)); } } if (array) { json_array_append_new( mutations, json_pack("[s, s, [s, o]]", strdict_type == STRDICT_TYPE_EXTERNAL_IDS ? "external_ids" : "other_config", "delete", "set", array)); } array = json_array(); if (strdict_type == STRDICT_TYPE_EXTERNAL_IDS) { json_array_append_new( array, json_pack("[s, s]", NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID, connection_uuid)); } if (hash_new) { g_hash_table_iter_init(&iter, hash_new); while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) { if (strdict_type == STRDICT_TYPE_EXTERNAL_IDS) { if (NM_STR_HAS_PREFIX(key, NM_OVS_EXTERNAL_ID_NM_PREFIX)) continue; } if (strdict_type == STRDICT_TYPE_OTHER_CONFIG && nm_streq(key, OTHER_CONFIG_HWADDR)) continue; json_array_append_new(array, json_pack("[s, s]", key, val)); } } json_array_append_new( mutations, json_pack("[s, s, [s, o]]", strdict_type == STRDICT_TYPE_EXTERNAL_IDS ? "external_ids" : "other_config", "insert", "map", array)); } /** * _insert_interface: * * Returns a command that adds new interface from a given connection. */ static void _insert_interface(json_t *params, NMConnection *interface, NMDevice *interface_device, const char *cloned_mac) { const char *type = NULL; NMSettingOvsInterface *s_ovs_iface; NMSettingOvsDpdk *s_ovs_dpdk; char sbuf[64]; json_t *dpdk_array; NMSettingOvsPatch *s_ovs_patch; json_t *options = json_array(); json_t *row; guint32 mtu = 0; guint32 ofport_request = 0; s_ovs_iface = nm_connection_get_setting_ovs_interface(interface); if (s_ovs_iface) { type = nm_setting_ovs_interface_get_interface_type(s_ovs_iface); ofport_request = nm_setting_ovs_interface_get_ofport_request(s_ovs_iface); } if (nm_streq0(type, "internal")) { NMSettingWired *s_wired; s_wired = _nm_connection_get_setting(interface, NM_TYPE_SETTING_WIRED); if (s_wired) mtu = nm_setting_wired_get_mtu(s_wired); } json_array_append_new(options, json_string("map")); s_ovs_dpdk = (NMSettingOvsDpdk *) nm_connection_get_setting(interface, NM_TYPE_SETTING_OVS_DPDK); if (!s_ovs_dpdk) s_ovs_patch = nm_connection_get_setting_ovs_patch(interface); if (s_ovs_dpdk) { const char *devargs; guint32 n_rxq; guint32 n_rxq_desc; guint32 n_txq_desc; NMSettingOvsDpdkLscInterrupt lsc_int; devargs = nm_setting_ovs_dpdk_get_devargs(s_ovs_dpdk); n_rxq = nm_setting_ovs_dpdk_get_n_rxq(s_ovs_dpdk); n_rxq_desc = nm_setting_ovs_dpdk_get_n_rxq_desc(s_ovs_dpdk); n_txq_desc = nm_setting_ovs_dpdk_get_n_txq_desc(s_ovs_dpdk); lsc_int = nm_setting_ovs_dpdk_get_lsc_interrupt(s_ovs_dpdk); dpdk_array = json_array(); if (devargs) json_array_append_new(dpdk_array, json_pack("[s,s]", "dpdk-devargs", devargs)); if (n_rxq != 0) { json_array_append_new(dpdk_array, json_pack("[s,s]", "n_rxq", nm_sprintf_buf(sbuf, "%u", n_rxq))); } if (n_rxq_desc != 0) { json_array_append_new( dpdk_array, json_pack("[s,s]", "n_rxq_desc", nm_sprintf_buf(sbuf, "%u", n_rxq_desc))); } if (n_txq_desc != 0) { json_array_append_new( dpdk_array, json_pack("[s,s]", "n_txq_desc", nm_sprintf_buf(sbuf, "%u", n_txq_desc))); } switch (lsc_int) { case NM_SETTING_OVS_DPDK_LSC_INTERRUPT_IGNORE: break; case NM_SETTING_OVS_DPDK_LSC_INTERRUPT_ENABLED: json_array_append_new(dpdk_array, json_pack("[s,s]", "dpdk-lsc-interrupt", "true")); break; case NM_SETTING_OVS_DPDK_LSC_INTERRUPT_DISABLED: json_array_append_new(dpdk_array, json_pack("[s,s]", "dpdk-lsc-interrupt", "false")); break; } json_array_append_new(options, dpdk_array); } else if (s_ovs_patch) { json_array_append_new( options, json_pack("[[s, s]]", "peer", nm_setting_ovs_patch_get_peer(s_ovs_patch))); } else { json_array_append_new(options, json_array()); } row = json_pack("{s:s, s:s, s:o, s:o, s:o}", "name", nm_device_get_iface(interface_device), "type", type ?: "", "options", options, "external_ids", _j_create_strdict_new(interface, STRDICT_TYPE_EXTERNAL_IDS, NULL), "other_config", _j_create_strdict_new(interface, STRDICT_TYPE_OTHER_CONFIG, NULL)); if (cloned_mac) json_object_set_new(row, "mac", json_string(cloned_mac)); if (mtu != 0) json_object_set_new(row, "mtu_request", json_integer(mtu)); if (ofport_request != 0) json_object_set_new(row, "ofport_request", json_integer(ofport_request)); json_array_append_new(params, json_pack("{s:s, s:s, s:o, s:s}", "op", "insert", "table", "Interface", "row", row, "uuid-name", "rowInterface")); } static void ovsdb_row_set_string_or_null(json_t *row, const char *key, const char *str) { /* ovsdb represents a NULL string (no value) as an empty set */ if (str) { json_object_set_new(row, key, json_string(str)); } else { json_object_set_new(row, key, json_pack("[s, []]", "set")); } } static json_t * create_port_row_object(NMConnection *connection) { NMSettingOvsPort *s_ovs_port; json_t *row; const char *s; guint u; s_ovs_port = nm_connection_get_setting_ovs_port(connection); nm_assert(s_ovs_port); row = json_object(); s = nm_setting_ovs_port_get_vlan_mode(s_ovs_port); ovsdb_row_set_string_or_null(row, "vlan_mode", s); u = nm_setting_ovs_port_get_tag(s_ovs_port); json_object_set_new(row, "tag", u != 0 ? json_integer(u) : json_pack("[s, []]", "set")); u = nm_setting_ovs_port_get_bond_updelay(s_ovs_port); json_object_set_new(row, "bond_updelay", json_integer(u)); u = nm_setting_ovs_port_get_bond_downdelay(s_ovs_port); json_object_set_new(row, "bond_downdelay", json_integer(u)); s = nm_setting_ovs_port_get_lacp(s_ovs_port); ovsdb_row_set_string_or_null(row, "lacp", s); s = nm_setting_ovs_port_get_bond_mode(s_ovs_port); ovsdb_row_set_string_or_null(row, "bond_mode", s); { const GPtrArray *ranges; json_t *trunks = json_array(); guint64 start; guint64 end; guint i; ranges = _nm_setting_ovs_port_get_trunks_arr(s_ovs_port); for (i = 0; i < ranges->len; i++) { nm_range_get_range(ranges->pdata[i], &start, &end); for (; start <= end; start++) json_array_append_new(trunks, json_integer(start)); } json_object_set_new(row, "trunks", json_pack("[s, o]", "set", trunks)); } return row; } /** * _insert_port: * * Returns a command that adds new port from a given connection. */ static void _insert_port(json_t *params, NMConnection *port, json_t *new_interfaces) { json_t *row; row = create_port_row_object(port); json_object_set_new(row, "name", json_string(nm_connection_get_interface_name(port))); json_object_set_new(row, "interfaces", json_pack("[s, O]", "set", new_interfaces)); json_object_set_new(row, "external_ids", _j_create_strdict_new(port, STRDICT_TYPE_EXTERNAL_IDS, NULL)); json_object_set_new(row, "other_config", _j_create_strdict_new(port, STRDICT_TYPE_OTHER_CONFIG, NULL)); /* Create a new one. */ json_array_append_new(params, json_pack("{s:s, s:s, s:o, s:s}", "op", "insert", "table", "Port", "row", row, "uuid-name", "rowPort")); } static json_t * create_bridge_row_object(NMConnection *connection, gboolean is_reapply) { NMSettingOvsBridge *s_ovs_bridge; json_t *row; gboolean b; const char *s; s_ovs_bridge = nm_connection_get_setting_ovs_bridge(connection); nm_assert(s_ovs_bridge); row = json_object(); b = nm_setting_ovs_bridge_get_mcast_snooping_enable(s_ovs_bridge); json_object_set_new(row, "mcast_snooping_enable", json_boolean(b)); b = nm_setting_ovs_bridge_get_rstp_enable(s_ovs_bridge); json_object_set_new(row, "rstp_enable", json_boolean(b)); b = nm_setting_ovs_bridge_get_stp_enable(s_ovs_bridge); json_object_set_new(row, "stp_enable", json_boolean(b)); s = nm_setting_ovs_bridge_get_fail_mode(s_ovs_bridge); ovsdb_row_set_string_or_null(row, "fail_mode", s); if (!is_reapply) { /* The datapath type can't be reapplied because after changing it, * ovs removes the existing ovs-interface and creates a tun one (or * vice-versa). */ s = nm_setting_ovs_bridge_get_datapath_type(s_ovs_bridge); if (s) { /* Cannot use ovsdb_row_set_string_or_null() here as the column * is a set and must not be empty. */ json_object_set_new(row, "datapath_type", json_string(s)); } } return row; } /** * _insert_bridge: * * Returns a command that adds new bridge from a given connection. */ static void _insert_bridge(json_t *params, NMConnection *bridge, NMDevice *bridge_device, json_t *new_ports, const char *cloned_mac) { json_t *row; row = create_bridge_row_object(bridge, FALSE); json_object_set_new(row, "name", json_string(nm_connection_get_interface_name(bridge))); json_object_set_new(row, "ports", json_pack("[s, O]", "set", new_ports)); json_object_set_new(row, "external_ids", _j_create_strdict_new(bridge, STRDICT_TYPE_EXTERNAL_IDS, NULL)); json_object_set_new(row, "other_config", _j_create_strdict_new(bridge, STRDICT_TYPE_OTHER_CONFIG, cloned_mac)); /* Create a new one. */ json_array_append_new(params, json_pack("{s:s, s:s, s:o, s:s}", "op", "insert", "table", "Bridge", "row", row, "uuid-name", "rowBridge")); } /** * _inc_next_cfg: * * Returns an mutate command 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); } /** * _add_interface: * * Adds an interface as specified by @interface connection, optionally creating * a parent @port and @bridge if needed. */ static void _add_interface(NMOvsdb *self, json_t *params, NMConnection *bridge, NMConnection *port, NMConnection *interface, NMDevice *bridge_device, NMDevice *interface_device) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); GHashTableIter iter; const char *port_uuid; const char *interface_uuid; const char *bridge_name; const char *port_name; const char *interface_name; OpenvswitchBridge *ovs_bridge = NULL; OpenvswitchPort *ovs_port = NULL; OpenvswitchInterface *ovs_interface = NULL; nm_auto_decref_json json_t *bridges = NULL; nm_auto_decref_json json_t *new_bridges = NULL; nm_auto_decref_json json_t *ports = NULL; nm_auto_decref_json json_t *new_ports = NULL; nm_auto_decref_json json_t *interfaces = NULL; nm_auto_decref_json json_t *new_interfaces = NULL; gboolean has_interface = FALSE; gboolean interface_is_local; gs_free char *bridge_cloned_mac = NULL; gs_free char *interface_cloned_mac = NULL; GError *error = NULL; int pi; int ii; bridges = json_array(); ports = json_array(); interfaces = json_array(); new_bridges = json_array(); new_ports = json_array(); new_interfaces = json_array(); bridge_name = nm_connection_get_interface_name(bridge); port_name = nm_connection_get_interface_name(port); interface_name = nm_device_get_iface(interface_device); interface_is_local = nm_streq0(bridge_name, interface_name); /* Determine cloned MAC addresses */ if (!nm_device_hw_addr_get_cloned(bridge_device, bridge, FALSE, &bridge_cloned_mac, NULL, &error)) { _LOGW("Cannot determine cloned MAC for OVS %s '%s': %s", "bridge", bridge_name, error->message); g_clear_error(&error); } if (!nm_device_hw_addr_get_cloned(interface_device, interface, FALSE, &interface_cloned_mac, NULL, &error)) { _LOGW("Cannot determine cloned MAC for OVS %s '%s': %s", "interface", interface_name, error->message); g_clear_error(&error); } /* For local interfaces, ovs complains if it finds a * MAC address in the Interface table because it only takes * the MAC from the Bridge table. * Set any cloned MAC present in a local interface connection * into the Bridge table, unless conflicting with the bridge MAC. */ if (interface_is_local && interface_cloned_mac) { if (bridge_cloned_mac && !nm_streq(interface_cloned_mac, bridge_cloned_mac)) { _LOGW("Cloned MAC '%s' of local ovs-interface '%s' conflicts with MAC '%s' of bridge " "'%s'", interface_cloned_mac, interface_name, bridge_cloned_mac, bridge_name); nm_clear_g_free(&interface_cloned_mac); } else { nm_clear_g_free(&bridge_cloned_mac); bridge_cloned_mac = g_steal_pointer(&interface_cloned_mac); _LOGT("'%s' is a local ovs-interface, the MAC will be set on ovs-bridge '%s'", interface_name, bridge_name); } } g_hash_table_iter_init(&iter, priv->bridges); while (g_hash_table_iter_next(&iter, (gpointer) &ovs_bridge, NULL)) { json_array_append_new(bridges, json_pack("[s, s]", "uuid", ovs_bridge->bridge_uuid)); if (!nm_streq0(ovs_bridge->name, bridge_name) || !nm_streq0(ovs_bridge->connection_uuid, nm_connection_get_uuid(bridge))) continue; for (pi = 0; pi < ovs_bridge->ports->len; pi++) { port_uuid = g_ptr_array_index(ovs_bridge->ports, pi); ovs_port = g_hash_table_lookup(priv->ports, &port_uuid); json_array_append_new(ports, json_pack("[s, s]", "uuid", port_uuid)); if (!ovs_port) { /* This would be a violation of ovsdb's reference integrity (a bug). */ _LOGW("Unknown port '%s' in bridge '%s'", port_uuid, ovs_bridge->bridge_uuid); continue; } if (!nm_streq(ovs_port->name, port_name) || !nm_streq0(ovs_port->connection_uuid, nm_connection_get_uuid(port))) continue; for (ii = 0; ii < ovs_port->interfaces->len; ii++) { interface_uuid = g_ptr_array_index(ovs_port->interfaces, ii); ovs_interface = g_hash_table_lookup(priv->interfaces, &interface_uuid); json_array_append_new(interfaces, json_pack("[s, s]", "uuid", interface_uuid)); if (!ovs_interface) { /* This would be a violation of ovsdb's reference integrity (a bug). */ _LOGW("Unknown interface '%s' in port '%s'", interface_uuid, port_uuid); continue; } if (nm_streq(ovs_interface->name, interface_name) && nm_streq0(ovs_interface->connection_uuid, nm_connection_get_uuid(interface))) has_interface = TRUE; } break; } break; } json_array_extend(new_bridges, bridges); json_array_extend(new_ports, ports); json_array_extend(new_interfaces, interfaces); if (json_array_size(interfaces) == 0) { /* Need to create a port. */ if (json_array_size(ports) == 0) { /* Need to create a bridge. */ _expect_ovs_bridges(params, priv->db_uuid, bridges); json_array_append_new(new_bridges, json_pack("[s, s]", "named-uuid", "rowBridge")); _set_ovs_bridges(params, priv->db_uuid, new_bridges); _insert_bridge(params, bridge, bridge_device, new_ports, bridge_cloned_mac); } else { /* Bridge already exists. */ g_return_if_fail(ovs_bridge); _expect_bridge_ports(params, ovs_bridge->name, ports); _set_bridge_ports(params, bridge_name, new_ports); if (bridge_cloned_mac && interface_is_local) _set_bridge_mac(params, bridge_name, bridge_cloned_mac); } json_array_append_new(new_ports, json_pack("[s, s]", "named-uuid", "rowPort")); _insert_port(params, port, new_interfaces); } else { /* Port already exists */ g_return_if_fail(ovs_port); _expect_port_interfaces(params, ovs_port->name, interfaces); _set_port_interfaces(params, port_name, new_interfaces); } if (!has_interface) { _insert_interface(params, interface, interface_device, interface_cloned_mac); json_array_append_new(new_interfaces, json_pack("[s, s]", "named-uuid", "rowInterface")); } } /** * _delete_interface: * * Removes an interface of @ifname name, collecting empty ports and bridge * if last item is removed from them. */ static void _delete_interface(NMOvsdb *self, json_t *params, const char *ifname) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); GHashTableIter iter; char *port_uuid; char *interface_uuid; OpenvswitchBridge *ovs_bridge; OpenvswitchPort *ovs_port; OpenvswitchInterface *ovs_interface; nm_auto_decref_json json_t *bridges = NULL; nm_auto_decref_json json_t *new_bridges = NULL; gboolean bridges_changed; bridges = json_array(); new_bridges = json_array(); bridges_changed = FALSE; /* Loop over all bridges */ g_hash_table_iter_init(&iter, priv->bridges); while (g_hash_table_iter_next(&iter, (gpointer) &ovs_bridge, NULL)) { nm_auto_decref_json json_t *ports = NULL; nm_auto_decref_json json_t *new_ports = NULL; guint num_nm_ports = 0; gboolean ports_changed = FALSE; int pi; ports = json_array(); new_ports = json_array(); /* Add the bridge UUID to the list of known bridges for the "expect" condition */ json_array_append_new(bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid)); if (!ovs_bridge->connection_uuid) { /* Externally created, don't touch it */ json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid)); continue; } /* Loop over all bridge's ports */ for (pi = 0; pi < ovs_bridge->ports->len; pi++) { nm_auto_decref_json json_t *interfaces = NULL; nm_auto_decref_json json_t *new_interfaces = NULL; guint num_nm_interfaces = 0; gboolean interfaces_changed = FALSE; int ii; interfaces = json_array(); new_interfaces = json_array(); port_uuid = g_ptr_array_index(ovs_bridge->ports, pi); ovs_port = g_hash_table_lookup(priv->ports, &port_uuid); /* Add the port UUID to the list of known bridge port for the "expect" condition */ json_array_append_new(ports, json_pack("[s,s]", "uuid", port_uuid)); if (!ovs_port) { /* This would be a violation of ovsdb's reference integrity (a bug). */ _LOGW("Unknown port '%s' in bridge '%s'", port_uuid, ovs_bridge->bridge_uuid); continue; } /* Loop over all port's interfaces */ for (ii = 0; ii < ovs_port->interfaces->len; ii++) { interface_uuid = g_ptr_array_index(ovs_port->interfaces, ii); ovs_interface = g_hash_table_lookup(priv->interfaces, &interface_uuid); /* Add the interface UUID to the list of known port interfaces for the "expect" condition */ json_array_append_new(interfaces, json_pack("[s,s]", "uuid", interface_uuid)); if (ovs_interface) { if (nm_streq(ovs_interface->name, ifname)) { /* We are deleting this interface, don't count it */ interfaces_changed = TRUE; continue; } if (ovs_interface->connection_uuid) num_nm_interfaces++; } else { /* This would be a violation of ovsdb's reference integrity (a bug). */ _LOGW("Unknown interface '%s' in port '%s'", interface_uuid, port_uuid); } /* Add the interface to the list of new interfaces to set on the port */ json_array_append_new(new_interfaces, json_pack("[s,s]", "uuid", interface_uuid)); } if (interfaces_changed && num_nm_interfaces == 0) { /* We are deleting the last nm-interface of this port. Don't add it to "new_ports" * and set ports_changed=TRUE, so that it will be deleted. */ ports_changed = TRUE; } else { /* Keep this port: it's still alive, or it's unrelated to the deleted interface */ json_array_append_new(new_ports, json_pack("[s,s]", "uuid", port_uuid)); if (ovs_port->connection_uuid) num_nm_ports++; if (interfaces_changed) { /* This port is still alive, but an interface needs to be deleted from it */ _expect_port_interfaces(params, ovs_port->name, interfaces); _set_port_interfaces(params, ovs_port->name, new_interfaces); } } } if (ports_changed && num_nm_ports == 0) { /* We are deleting the last nm-port of this bridge. Don't add it to "new_bridges" * and set bridges_changed=TRUE, so that it will be deleted. */ bridges_changed = TRUE; } else { /* Keep this bridge: it's still alive, or it's unrelated to the deleted interface */ json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid)); if (ports_changed) { /* This bridge is still alive, but a port needs to be deleted from it */ _expect_bridge_ports(params, ovs_bridge->name, ports); _set_bridge_ports(params, ovs_bridge->name, new_ports); } } } if (bridges_changed) { /* A bridge needs to be deleted */ _expect_ovs_bridges(params, priv->db_uuid, bridges); _set_ovs_bridges(params, priv->db_uuid, new_bridges); } } /** * 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(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); OvsdbMethodCall *call; nm_auto_free char *cmd = NULL; nm_auto_decref_json json_t *msg = NULL; if (priv->conn_fd < 0) return; if (c_list_is_empty(&priv->calls_lst_head)) return; call = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst); if (call->call_id != CALL_ID_UNSPEC) return; call->call_id = ++priv->call_id_counter; 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:[{s:[s, s, s, s, s]}]," " s:[{s:[]}]" "}]}", "id", (json_int_t) call->call_id, "method", "monitor", "params", "Open_vSwitch", "Bridge", "columns", "name", "ports", "external_ids", "other_config", "Port", "columns", "name", "interfaces", "external_ids", "other_config", "Interface", "columns", "name", "type", "external_ids", "other_config", "error", "Open_vSwitch", "columns"); break; default: { json_t *params = NULL; params = json_array(); json_array_append_new(params, json_string("Open_vSwitch")); json_array_append_new(params, _inc_next_cfg(priv->db_uuid)); switch (call->command) { case OVSDB_ADD_INTERFACE: _add_interface(self, params, call->payload.add_interface.bridge, call->payload.add_interface.port, call->payload.add_interface.interface, call->payload.add_interface.bridge_device, call->payload.add_interface.interface_device); break; case OVSDB_DEL_INTERFACE: _delete_interface(self, params, call->payload.del_interface.ifname); break; case OVSDB_SET_INTERFACE_MTU: json_array_append_new(params, json_pack("{s:s, s:s, s:{s: I}, s:[[s, s, s]]}", "op", "update", "table", "Interface", "row", "mtu_request", (json_int_t) call->payload.set_interface_mtu.mtu, "where", "name", "==", call->payload.set_interface_mtu.ifname)); break; case OVSDB_SET_REAPPLY: { NMConnection *connection; json_t *mutations; json_t *row; const char *table; connection = call->payload.set_reapply.connection; table = _device_type_to_table(call->payload.set_reapply.device_type); /* Reapply device properties */ switch (call->payload.set_reapply.device_type) { case NM_DEVICE_TYPE_OVS_BRIDGE: row = create_bridge_row_object(connection, TRUE); break; case NM_DEVICE_TYPE_OVS_PORT: row = create_port_row_object(connection); break; default: row = NULL; break; } if (row) { json_array_append_new(params, json_pack("{s:s, s:s, s:o, s:[[s, s, s]]}", "op", "update", "table", table, "row", row, "where", "name", "==", call->payload.set_reapply.ifname)); } /* Reapply external-ids and other-config */ mutations = json_array(); _j_create_strv_array_update(mutations, STRDICT_TYPE_EXTERNAL_IDS, nm_connection_get_uuid(connection), call->payload.set_reapply.external_ids_old, call->payload.set_reapply.external_ids_new); _j_create_strv_array_update(mutations, STRDICT_TYPE_OTHER_CONFIG, NULL, call->payload.set_reapply.other_config_old, call->payload.set_reapply.other_config_new); json_array_append_new(params, json_pack("{s:s, s:s, s:o, s:[[s, s, s]]}", "op", "mutate", "table", table, "mutations", mutations, "where", "name", "==", call->payload.set_reapply.ifname)); break; } default: nm_assert_not_reached(); break; } msg = json_pack("{s:I, s:s, s:o}", "id", (json_int_t) call->call_id, "method", "transact", "params", params); break; } } g_return_if_fail(msg); cmd = json_dumps(msg, 0); _LOGT_call(call, "send: call-id=%" G_GUINT64_FORMAT ", %s", call->call_id, cmd); nm_str_buf_append(&priv->output_buf, cmd); ovsdb_write_try(self); } /** * _uuids_to_array: * * This tidies up the somewhat non-straightforward way ovsdb represents an array * of UUID elements. The single element is a tuple (called in RFC7047), * * [ "uuid", "aa095ffb-e1f1-0fc4-8038-82c1ea7e4797" ] * * while the list of multiple UUIDs are turned into a set of such tuples ("atoms"): * * [ "set", [ [ "uuid", "aa095ffb-e1f1-0fc4-8038-82c1ea7e4797" ], * [ "uuid", "185c93f6-0b39-424e-8587-77d074aa7ce0" ], ... ] ] */ static void _uuids_to_array_inplace(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 || !key) return; if (nm_streq(key, "uuid")) { if (json_is_string(value)) g_ptr_array_add(array, g_strdup(json_string_value(value))); continue; } if (nm_streq(key, "set")) { if (json_is_array(value)) { json_array_foreach (value, set_index, set_value) _uuids_to_array_inplace(array, set_value); } continue; } } } static GPtrArray * _uuids_to_array(const json_t *items) { GPtrArray *array; array = g_ptr_array_new_with_free_func(g_free); _uuids_to_array_inplace(array, items); return array; } static void _strdict_extract(json_t *strdict, GArray **out_array) { json_t *array; json_t *value; gsize index; nm_assert(out_array && !*out_array); if (!nm_streq0("map", json_string_value(json_array_get(strdict, 0)))) return; array = json_array_get(strdict, 1); json_array_foreach (array, index, value) { const char *key = json_string_value(json_array_get(value, 0)); const char *val = json_string_value(json_array_get(value, 1)); NMUtilsNamedValue *v; if (!key || !val) continue; if (!*out_array) { *out_array = g_array_new(FALSE, FALSE, sizeof(NMUtilsNamedValue)); g_array_set_clear_func(*out_array, (GDestroyNotify) nm_utils_named_value_clear_with_g_free); } v = nm_g_array_append_new(*out_array, NMUtilsNamedValue); *v = (NMUtilsNamedValue) { .name = g_strdup(key), .value_str = g_strdup(val), }; } } static const char * _strdict_find_key(GArray *array, const char *key) { gssize idx; idx = nm_utils_named_value_list_find(nm_g_array_first_p(array, NMUtilsNamedValue), nm_g_array_len(array), key, FALSE); if (idx < 0) return NULL; return nm_g_array_index(array, NMUtilsNamedValue, idx).value_str; } static gboolean _strdict_equals(const GArray *arr1, const GArray *arr2) { guint n; guint i; n = nm_g_array_len(arr1); if (n != nm_g_array_len(arr2)) return FALSE; for (i = 0; i < n; i++) { const NMUtilsNamedValue *n1 = &nm_g_array_index(arr1, NMUtilsNamedValue, i); const NMUtilsNamedValue *n2 = &nm_g_array_index(arr2, NMUtilsNamedValue, i); if (!nm_streq(n1->name, n2->name)) return FALSE; if (!nm_streq(n1->value_str, n2->value_str)) return FALSE; } return TRUE; } static char * _strdict_to_string(const GArray *arr) { NMStrBuf strbuf; guint i; if (!arr) return g_strdup("empty"); strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE); nm_str_buf_append(&strbuf, "["); for (i = 0; i < arr->len; i++) { const NMUtilsNamedValue *n = &nm_g_array_index(arr, NMUtilsNamedValue, i); if (i > 0) nm_str_buf_append_c(&strbuf, ','); nm_str_buf_append_printf(&strbuf, " \"%s\" = \"%s\" ", n->name, n->value_str); } nm_str_buf_append(&strbuf, "]"); return nm_str_buf_finalize(&strbuf, NULL); } /*****************************************************************************/ /** * 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(NMOvsdb *self, json_t *msg) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); json_t *ovs = NULL; json_t *bridge = NULL; json_t *port = NULL; json_t *interface = NULL; json_t *items; json_t *external_ids; json_t *other_config; json_error_t json_error = { 0, }; void *iter; const char *name; const char *key; const char *type; json_t *value; 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("monitor: bad update: %s", json_error.text); } if (ovs) { const char *s; iter = json_object_iter(ovs); s = json_object_iter_key(iter); if (s) nm_strdup_reset(&priv->db_uuid, s); } json_object_foreach (interface, key, value) { OpenvswitchInterface *ovs_interface; gs_unref_array GArray *external_ids_arr = NULL; gs_unref_array GArray *other_config_arr = NULL; const char *connection_uuid = NULL; json_t *error = NULL; int r; r = json_unpack(value, "{s:{s:s, s:s, s?:o, s:o, s:o}}", "new", "name", &name, "type", &type, "error", &error, "external_ids", &external_ids, "other_config", &other_config); if (r != 0) { gpointer unused; r = json_unpack(value, "{s:{}}", "old"); if (r != 0) continue; if (!g_hash_table_steal_extended(priv->interfaces, &key, (gpointer *) &ovs_interface, &unused)) continue; _LOGT("monitor: %s: interface removed: type=%s, obj[iface:%s]%s%s", ovs_interface->name, ovs_interface->type, key, NM_PRINT_FMT_QUOTED2(ovs_interface->connection_uuid, ", connection=", ovs_interface->connection_uuid, "")); _signal_emit_device_removed(self, ovs_interface->name, NM_DEVICE_TYPE_OVS_INTERFACE, ovs_interface->type); _free_interface(ovs_interface); continue; } ovs_interface = g_hash_table_lookup(priv->interfaces, &key); if (ovs_interface && (!nm_streq0(ovs_interface->name, name) || !nm_streq0(ovs_interface->type, type))) { if (!g_hash_table_steal(priv->interfaces, ovs_interface)) nm_assert_not_reached(); _signal_emit_device_removed(self, ovs_interface->name, NM_DEVICE_TYPE_OVS_INTERFACE, ovs_interface->type); nm_clear_pointer(&ovs_interface, _free_interface); } _strdict_extract(external_ids, &external_ids_arr); connection_uuid = _strdict_find_key(external_ids_arr, NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID); _strdict_extract(other_config, &other_config_arr); if (ovs_interface) { gboolean changed = FALSE; nm_assert(nm_streq0(ovs_interface->name, name)); changed |= nm_strdup_reset(&ovs_interface->type, type); changed |= nm_strdup_reset(&ovs_interface->connection_uuid, connection_uuid); if (!_strdict_equals(ovs_interface->external_ids, external_ids_arr)) { NM_SWAP(&ovs_interface->external_ids, &external_ids_arr); changed = TRUE; } if (!_strdict_equals(ovs_interface->other_config, other_config_arr)) { NM_SWAP(&ovs_interface->other_config, &other_config_arr); changed = TRUE; } if (changed) { gs_free char *strtmp1 = NULL; gs_free char *strtmp2 = NULL; _LOGT( "monitor: %s: interface changed: type=%s, obj[iface:%s]%s%s, external-ids=%s, " "other-config=%s", ovs_interface->name, type, key, NM_PRINT_FMT_QUOTED2(ovs_interface->connection_uuid, ", connection=", ovs_interface->connection_uuid, ""), (strtmp1 = _strdict_to_string(ovs_interface->external_ids)), (strtmp2 = _strdict_to_string(ovs_interface->other_config))); } } else { gs_free char *strtmp1 = NULL; gs_free char *strtmp2 = NULL; ovs_interface = g_slice_new(OpenvswitchInterface); *ovs_interface = (OpenvswitchInterface) { .interface_uuid = g_strdup(key), .name = g_strdup(name), .type = g_strdup(type), .connection_uuid = g_strdup(connection_uuid), .external_ids = g_steal_pointer(&external_ids_arr), .other_config = g_steal_pointer(&other_config_arr), }; g_hash_table_add(priv->interfaces, ovs_interface); _LOGT("monitor: %s: interface added: type=%s, obj[iface:%s]%s%s, external-ids=%s, " "other-config=%s", ovs_interface->name, ovs_interface->type, key, NM_PRINT_FMT_QUOTED2(ovs_interface->connection_uuid, ", connection=", ovs_interface->connection_uuid, ""), (strtmp1 = _strdict_to_string(ovs_interface->external_ids)), (strtmp2 = _strdict_to_string(ovs_interface->other_config))); _signal_emit_device_added(self, ovs_interface->name, NM_DEVICE_TYPE_OVS_INTERFACE, ovs_interface->type); } /* The error is a string. No error is indicated by an empty set, * Why not: [ "set": [] ] ? */ if (error && json_is_string(error)) { _signal_emit_interface_failed(self, ovs_interface->name, ovs_interface->connection_uuid, json_string_value(error)); } } json_object_foreach (port, key, value) { gs_unref_ptrarray GPtrArray *interfaces = NULL; OpenvswitchPort *ovs_port; gs_unref_array GArray *external_ids_arr = NULL; gs_unref_array GArray *other_config_arr = NULL; const char *connection_uuid = NULL; int r; r = json_unpack(value, "{s:{s:s, s:o, s:o, s:o}}", "new", "name", &name, "external_ids", &external_ids, "other_config", &other_config, "interfaces", &items); if (r != 0) { gpointer unused; r = json_unpack(value, "{s:{}}", "old"); if (r != 0) continue; if (!g_hash_table_steal_extended(priv->ports, &key, (gpointer *) &ovs_port, &unused)) continue; _LOGT("monitor: %s: port removed: obj[port:%s]%s%s", ovs_port->name, key, NM_PRINT_FMT_QUOTED2(ovs_port->connection_uuid, ", connection=", ovs_port->connection_uuid, "")); _signal_emit_device_removed(self, ovs_port->name, NM_DEVICE_TYPE_OVS_PORT, NULL); _free_port(ovs_port); continue; } ovs_port = g_hash_table_lookup(priv->ports, &key); if (ovs_port && !nm_streq0(ovs_port->name, name)) { if (!g_hash_table_steal(priv->ports, ovs_port)) nm_assert_not_reached(); _signal_emit_device_removed(self, ovs_port->name, NM_DEVICE_TYPE_OVS_PORT, NULL); nm_clear_pointer(&ovs_port, _free_port); } _strdict_extract(external_ids, &external_ids_arr); connection_uuid = _strdict_find_key(external_ids_arr, NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID); _strdict_extract(other_config, &other_config_arr); interfaces = _uuids_to_array(items); if (ovs_port) { gboolean changed = FALSE; nm_assert(nm_streq0(ovs_port->name, name)); changed |= nm_strdup_reset(&ovs_port->name, name); changed |= nm_strdup_reset(&ovs_port->connection_uuid, connection_uuid); if (nm_strv_ptrarray_cmp(ovs_port->interfaces, interfaces) != 0) { NM_SWAP(&ovs_port->interfaces, &interfaces); changed = TRUE; } if (!_strdict_equals(ovs_port->external_ids, external_ids_arr)) { NM_SWAP(&ovs_port->external_ids, &external_ids_arr); changed = TRUE; } if (!_strdict_equals(ovs_port->other_config, other_config_arr)) { NM_SWAP(&ovs_port->other_config, &other_config_arr); changed = TRUE; } if (changed) { gs_free char *strtmp1 = NULL; gs_free char *strtmp2 = NULL; _LOGT( "monitor: %s: port changed: obj[port:%s]%s%s, external-ids=%s, other-config=%s", ovs_port->name, key, NM_PRINT_FMT_QUOTED2(ovs_port->connection_uuid, ", connection=", ovs_port->connection_uuid, ""), (strtmp1 = _strdict_to_string(ovs_port->external_ids)), (strtmp2 = _strdict_to_string(ovs_port->other_config))); } } else { gs_free char *strtmp1 = NULL; gs_free char *strtmp2 = NULL; ovs_port = g_slice_new(OpenvswitchPort); *ovs_port = (OpenvswitchPort) { .port_uuid = g_strdup(key), .name = g_strdup(name), .connection_uuid = g_strdup(connection_uuid), .interfaces = g_steal_pointer(&interfaces), .external_ids = g_steal_pointer(&external_ids_arr), .other_config = g_steal_pointer(&other_config_arr), }; g_hash_table_add(priv->ports, ovs_port); _LOGT("monitor: %s: port added: obj[port:%s]%s%s, external-ids=%s, other-config=%s", ovs_port->name, key, NM_PRINT_FMT_QUOTED2(ovs_port->connection_uuid, ", connection=", ovs_port->connection_uuid, ""), (strtmp1 = _strdict_to_string(ovs_port->external_ids)), (strtmp2 = _strdict_to_string(ovs_port->other_config))); _signal_emit_device_added(self, ovs_port->name, NM_DEVICE_TYPE_OVS_PORT, NULL); } } json_object_foreach (bridge, key, value) { gs_unref_ptrarray GPtrArray *ports = NULL; OpenvswitchBridge *ovs_bridge; gs_unref_array GArray *external_ids_arr = NULL; gs_unref_array GArray *other_config_arr = NULL; const char *connection_uuid = NULL; int r; r = json_unpack(value, "{s:{s:s, s:o, s:o, s:o}}", "new", "name", &name, "external_ids", &external_ids, "other_config", &other_config, "ports", &items); if (r != 0) { gpointer unused; r = json_unpack(value, "{s:{}}", "old"); if (r != 0) continue; if (!g_hash_table_steal_extended(priv->bridges, &key, (gpointer *) &ovs_bridge, &unused)) continue; _LOGT("monitor: %s: bridge removed: obj[bridge:%s]%s%s", ovs_bridge->name, key, NM_PRINT_FMT_QUOTED2(ovs_bridge->connection_uuid, ", connection=", ovs_bridge->connection_uuid, "")); _signal_emit_device_removed(self, ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE, NULL); _free_bridge(ovs_bridge); continue; } ovs_bridge = g_hash_table_lookup(priv->bridges, &key); if (ovs_bridge && !nm_streq0(ovs_bridge->name, name)) { if (!g_hash_table_steal(priv->bridges, ovs_bridge)) nm_assert_not_reached(); _signal_emit_device_removed(self, ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE, NULL); nm_clear_pointer(&ovs_bridge, _free_bridge); } _strdict_extract(external_ids, &external_ids_arr); connection_uuid = _strdict_find_key(external_ids_arr, NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID); _strdict_extract(other_config, &other_config_arr); ports = _uuids_to_array(items); if (ovs_bridge) { gboolean changed = FALSE; nm_assert(nm_streq0(ovs_bridge->name, name)); changed = nm_strdup_reset(&ovs_bridge->name, name); changed = nm_strdup_reset(&ovs_bridge->connection_uuid, connection_uuid); if (nm_strv_ptrarray_cmp(ovs_bridge->ports, ports) != 0) { NM_SWAP(&ovs_bridge->ports, &ports); changed = TRUE; } if (!_strdict_equals(ovs_bridge->external_ids, external_ids_arr)) { NM_SWAP(&ovs_bridge->external_ids, &external_ids_arr); changed = TRUE; } if (!_strdict_equals(ovs_bridge->other_config, other_config_arr)) { NM_SWAP(&ovs_bridge->other_config, &other_config_arr); changed = TRUE; } if (changed) { gs_free char *strtmp1 = NULL; gs_free char *strtmp2 = NULL; _LOGT("monitor: %s: bridge changed: obj[bridge:%s]%s%s, external-ids=%s, " "other-config=%s", ovs_bridge->name, key, NM_PRINT_FMT_QUOTED2(ovs_bridge->connection_uuid, ", connection=", ovs_bridge->connection_uuid, ""), (strtmp1 = _strdict_to_string(ovs_bridge->external_ids)), (strtmp2 = _strdict_to_string(ovs_bridge->other_config))); } } else { gs_free char *strtmp1 = NULL; gs_free char *strtmp2 = NULL; ovs_bridge = g_slice_new(OpenvswitchBridge); *ovs_bridge = (OpenvswitchBridge) { .bridge_uuid = g_strdup(key), .name = g_strdup(name), .connection_uuid = g_strdup(connection_uuid), .ports = g_steal_pointer(&ports), .external_ids = g_steal_pointer(&external_ids_arr), .other_config = g_steal_pointer(&other_config_arr), }; g_hash_table_add(priv->bridges, ovs_bridge); _LOGT("monitor: %s: bridge added: obj[bridge:%s]%s%s, external-ids=%s, other-config=%s", ovs_bridge->name, key, NM_PRINT_FMT_QUOTED2(ovs_bridge->connection_uuid, ", connection=", ovs_bridge->connection_uuid, ""), (strtmp1 = _strdict_to_string(ovs_bridge->external_ids)), (strtmp2 = _strdict_to_string(ovs_bridge->other_config))); _signal_emit_device_added(self, ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE, NULL); } } } /** * ovsdb_got_echo: * * Only implemented because the specification mandates it. Actual ovsdb hasn't been * seen doing this. */ static void ovsdb_got_echo(NMOvsdb *self, json_int_t id, json_t *data) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); nm_auto_decref_json json_t *msg = NULL; nm_auto_free char *reply = NULL; msg = json_pack("{s:I, s:O}", "id", id, "result", data); reply = json_dumps(msg, 0); _LOGT("send: echo: %s", reply); nm_str_buf_append(&priv->output_buf, reply); ovsdb_write_try(self); } /** * ovsdb_got_msg:: * * Called when a complete JSON object was seen and unmarshalled. * Either finishes a method call or processes a method call. */ static void ovsdb_got_msg(NMOvsdb *self, json_t *msg) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); json_error_t json_error = { 0, }; json_t *json_id = NULL; json_int_t id = (json_int_t) -1; const char *method = NULL; json_t *params = NULL; json_t *result = NULL; json_t *error = NULL; if (json_unpack_ex(msg, &json_error, 0, "{s?:o, s?:s, s?:o, s?:o, s?:o}", "id", &json_id, "method", &method, "params", ¶ms, "result", &result, "error", &error) == -1) { _LOGW("couldn't grok the message: %s", json_error.text); ovsdb_disconnect(self, FALSE, FALSE); return; } if (json_is_number(json_id)) id = json_integer_value(json_id); if (method) { /* It's a method call! */ if (!params) { _LOGW("a method call with no params: '%s'", method); ovsdb_disconnect(self, FALSE, FALSE); return; } if (nm_streq0(method, "update")) { /* This is a update method call. */ ovsdb_got_update(self, json_array_get(params, 1)); } else if (nm_streq0(method, "echo")) { /* This is an echo request. */ ovsdb_got_echo(self, id, params); } else { _LOGW("got an unknown method call: '%s'", method); } return; } if (id >= 0) { OvsdbMethodCall *call; gs_free_error GError *local = NULL; gs_free char *msg_as_str = NULL; /* This is a response to a method call. */ if (c_list_is_empty(&priv->calls_lst_head)) { _LOGW("there are no queued calls expecting response %" G_GUINT64_FORMAT, (guint64) id); ovsdb_disconnect(self, FALSE, FALSE); return; } call = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst); if (call->call_id != id) { _LOGW("expected a response to call %" G_GUINT64_FORMAT ", not %" G_GUINT64_FORMAT, call->call_id, (guint64) id); ovsdb_disconnect(self, FALSE, FALSE); return; } /* Cool, we found a corresponding call. Finish it. */ _LOGT_call(call, "response: %s", (msg_as_str = json_dumps(msg, 0))); if (!json_is_null(error)) { /* The response contains an error. */ g_set_error(&local, G_IO_ERROR, G_IO_ERROR_FAILED, "Error call to OVSDB returned an error: %s", json_string_value(error)); } _call_complete(call, result, local); priv->num_failures = 0; /* Don't progress further commands in case the callback hit an error * and disconnected us. */ if (priv->conn_fd < 0) return; /* Now we're free to serialize and send the next command, if any. */ ovsdb_next_command(self); return; } /* This is a message we are not interested in. */ _LOGW("got an unknown message, ignoring"); } /*****************************************************************************/ typedef struct { gsize bufp; NMStrBuf *input; } JsonReadMsgData; /* Lower level marshalling and demarshalling of the JSON-RPC traffic on the * ovsdb socket. */ static size_t _json_read_msg_cb(void *buffer, size_t buflen, void *user_data) { JsonReadMsgData *data = user_data; nm_assert(buffer); nm_assert(buflen > 0); if (data->bufp == data->input->len) { /* No more bytes buffered for decoding. */ return 0; } /* Pass one more byte to the JSON decoder. */ *(char *) buffer = nm_str_buf_get_char(data->input, data->bufp); data->bufp++; return 1; } static json_t * _json_read_msg(NMOvsdb *self, NMStrBuf *input) { gs_free char *ss = NULL; JsonReadMsgData data = { .bufp = 0, .input = input, }; json_error_t json_error = { 0, }; json_t *msg; /* 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_read_msg_cb, &data, JSON_DISABLE_EOF_CHECK, &json_error); if (!msg) return NULL; nm_assert(data.bufp > 0); _LOGT("json: parse %zu bytes: \"%s\"", data.bufp, (ss = g_strndup(nm_str_buf_get_str_at_unsafe(input, 0), data.bufp))); nm_str_buf_erase(input, 0, data.bufp, FALSE); return msg; } static gboolean _ovsdb_read_input_timeout_cb(gpointer user_data) { NMOvsdb *self = user_data; NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); _LOGW("invalid/incomplete data in receive buffer. Reset"); priv->num_failures++; ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE); return G_SOURCE_CONTINUE; } static void ovsdb_read(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); gssize size; again: size = nm_utils_fd_read(priv->conn_fd, &priv->input_buf); if (size <= 0) { if (size == -EAGAIN) { if (priv->input_buf.len == 0) nm_clear_g_source_inst(&priv->input_timeout_source); else if (!priv->input_timeout_source) { /* We have data in the buffer but nothing further to read. Schedule a timer, * if we don't get the rest within timeout, it means that the buffer * content is broken (_json_read_msg() cannot extract any data) and * we disconnect. */ priv->input_timeout_source = nm_g_timeout_add_seconds_source(5, _ovsdb_read_input_timeout_cb, self); } return; } /* ovsdb-server was possibly restarted */ _LOGW("short read from ovsdb: %s", nm_strerror_native(-size)); priv->num_failures++; ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE); return; } nm_assert(priv->input_buf.len > 0); while (TRUE) { nm_auto_decref_json json_t *msg = NULL; msg = _json_read_msg(self, &priv->input_buf); if (!msg) break; nm_clear_g_source_inst(&priv->input_timeout_source); ovsdb_got_msg(self, msg); if (priv->input_buf.len == 0) break; } if (priv->input_buf.len > 0) { if (priv->input_buf.len > 50 * 1024 * 1024) { _LOGW("received too much data from ovsdb that is not valid JSON"); priv->num_failures++; ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE); return; } /* We have an incomplete message in the message buffer. Don't wait for another round * of "poll", instead try to read it again. */ goto again; } nm_clear_g_source_inst(&priv->input_timeout_source); } static gboolean ovsdb_read_cb(int fd, GIOCondition condition, gpointer user_data) { ovsdb_read(user_data); return G_SOURCE_CONTINUE; } static void ovsdb_write(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); gssize n; again: if (priv->output_buf.len == 0) { nm_clear_g_source_inst(&priv->conn_fd_out_source); return; } n = write(priv->conn_fd, nm_str_buf_get_str_at_unsafe(&priv->output_buf, 0), priv->output_buf.len); if (n < 0) n = -NM_ERRNO_NATIVE(errno); if (n == -EAGAIN) { if (!priv->conn_fd_out_source) { priv->conn_fd_out_source = nm_g_unix_fd_add_source(priv->conn_fd, G_IO_OUT, ovsdb_write_cb, self); } return; } if (n <= 0) { /* ovsdb-server was possibly restarted */ _LOGW("short write to ovsdb: %s", nm_strerror_native(-n)); priv->num_failures++; ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE); return; } nm_str_buf_erase(&priv->output_buf, 0, n, FALSE); goto again; } static void ovsdb_write_try(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); if (priv->conn_fd >= 0 && !priv->conn_fd_out_source) ovsdb_write(self); } static gboolean ovsdb_write_cb(int fd, GIOCondition condition, gpointer user_data) { ovsdb_write(user_data); return G_SOURCE_CONTINUE; } /*****************************************************************************/ /* 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(NMOvsdb *self, gboolean retry, gboolean is_disposing) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); OvsdbMethodCall *call; nm_assert(!retry || !is_disposing); if (priv->conn_fd < 0 && !priv->conn_cancellable) return; _LOGD("disconnecting from ovsdb, retry %d", retry); /* FIXME(shutdown): NMOvsdb should process the pending calls before * shutting down, and cancel the remaining calls after the timeout. */ if (retry) { if (!c_list_is_empty(&priv->calls_lst_head)) { call = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst); call->call_id = CALL_ID_UNSPEC; } } else { gs_free_error GError *error = NULL; if (is_disposing) nm_utils_error_set_cancelled(&error, is_disposing, "NMOvsdb"); else nm_utils_error_set(&error, NM_UTILS_ERROR_NOT_READY, "disconnected from ovsdb"); while ((call = c_list_last_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst))) _call_complete(call, NULL, error); } nm_str_buf_reset(&priv->input_buf); nm_str_buf_reset(&priv->output_buf); nm_clear_fd(&priv->conn_fd); nm_clear_g_source_inst(&priv->conn_fd_in_source); nm_clear_g_source_inst(&priv->conn_fd_out_source); nm_clear_g_source_inst(&priv->input_timeout_source); nm_clear_g_free(&priv->db_uuid); nm_clear_g_cancellable(&priv->conn_cancellable); if (retry) ovsdb_try_connect(self); } static void cleanup_emit_ready(NMOvsdb *self, const char *reason) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); _LOGT("cleanup: ready (%s)", reason); nm_clear_pointer(&priv->cleanup.interfaces, g_ptr_array_unref); nm_clear_g_source_inst(&priv->cleanup.timeout_source); nm_clear_g_signal_handler(priv->platform, &priv->cleanup.link_changed_id); priv->ready = TRUE; g_signal_emit(self, signals[READY], 0); nm_manager_unblock_failed_ovs_interfaces(nm_manager_get()); } static gboolean cleanup_timeout(NMOvsdb *self) { cleanup_emit_ready(self, "timeout"); return G_SOURCE_CONTINUE; } static void cleanup_link_cb(NMPlatform *platform, int obj_type_i, int ifindex, NMPlatformLink *plink, int change_type_i, gpointer user_data) { const NMPlatformSignalChangeType change_type = change_type_i; if (change_type != NM_PLATFORM_SIGNAL_REMOVED) return; cleanup_check_ready(user_data); } static void cleanup_check_ready(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); guint i = 0; nm_assert(!priv->ready); if (priv->cleanup.num_pending_del > 0) return; /* After we have deleted an interface from ovsdb, the link will stay * in platform until ovs-vswitch removes it. To avoid race conditions, * we need to wait until the link goes away; otherwise, after adding the * interface again, these race conditions can happen: * 1) we see the link in platform, and proceed with activation. But after * that, ovs-vswitchd reads the updates from ovsdb-server and deletes/recreates * the link. * 2) ovs-vswitch combines the delete/insert of the interface to a no-op. NM sees * the link staying in platform, but doesn't know whether the link is ready * or we are again in case 1) * In other words, it's necessary to wait that the link goes away before inserting * the interface again. */ while (i < nm_g_ptr_array_len(priv->cleanup.interfaces)) { const char *ifname; const NMDedupMultiHeadEntry *pl_links_head_entry; NMDedupMultiIter pliter; const NMPlatformLink *link; gboolean found = FALSE; ifname = priv->cleanup.interfaces->pdata[i]; pl_links_head_entry = nm_platform_lookup_link_by_ifname(priv->platform, ifname); nmp_cache_iter_for_each_link (&pliter, pl_links_head_entry, &link) { if (link->type == NM_LINK_TYPE_OPENVSWITCH && nmp_object_is_visible(NMP_OBJECT_UP_CAST(link))) { found = TRUE; break; } } if (!found) { g_ptr_array_remove_index_fast(priv->cleanup.interfaces, i); continue; } i++; } if (nm_g_ptr_array_len(priv->cleanup.interfaces) == 0) { cleanup_emit_ready(self, "all interfaces deleted"); return; } _LOGT("cleanup: still waiting for %d interfaces", priv->cleanup.interfaces->len); if (priv->cleanup.timeout_source) { /* We already registered the timeout/change-callback */ return; } priv->cleanup.timeout_source = nm_g_timeout_add_seconds_source(6, G_SOURCE_FUNC(cleanup_timeout), self); priv->cleanup.link_changed_id = g_signal_connect(priv->platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK(cleanup_link_cb), self); } static void cleanup_del_iface_cb(GError *error, gpointer user_data) { NMOvsdb *self; gs_free char *ifname = NULL; NMOvsdbPrivate *priv; nm_utils_user_data_unpack(user_data, &self, &ifname); if (nm_utils_error_is_cancelled_or_disposing(error)) return; priv = NM_OVSDB_GET_PRIVATE(self); nm_assert(priv->cleanup.num_pending_del > 0); priv->cleanup.num_pending_del--; _LOGD("cleanup: deleted interface '%s': %s %s%s%s, pending %u", ifname, error ? "error" : "success", error ? "(" : "", error ? error->message : "", error ? ")" : "", priv->cleanup.num_pending_del); cleanup_check_ready(self); } static void ovsdb_cleanup_initial_interfaces(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); const OpenvswitchInterface *interface; NMUtilsUserData *data; GHashTableIter iter; if (priv->ready || priv->cleanup.num_pending_del > 0 || priv->cleanup.interfaces) return; /* Delete OVS interfaces added by NM. Bridges and ports and * not considered because they are deleted automatically * when no interface is present. */ g_hash_table_iter_init(&iter, self->_priv.interfaces); while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &interface)) { if (!interface->connection_uuid) { /* not created by NM, ignore */ continue; } if (!priv->cleanup.interfaces) priv->cleanup.interfaces = g_ptr_array_new_with_free_func(g_free); g_ptr_array_add(priv->cleanup.interfaces, g_strdup(interface->name)); _LOGD("cleanup: deleting interface '%s'", interface->name); priv->cleanup.num_pending_del++; data = nm_utils_user_data_pack(self, g_strdup(interface->name)); nm_ovsdb_del_interface(self, interface->name, cleanup_del_iface_cb, data); } cleanup_check_ready(self); } static void _monitor_bridges_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_data) { if (error) { if (!nm_utils_error_is_cancelled_or_disposing(error)) { _LOGI("%s", error->message); ovsdb_disconnect(self, FALSE, FALSE); } return; } /* Treat the first response the same as the subsequent "update" * messages we eventually get. */ ovsdb_got_update(self, result); ovsdb_cleanup_initial_interfaces(self); } static void _ovsdb_connect_complete_with_fd(NMOvsdb *self, int fd_take) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); gs_unref_object GSocket *socket = NULL; gs_free_error GError *error = NULL; nm_clear_g_cancellable(&priv->conn_cancellable); nm_io_fcntl_setfl_update_nonblock(fd_take); priv->conn_fd = nm_steal_fd(&fd_take); priv->conn_fd_in_source = nm_g_unix_fd_add_source(priv->conn_fd, G_IO_IN, ovsdb_read_cb, self); ovsdb_read(self); ovsdb_next_command(self); } static void _ovsdb_connect_priv_helper_cb(int fd_take, GError *error, gpointer user_data) { nm_auto_close int fd = fd_take; NMOvsdb *self; if (nm_utils_error_is_cancelled(error)) return; self = user_data; if (error) { _LOGT("connect: failure to get FD from nm-priv-helper: %s", error->message); ovsdb_disconnect(self, FALSE, FALSE); return; } _LOGT("connect: connected successfully with FD from nm-priv-helper"); _ovsdb_connect_complete_with_fd(self, nm_steal_fd(&fd)); } static void _ovsdb_connect_idle(gpointer user_data, GCancellable *cancellable) { NMOvsdb *self; NMOvsdbPrivate *priv; nm_auto_close int fd = -1; gs_free_error GError *error = NULL; if (g_cancellable_is_cancelled(cancellable)) return; self = user_data; priv = NM_OVSDB_GET_PRIVATE(self); fd = nm_priv_helper_utils_open_fd(NM_PRIV_HELPER_GET_FD_TYPE_OVSDB_SOCKET, &error); if (fd == -ENOENT) { _LOGT("connect: opening %s failed (\"%s\")", NM_OVSDB_SOCKET, error->message); ovsdb_disconnect(self, FALSE, FALSE); return; } if (fd < 0) { _LOGT("connect: opening %s failed (\"%s\"). Retry with nm-priv-helper", NM_OVSDB_SOCKET, error->message); nm_priv_helper_call_get_fd(NM_PRIV_HELPER_GET_FD_TYPE_OVSDB_SOCKET, priv->conn_cancellable, _ovsdb_connect_priv_helper_cb, self); return; } _LOGT("connect: opening %s succeeded", NM_OVSDB_SOCKET); _ovsdb_connect_complete_with_fd(self, nm_steal_fd(&fd)); } /** * 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(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); if (priv->conn_fd >= 0 || priv->conn_cancellable) return; _LOGT("connect: start connecting socket %s on idle", NM_OVSDB_SOCKET); priv->conn_cancellable = g_cancellable_new(); nm_utils_invoke_on_idle(priv->conn_cancellable, _ovsdb_connect_idle, self); /* 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, _monitor_bridges_cb, NULL, TRUE, OVSDB_MONITOR, OVSDB_METHOD_PAYLOAD_MONITOR()); } /*****************************************************************************/ /* Public functions useful for NMDeviceOpenvswitch to maintain the life cycle of * their ovsdb entries without having to deal with ovsdb complexities themselves. */ typedef struct { NMOvsdbCallback callback; gpointer user_data; } OvsdbCall; static void _transact_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_data) { OvsdbCall *call = user_data; gs_free_error GError *local = NULL; const char *err; const char *err_details; size_t index; json_t *value; if (!error) { json_array_foreach (result, index, value) { if (json_unpack(value, "{s:s, s:s}", "error", &err, "details", &err_details) == 0) { local = g_error_new(G_IO_ERROR, G_IO_ERROR_FAILED, "Error running the transaction: %s: %s", err, err_details); break; } } } call->callback(local ?: error, call->user_data); nm_g_slice_free(call); } static OvsdbCall * ovsdb_call_new(NMOvsdbCallback callback, gpointer user_data) { OvsdbCall *call; call = g_slice_new(OvsdbCall); *call = (OvsdbCall) { .callback = callback, .user_data = user_data, }; return call; } gboolean nm_ovsdb_is_ready(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); return priv->ready; } void nm_ovsdb_add_interface(NMOvsdb *self, NMConnection *bridge, NMConnection *port, NMConnection *interface, NMDevice *bridge_device, NMDevice *interface_device, NMOvsdbCallback callback, gpointer user_data) { ovsdb_call_method(self, _transact_cb, ovsdb_call_new(callback, user_data), FALSE, OVSDB_ADD_INTERFACE, OVSDB_METHOD_PAYLOAD_ADD_INTERFACE(bridge, port, interface, bridge_device, interface_device)); } void nm_ovsdb_del_interface(NMOvsdb *self, const char *ifname, NMOvsdbCallback callback, gpointer user_data) { ovsdb_call_method(self, _transact_cb, ovsdb_call_new(callback, user_data), FALSE, OVSDB_DEL_INTERFACE, OVSDB_METHOD_PAYLOAD_DEL_INTERFACE(ifname)); } void nm_ovsdb_set_interface_mtu(NMOvsdb *self, const char *ifname, guint32 mtu, NMOvsdbCallback callback, gpointer user_data) { ovsdb_call_method(self, _transact_cb, ovsdb_call_new(callback, user_data), FALSE, OVSDB_SET_INTERFACE_MTU, OVSDB_METHOD_PAYLOAD_SET_INTERFACE_MTU(ifname, mtu)); } void nm_ovsdb_set_reapply(NMOvsdb *self, NMDeviceType device_type, const char *ifname, NMConnection *connection_take, NMSettingOvsExternalIDs *s_external_ids_old, NMSettingOvsExternalIDs *s_external_ids_new, NMSettingOvsOtherConfig *s_other_config_old, NMSettingOvsOtherConfig *s_other_config_new) { gs_unref_hashtable GHashTable *external_ids_old = NULL; gs_unref_hashtable GHashTable *external_ids_new = NULL; gs_unref_hashtable GHashTable *other_config_old = NULL; gs_unref_hashtable GHashTable *other_config_new = NULL; nm_assert(NM_IN_SET(device_type, NM_DEVICE_TYPE_OVS_BRIDGE, NM_DEVICE_TYPE_OVS_PORT, NM_DEVICE_TYPE_OVS_INTERFACE)); external_ids_old = s_external_ids_old ? nm_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_external_ids_old)) : NULL; external_ids_new = s_external_ids_new ? nm_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_external_ids_new)) : NULL; other_config_old = s_other_config_old ? nm_strdict_clone(_nm_setting_ovs_other_config_get_data(s_other_config_old)) : NULL; other_config_new = s_other_config_new ? nm_strdict_clone(_nm_setting_ovs_other_config_get_data(s_other_config_new)) : NULL; ovsdb_call_method(self, NULL, NULL, FALSE, OVSDB_SET_REAPPLY, OVSDB_METHOD_PAYLOAD_SET_REAPPLY(device_type, ifname, connection_take, external_ids_old, external_ids_new, other_config_old, other_config_new)); } /*****************************************************************************/ static void nm_ovsdb_init(NMOvsdb *self) { NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); priv->conn_fd = -1; priv->input_buf = NM_STR_BUF_INIT(0, FALSE); priv->output_buf = NM_STR_BUF_INIT(0, FALSE); c_list_init(&priv->calls_lst_head); priv->platform = g_object_ref(NM_PLATFORM_GET); priv->bridges = g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_bridge, NULL); priv->ports = g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_port, NULL); priv->interfaces = g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_interface, NULL); ovsdb_try_connect(self); } static void dispose(GObject *object) { NMOvsdb *self = NM_OVSDB(object); NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self); ovsdb_disconnect(self, FALSE, TRUE); nm_assert(c_list_is_empty(&priv->calls_lst_head)); nm_str_buf_destroy(&priv->input_buf); nm_str_buf_destroy(&priv->output_buf); g_clear_object(&priv->platform); nm_clear_pointer(&priv->bridges, g_hash_table_destroy); nm_clear_pointer(&priv->ports, g_hash_table_destroy); nm_clear_pointer(&priv->interfaces, g_hash_table_destroy); G_OBJECT_CLASS(nm_ovsdb_parent_class)->dispose(object); } static void nm_ovsdb_class_init(NMOvsdbClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->dispose = dispose; signals[DEVICE_ADDED] = g_signal_new(NM_OVSDB_DEVICE_ADDED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING); signals[DEVICE_REMOVED] = g_signal_new(NM_OVSDB_DEVICE_REMOVED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING); signals[INTERFACE_FAILED] = g_signal_new(NM_OVSDB_INTERFACE_FAILED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); signals[READY] = g_signal_new(NM_OVSDB_READY, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); }