/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2013 Red Hat, Inc. */ /** * SECTION:nmt-page-bond * @short_description: The editor page for Bond connections * * Note that this is fairly different from most of the other editor * pages, because #NMSettingBond doesn't have properties, so we * can't just use #GBinding. */ #include "libnm-client-aux-extern/nm-default-client.h" #include "nmt-page-bond.h" #include #include #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "nmt-mac-entry.h" #include "nmt-list.h" #include "nmt-port-list.h" G_DEFINE_TYPE(NmtPageBond, nmt_page_bond, NMT_TYPE_EDITOR_PAGE_DEVICE) #define NMT_PAGE_BOND_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PAGE_BOND, NmtPageBondPrivate)) typedef enum { NMT_PAGE_BOND_MONITORING_UNKNOWN = -1, NMT_PAGE_BOND_MONITORING_MII = 0, NMT_PAGE_BOND_MONITORING_ARP = 1, } NmtPageBondMonitoringMode; typedef struct { NmtPortList *ports; /* Note: when adding new options to the UI also ensure they are * initialized in bond_connection_setup_func() */ NmtNewtPopup *mode; NmtNewtEntry *primary; NmtNewtPopup *monitoring; NmtNewtEntry *miimon; NmtNewtEntry *updelay; NmtNewtEntry *downdelay; NmtNewtEntry *arp_interval; NmtList *arp_ip_target; NmtList *other_options; NmtPageBondMonitoringMode monitoring_mode; NMSettingBond *s_bond; GType port_type; gboolean updating; } NmtPageBondPrivate; /*****************************************************************************/ static void arp_ip_target_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_data); /*****************************************************************************/ static gboolean _is_other_option(const char *option) { return !NM_IN_STRSET(option, NM_SETTING_BOND_OPTION_MODE, NM_SETTING_BOND_OPTION_PRIMARY, NM_SETTING_BOND_OPTION_MIIMON, NM_SETTING_BOND_OPTION_UPDELAY, NM_SETTING_BOND_OPTION_DOWNDELAY, NM_SETTING_BOND_OPTION_ARP_INTERVAL, NM_SETTING_BOND_OPTION_ARP_IP_TARGET); } static void _bond_update_other_options(NMSettingBond *s_bond, NmtList *list) { gs_unref_ptrarray GPtrArray *arr = g_ptr_array_new_with_free_func(g_free); guint num_opts = nm_setting_bond_get_num_options(s_bond); guint i; for (i = 0; i < num_opts; i++) { const char *opt_name; const char *opt_value; gboolean ret; ret = nm_setting_bond_get_option(s_bond, i, &opt_name, &opt_value); nm_assert(ret); if (_is_other_option(opt_name)) { g_ptr_array_add(arr, g_strdup_printf("%s=%s", opt_name, opt_value)); } } g_ptr_array_add(arr, NULL); g_object_set(G_OBJECT(list), "strings", arr->pdata, NULL); } /*****************************************************************************/ NmtEditorPage * nmt_page_bond_new(NMConnection *conn, NmtDeviceEntry *deventry) { return g_object_new(NMT_TYPE_PAGE_BOND, "connection", conn, "device-entry", deventry, NULL); } static void nmt_page_bond_init(NmtPageBond *bond) { NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond); priv->monitoring_mode = NMT_PAGE_BOND_MONITORING_UNKNOWN; priv->port_type = G_TYPE_NONE; } static NmtNewtPopupEntry bond_mode[] = { {N_("Round-robin"), "balance-rr"}, {N_("Active Backup"), "active-backup"}, {N_("XOR"), "balance-xor"}, {N_("Broadcast"), "broadcast"}, {N_("802.3ad"), "802.3ad"}, {N_("Adaptive Transmit Load Balancing (tlb)"), "balance-tlb"}, {N_("Adaptive Load Balancing (alb)"), "balance-alb"}, {NULL, NULL}}; /* NB: the ordering/numbering here corresponds to NmtPageBondMonitoringMode */ static NmtNewtPopupEntry bond_monitoring[] = {{N_("MII (recommended)"), "mii"}, {N_("ARP"), "arp"}, {NULL, NULL}}; static void bond_options_changed(GObject *object, GParamSpec *pspec, gpointer user_data) { NMSettingBond *s_bond = NM_SETTING_BOND(object); NmtPageBond *bond = NMT_PAGE_BOND(user_data); NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond); gs_free const char **ips = NULL; const char *val; gboolean visible_mii; NMBondMode mode; if (priv->updating) return; priv->updating = TRUE; val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MODE); nmt_newt_popup_set_active_id(priv->mode, val); mode = _nm_setting_bond_mode_from_string(val ?: ""); val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_PRIMARY); nmt_newt_entry_set_text(priv->primary, val); nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->primary), mode == NM_BOND_MODE_ACTIVEBACKUP); if (priv->monitoring_mode == NMT_PAGE_BOND_MONITORING_UNKNOWN) { val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL); if (_nm_utils_ascii_str_to_int64(val, 10, 0, G_MAXINT, 0) > 0) priv->monitoring_mode = NMT_PAGE_BOND_MONITORING_ARP; else priv->monitoring_mode = NMT_PAGE_BOND_MONITORING_MII; } nmt_newt_popup_set_active(priv->monitoring, priv->monitoring_mode); val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MIIMON); nmt_newt_entry_set_text(priv->miimon, val ?: "0"); val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_UPDELAY); nmt_newt_entry_set_text(priv->updelay, val ?: "0"); val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY); nmt_newt_entry_set_text(priv->downdelay, val ?: "0"); val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL); nmt_newt_entry_set_text(priv->arp_interval, val ?: "0"); val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET); ips = nm_utils_bond_option_ip_split(val); g_object_set(G_OBJECT(priv->arp_ip_target), "strings", ips ?: NM_PTRARRAY_EMPTY(const char *), NULL); visible_mii = (priv->monitoring_mode == NMT_PAGE_BOND_MONITORING_MII); nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->miimon), visible_mii); nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->updelay), visible_mii); nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->downdelay), visible_mii); nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->arp_interval), !visible_mii); nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->arp_ip_target), !visible_mii); _bond_update_other_options(s_bond, priv->other_options); priv->updating = FALSE; } static void ports_changed(GObject *object, GParamSpec *pspec, gpointer user_data) { NmtPageBond *bond = NMT_PAGE_BOND(user_data); NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond); GPtrArray *ports; g_object_get(object, "connections", &ports, NULL); if (ports->len == 0) { if (priv->port_type == G_TYPE_NONE) return; priv->port_type = G_TYPE_NONE; } else { NMConnection *port = ports->pdata[0]; if (priv->port_type != G_TYPE_NONE) return; if (nm_connection_is_type(port, NM_SETTING_INFINIBAND_SETTING_NAME)) priv->port_type = NM_TYPE_SETTING_INFINIBAND; else priv->port_type = NM_TYPE_SETTING_WIRED; } if (priv->port_type == NM_TYPE_SETTING_INFINIBAND) { nmt_newt_popup_set_active_id(priv->mode, "active-backup"); nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->mode), FALSE); } else nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->mode), TRUE); } static void _bond_add_option(NMSettingBond *s_bond, const char *option, const char *value) { if (nm_str_is_empty(value)) nm_setting_bond_remove_option(s_bond, option); else nm_setting_bond_add_option(s_bond, option, value); if (nm_streq(option, NM_SETTING_BOND_OPTION_ARP_INTERVAL)) _nm_setting_bond_remove_options_miimon(s_bond); else if (nm_streq(option, NM_SETTING_BOND_OPTION_MIIMON)) _nm_setting_bond_remove_options_arp_interval(s_bond); nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE); } #define WIDGET_CHANGED_FUNC(widget, func, option, dflt) \ static void widget##_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_data) \ { \ NmtPageBond *bond = NMT_PAGE_BOND(user_data); \ NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond); \ const char *v; \ \ if (priv->updating) \ return; \ \ v = func(priv->widget); \ priv->updating = TRUE; \ _bond_add_option(priv->s_bond, option, v ?: dflt); \ priv->updating = FALSE; \ } WIDGET_CHANGED_FUNC(primary, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_PRIMARY, NULL) WIDGET_CHANGED_FUNC(miimon, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_MIIMON, "0") WIDGET_CHANGED_FUNC(updelay, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_UPDELAY, "0") WIDGET_CHANGED_FUNC(downdelay, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_DOWNDELAY, "0") WIDGET_CHANGED_FUNC(arp_interval, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_ARP_INTERVAL, "0") static void mode_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_data) { NmtPageBond *bond = NMT_PAGE_BOND(user_data); NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond); const char *mode; if (priv->updating) return; mode = nmt_newt_popup_get_active_id(priv->mode); priv->updating = TRUE; _bond_add_option(priv->s_bond, NM_SETTING_BOND_OPTION_MODE, mode); priv->updating = FALSE; if (NM_IN_STRSET(mode, "balance-tlb", "balance-alb")) { nmt_newt_popup_set_active(priv->monitoring, NMT_PAGE_BOND_MONITORING_MII); nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->monitoring), FALSE); } else nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->monitoring), TRUE); if (NM_IN_STRSET(mode, "active-backup", "balance-alb", "balance-tlb")) { nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->primary), TRUE); _bond_add_option(priv->s_bond, NM_SETTING_BOND_OPTION_PRIMARY, nmt_newt_entry_get_text(priv->primary)); } else { nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->primary), FALSE); nm_setting_bond_remove_option(priv->s_bond, NM_SETTING_BOND_OPTION_PRIMARY); } } static void monitoring_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_data) { NmtPageBond *bond = NMT_PAGE_BOND(user_data); NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond); gboolean visible_mii; if (priv->updating) return; priv->monitoring_mode = nmt_newt_popup_get_active(priv->monitoring); visible_mii = (priv->monitoring_mode == NMT_PAGE_BOND_MONITORING_MII); nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->miimon), visible_mii); nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->updelay), visible_mii); nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->downdelay), visible_mii); nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->arp_interval), !visible_mii); nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->arp_ip_target), !visible_mii); if (visible_mii) { miimon_widget_changed(NULL, NULL, bond); updelay_widget_changed(NULL, NULL, bond); downdelay_widget_changed(NULL, NULL, bond); } else { arp_interval_widget_changed(NULL, NULL, bond); arp_ip_target_widget_changed(NULL, NULL, bond); } } static void arp_ip_target_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_data) { NmtPageBond *bond = NMT_PAGE_BOND(user_data); NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond); char **ips, *target; if (priv->updating) return; g_object_get(G_OBJECT(priv->arp_ip_target), "strings", &ips, NULL); target = g_strjoinv(",", ips); priv->updating = TRUE; _bond_add_option(priv->s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, target); priv->updating = FALSE; g_free(target); g_strfreev(ips); } static void other_options_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_data) { NmtPageBond *bond = NMT_PAGE_BOND(user_data); NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond); gs_strfreev char **other_options = NULL; const char *name; gboolean ret; guint num; guint i; if (priv->updating) return; priv->updating = TRUE; g_object_get(G_OBJECT(priv->other_options), "strings", &other_options, NULL); again: num = nm_setting_bond_get_num_options(priv->s_bond); for (i = 0; i < num; i++) { ret = nm_setting_bond_get_option(priv->s_bond, i, &name, NULL); nm_assert(ret); if (_is_other_option(name)) { nm_setting_bond_remove_option(priv->s_bond, name); goto again; } } for (i = 0; other_options && other_options[i]; i++) { char *val = strchr(other_options[i], '='); if (val && val != other_options[i] && val[1]) { *val = '\0'; if (_is_other_option(other_options[i])) nm_setting_bond_add_option(priv->s_bond, other_options[i], val + 1); } } priv->updating = FALSE; } static gboolean bond_connection_type_filter(GType connection_type, gpointer user_data) { NmtPageBond *bond = user_data; NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond); if (priv->port_type != NM_TYPE_SETTING_WIRED && connection_type == NM_TYPE_SETTING_INFINIBAND) return TRUE; if (priv->port_type != NM_TYPE_SETTING_INFINIBAND && connection_type == NM_TYPE_SETTING_WIRED) return TRUE; return FALSE; } static void nmt_page_bond_constructed(GObject *object) { NmtPageBond *bond = NMT_PAGE_BOND(object); NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond); NmtEditorSection *section; NmtEditorGrid *grid; NMSettingWired *s_wired; NMSettingBond *s_bond; NmtNewtWidget *widget, *label; NMConnection *conn; conn = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(bond)); s_bond = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_BOND); priv->s_bond = s_bond; s_wired = _nm_connection_ensure_setting(conn, NM_TYPE_SETTING_WIRED); section = nmt_editor_section_new(_("BOND"), NULL, TRUE); grid = nmt_editor_section_get_body(section); widget = nmt_newt_separator_new(); nmt_editor_grid_append(grid, _("Ports"), widget, NULL); nmt_editor_grid_set_row_flags(grid, widget, NMT_EDITOR_GRID_ROW_LABEL_ALIGN_LEFT); widget = nmt_port_list_new(conn, bond_connection_type_filter, bond); g_signal_connect(widget, "notify::connections", G_CALLBACK(ports_changed), bond); nmt_editor_grid_append(grid, NULL, widget, NULL); priv->ports = NMT_PORT_LIST(widget); widget = nmt_newt_popup_new(bond_mode); g_signal_connect(widget, "notify::active-id", G_CALLBACK(mode_widget_changed), bond); nmt_editor_grid_append(grid, _("Mode"), widget, NULL); priv->mode = NMT_NEWT_POPUP(widget); widget = nmt_newt_entry_new(40, 0); g_signal_connect(widget, "notify::text", G_CALLBACK(primary_widget_changed), bond); nmt_editor_grid_append(grid, _("Primary"), widget, NULL); priv->primary = NMT_NEWT_ENTRY(widget); widget = nmt_newt_popup_new(bond_monitoring); g_signal_connect(widget, "notify::active", G_CALLBACK(monitoring_widget_changed), bond); nmt_editor_grid_append(grid, _("Link monitoring"), widget, NULL); priv->monitoring = NMT_NEWT_POPUP(widget); widget = nmt_newt_entry_numeric_new(10, 0, G_MAXINT); g_signal_connect(widget, "notify::text", G_CALLBACK(miimon_widget_changed), bond); label = nmt_newt_label_new(C_("milliseconds", "ms")); nmt_editor_grid_append(grid, _("Monitoring frequency"), widget, label); priv->miimon = NMT_NEWT_ENTRY(widget); widget = nmt_newt_entry_numeric_new(10, 0, G_MAXINT); g_signal_connect(widget, "notify::text", G_CALLBACK(updelay_widget_changed), bond); label = nmt_newt_label_new(C_("milliseconds", "ms")); nmt_editor_grid_append(grid, _("Link up delay"), widget, label); priv->updelay = NMT_NEWT_ENTRY(widget); widget = nmt_newt_entry_numeric_new(10, 0, G_MAXINT); g_signal_connect(widget, "notify::text", G_CALLBACK(downdelay_widget_changed), bond); label = nmt_newt_label_new(C_("milliseconds", "ms")); nmt_editor_grid_append(grid, _("Link down delay"), widget, label); priv->downdelay = NMT_NEWT_ENTRY(widget); widget = nmt_newt_entry_numeric_new(10, 0, G_MAXINT); g_signal_connect(widget, "notify::text", G_CALLBACK(arp_interval_widget_changed), bond); label = nmt_newt_label_new(C_("milliseconds", "ms")); nmt_editor_grid_append(grid, _("Monitoring frequency"), widget, label); priv->arp_interval = NMT_NEWT_ENTRY(widget); widget = nmt_list_new(NMT_LIST_IP4); g_signal_connect(widget, "notify::strings", G_CALLBACK(arp_ip_target_widget_changed), bond); nmt_editor_grid_append(grid, _("ARP targets"), widget, NULL); priv->arp_ip_target = NMT_LIST(widget); widget = nmt_list_new(NMT_LIST_KEY_VALUE); g_signal_connect(widget, "notify::strings", G_CALLBACK(other_options_widget_changed), bond); nmt_editor_grid_append(grid, _("Other options (key=value)"), widget, NULL); priv->other_options = NMT_LIST(widget); widget = nmt_mac_entry_new(40, ETH_ALEN, NMT_MAC_ENTRY_TYPE_CLONED_ETHERNET); g_object_bind_property(s_wired, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, widget, "mac-address", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); nmt_editor_grid_append(grid, _("Cloned MAC address"), widget, NULL); g_signal_connect(s_bond, "notify::" NM_SETTING_BOND_OPTIONS, G_CALLBACK(bond_options_changed), bond); bond_options_changed(G_OBJECT(s_bond), NULL, bond); ports_changed(G_OBJECT(priv->ports), NULL, bond); nmt_editor_page_add_section(NMT_EDITOR_PAGE(bond), section); G_OBJECT_CLASS(nmt_page_bond_parent_class)->constructed(object); } static void nmt_page_bond_saved(NmtEditorPage *editor_page) { NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(editor_page); nmt_edit_connection_list_recommit(NMT_EDIT_CONNECTION_LIST(priv->ports)); } static void nmt_page_bond_class_init(NmtPageBondClass *bond_class) { GObjectClass *object_class = G_OBJECT_CLASS(bond_class); NmtEditorPageClass *editor_page_class = NMT_EDITOR_PAGE_CLASS(bond_class); g_type_class_add_private(bond_class, sizeof(NmtPageBondPrivate)); object_class->constructed = nmt_page_bond_constructed; editor_page_class->saved = nmt_page_bond_saved; }