/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2017 Red Hat, Inc. */ #include "src/core/nm-default-daemon.h" #include "nm-device-ovs-port.h" #include "nm-device-ovs-interface.h" #include "nm-device-ovs-bridge.h" #include "nm-ovsdb.h" #include "devices/nm-device-private.h" #include "nm-active-connection.h" #include "nm-setting-connection.h" #include "nm-setting-ovs-port.h" #include "nm-setting-ovs-interface.h" #include "nm-setting-ovs-external-ids.h" #include "nm-setting-ovs-other-config.h" #include "nm-setting-wired.h" #define _NMLOG_DEVICE_TYPE NMDeviceOvsPort #include "devices/nm-device-logging.h" /*****************************************************************************/ struct _NMDeviceOvsPort { NMDevice parent; }; struct _NMDeviceOvsPortClass { NMDeviceClass parent; }; G_DEFINE_TYPE(NMDeviceOvsPort, nm_device_ovs_port, NM_TYPE_DEVICE) /*****************************************************************************/ static const char * get_type_description(NMDevice *device) { return "ovs-port"; } static gboolean create_and_realize(NMDevice *device, NMConnection *connection, NMDevice *parent, const NMPlatformLink **out_plink, GError **error) { /* The port will be added to ovsdb when an interface is attached as port, * because there's no such thing like an empty port. */ return TRUE; } static NMDeviceCapabilities get_generic_capabilities(NMDevice *device) { return NM_DEVICE_CAP_IS_SOFTWARE; } static gboolean ready_for_ip_config(NMDevice *device, gboolean is_manual) { return FALSE; } static void act_stage3_ip_config(NMDevice *device, int addr_family) { nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, NULL); } typedef struct { NMDevice *device; NMDevice *port; GCancellable *cancellable; NMDeviceAttachPortCallback callback; gpointer callback_user_data; gboolean add; } AttachPortData; static void add_del_iface_cb(GError *error, gpointer user_data) { AttachPortData *data = user_data; NMDeviceOvsPort *self; gs_free_error GError *local = NULL; if (g_cancellable_is_cancelled(data->cancellable)) { local = nm_utils_error_new_cancelled(FALSE, NULL); error = local; } else if (error && !nm_utils_error_is_cancelled_or_disposing(error)) { self = NM_DEVICE_OVS_PORT(data->device); _LOGW(LOGD_DEVICE, "device %s could not be %s a ovs port: %s", nm_device_get_iface(data->port), data->add ? "added to" : "removed from", error->message); nm_device_state_changed(data->port, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_OVSDB_FAILED); } if (data->callback) data->callback(data->device, error, data->callback_user_data); g_object_unref(data->device); g_object_unref(data->port); nm_clear_g_cancellable(&data->cancellable); nm_g_slice_free(data); } static gboolean _ovs_interface_is_dpdk(NMDevice *device) { NMSettingOvsInterface *s_ovs_iface; s_ovs_iface = nm_device_get_applied_setting(device, NM_TYPE_SETTING_OVS_INTERFACE); g_return_val_if_fail(s_ovs_iface, FALSE); return nm_streq(nm_setting_ovs_interface_get_interface_type(s_ovs_iface), "dpdk"); } static void set_mtu_cb(GError *error, gpointer user_data) { NMDevice *self = user_data; if (error && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) { _LOGW(LOGD_DEVICE, "could not change mtu of '%s': %s", nm_device_get_iface(self), error->message); } g_object_unref(self); } static NMTernary attach_port(NMDevice *device, NMDevice *port, NMConnection *connection, gboolean configure, GCancellable *cancellable, NMDeviceAttachPortCallback callback, gpointer user_data) { NMDeviceOvsPort *self = NM_DEVICE_OVS_PORT(device); NMActiveConnection *ac_port = NULL; NMActiveConnection *ac_bridge = NULL; NMDevice *bridge_device; NMSettingWired *s_wired; AttachPortData *data; if (!configure) return TRUE; ac_port = NM_ACTIVE_CONNECTION(nm_device_get_act_request(device)); ac_bridge = nm_active_connection_get_controller(ac_port); if (!ac_bridge) { _LOGW(LOGD_DEVICE, "can't attach %s: bridge active-connection not found", nm_device_get_iface(port)); return FALSE; } bridge_device = nm_active_connection_get_device(ac_bridge); if (!bridge_device) { _LOGW(LOGD_DEVICE, "can't attach %s: bridge device not found", nm_device_get_iface(port)); return FALSE; } data = g_slice_new(AttachPortData); *data = (AttachPortData) { .device = g_object_ref(device), .port = g_object_ref(port), .cancellable = g_object_ref(cancellable), .callback = callback, .callback_user_data = user_data, .add = TRUE, }; nm_ovsdb_add_interface(nm_ovsdb_get(), nm_active_connection_get_applied_connection(ac_bridge), nm_device_get_applied_connection(device), nm_device_get_applied_connection(port), bridge_device, port, add_del_iface_cb, data); /* DPDK ports does not have a link after the devbind, so the MTU must be * set on ovsdb after adding the interface. */ if (NM_IS_DEVICE_OVS_INTERFACE(port) && _ovs_interface_is_dpdk(port)) { s_wired = nm_device_get_applied_setting(port, NM_TYPE_SETTING_WIRED); if (s_wired && nm_setting_wired_get_mtu(s_wired)) { nm_ovsdb_set_interface_mtu(nm_ovsdb_get(), nm_device_get_ip_iface(port), nm_setting_wired_get_mtu(s_wired), set_mtu_cb, g_object_ref(port)); } } return NM_TERNARY_DEFAULT; } static NMTernary detach_port(NMDevice *device, NMDevice *port, gboolean configure, GCancellable *cancellable, NMDeviceAttachPortCallback callback, gpointer user_data) { NMDeviceOvsPort *self = NM_DEVICE_OVS_PORT(device); bool port_not_managed = !NM_IN_SET(nm_device_managed_type_get(port), NM_DEVICE_MANAGED_TYPE_FULL, NM_DEVICE_MANAGED_TYPE_ASSUME); NMTernary ret = TRUE; _LOGI(LOGD_DEVICE, "detaching ovs interface %s", nm_device_get_ip_iface(port)); /* Even if the an interface's device has gone away (e.g. externally * removed and thus we're called with configure=FALSE), we still need * to make sure its OVSDB entry is gone. */ if (configure || port_not_managed) { AttachPortData *data; data = g_slice_new(AttachPortData); *data = (AttachPortData) { .device = g_object_ref(device), .port = g_object_ref(port), .cancellable = nm_g_object_ref(cancellable), .callback = callback, .callback_user_data = user_data, .add = FALSE, }; nm_ovsdb_del_interface(nm_ovsdb_get(), nm_device_get_iface(port), add_del_iface_cb, data); ret = NM_TERNARY_DEFAULT; } if (configure) { /* Open VSwitch is going to delete this one. We must ignore what happens * next with the interface. */ if (NM_IS_DEVICE_OVS_INTERFACE(port)) nm_device_update_from_platform_link(port, NULL); } return ret; } static gboolean can_reapply_change(NMDevice *device, const char *setting_name, NMSetting *s_old, NMSetting *s_new, GHashTable *diffs, GError **error) { NMDeviceClass *device_class = NM_DEVICE_CLASS(nm_device_ovs_port_parent_class); if (nm_streq(setting_name, NM_SETTING_OVS_PORT_SETTING_NAME)) { return nm_device_hash_check_invalid_keys(diffs, NM_SETTING_OVS_PORT_SETTING_NAME, error, NM_SETTING_OVS_PORT_TAG, NM_SETTING_OVS_PORT_VLAN_MODE, NM_SETTING_OVS_PORT_BOND_UPDELAY, NM_SETTING_OVS_PORT_BOND_DOWNDELAY, NM_SETTING_OVS_PORT_LACP, NM_SETTING_OVS_PORT_BOND_MODE, NM_SETTING_OVS_PORT_TRUNKS); } if (NM_IN_STRSET(setting_name, NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME, NM_SETTING_OVS_OTHER_CONFIG_SETTING_NAME)) return TRUE; return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error); } /*****************************************************************************/ static void nm_device_ovs_port_init(NMDeviceOvsPort *self) {} static const NMDBusInterfaceInfoExtended interface_info_device_ovs_port = { .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( NM_DBUS_INTERFACE_DEVICE_OVS_PORT, .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE( "Slaves", "ao", NM_DEVICE_SLAVES, .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ), }; static void nm_device_ovs_port_class_init(NMDeviceOvsPortClass *klass) { NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); NMDeviceClass *device_class = NM_DEVICE_CLASS(klass); dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_ovs_port); device_class->connection_type_supported = NM_SETTING_OVS_PORT_SETTING_NAME; device_class->connection_type_check_compatible = NM_SETTING_OVS_PORT_SETTING_NAME; device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(); device_class->is_controller = TRUE; device_class->get_type_description = get_type_description; device_class->create_and_realize = create_and_realize; device_class->get_generic_capabilities = get_generic_capabilities; device_class->act_stage3_ip_config = act_stage3_ip_config; device_class->ready_for_ip_config = ready_for_ip_config; device_class->attach_port = attach_port; device_class->detach_port = detach_port; device_class->can_reapply_change = can_reapply_change; device_class->reapply_connection = nm_device_ovs_reapply_connection; }