From 6a841072ec3b3fae7012d078f2a58133169e31b1 Mon Sep 17 00:00:00 2001 From: Jan Vaclav Date: Wed, 1 Apr 2026 14:43:30 +0200 Subject: [PATCH] nmtui/bond: introduce "other options" list Bond connections can have options that are not exposed by any widget in the bond editor. The presence of certain mode-specific options makes it impossible to change the mode, e.g. from 802.3ad to active-backup when `lacp_rate` is set. Introduce an "Other options" list that shows all bond options not already configurable by a specific widget, and allow the user to edit them as key=value entries. Resolves #1805 Resolves: https://redhat.atlassian.net/browse/NMT-1888 --- NEWS | 3 ++ src/nmtui/nmt-address-list.c | 26 ++++++++--- src/nmtui/nmt-address-list.h | 3 +- src/nmtui/nmt-page-bond.c | 86 ++++++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index cba09f8722..214b65a91f 100644 --- a/NEWS +++ b/NEWS @@ -61,6 +61,9 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! * Allow persisting the managed state across reboots from nmcli and the D-Bus API. * Allow changing the device's administrative state in the kernel at the same time as a change to the managed state from nmcli and the D-Bus API. +* Allow configuring all bond options in nmtui by introducing a + "other options" field, which covers options not already covered by a + dedicated input field. ============================================= NetworkManager-1.56 diff --git a/src/nmtui/nmt-address-list.c b/src/nmtui/nmt-address-list.c index 9b8a948e0e..52b2bc92e5 100644 --- a/src/nmtui/nmt-address-list.c +++ b/src/nmtui/nmt-address-list.c @@ -5,14 +5,13 @@ /** * SECTION:nmt-address-list - * @short_description: An editable list of IP addresses or hostnames + * @short_description: An editable list of IP addresses, hostnames, or key=value pairs * * #NmtAddressList is a subclass of #NmtWidgetList that contains - * entries displaying IP addresses, address/prefix strings, or - * hostnames. This is designed for binding its #NmtAddressList:strings - * property to an appropriate #NMSettingIP4Config or - * #NMSettingIP6Config property via one of the nm-editor-bindings - * functions. + * entries displaying IP addresses, address/prefix strings, + * hostnames, or key=value pairs. This is designed for binding its + * #NmtAddressList:strings property to an appropriate property via one + * of the nm-editor-bindings functions. */ #include "libnm-client-aux-extern/nm-default-client.h" @@ -115,6 +114,18 @@ hostname_filter(NmtNewtEntry *entry, const char *text, int ch, int position, gpo return g_ascii_isalnum(ch) || ch == '.' || ch == '-' || ch == '~'; } +static gboolean +key_value_validate(NmtNewtEntry *entry, const char *text, gpointer user_data) +{ + const char *val; + + if (!text || !text[0]) + return TRUE; + + val = strchr(text, '='); + return val && val != text && val[1]; +} + static NmtNewtWidget * nmt_address_list_create_widget(NmtWidgetList *list, int num) { @@ -132,6 +143,9 @@ nmt_address_list_create_widget(NmtWidgetList *list, int num) } else if (priv->list_type == NMT_ADDRESS_LIST_HOSTNAME) { entry = nmt_newt_entry_new(25, NMT_NEWT_ENTRY_NONEMPTY); nmt_newt_entry_set_filter(NMT_NEWT_ENTRY(entry), hostname_filter, list); + } else if (priv->list_type == NMT_ADDRESS_LIST_KEY_VALUE) { + entry = nmt_newt_entry_new(40, 0); + nmt_newt_entry_set_validator(NMT_NEWT_ENTRY(entry), key_value_validate, NULL); } else { g_return_val_if_reached(NULL); } diff --git a/src/nmtui/nmt-address-list.h b/src/nmtui/nmt-address-list.h index 01669fbd86..3de667f75e 100644 --- a/src/nmtui/nmt-address-list.h +++ b/src/nmtui/nmt-address-list.h @@ -35,7 +35,8 @@ typedef enum { NMT_ADDRESS_LIST_IP4, NMT_ADDRESS_LIST_IP6_WITH_PREFIX, NMT_ADDRESS_LIST_IP6, - NMT_ADDRESS_LIST_HOSTNAME + NMT_ADDRESS_LIST_HOSTNAME, + NMT_ADDRESS_LIST_KEY_VALUE } NmtAddressListType; NmtNewtWidget *nmt_address_list_new(NmtAddressListType list_type); diff --git a/src/nmtui/nmt-page-bond.c b/src/nmtui/nmt-page-bond.c index f74bac2732..dbc05c8411 100644 --- a/src/nmtui/nmt-page-bond.c +++ b/src/nmtui/nmt-page-bond.c @@ -49,6 +49,7 @@ typedef struct { NmtNewtEntry *downdelay; NmtNewtEntry *arp_interval; NmtAddressList *arp_ip_target; + NmtAddressList *other_options; NmtPageBondMonitoringMode monitoring_mode; @@ -63,6 +64,43 @@ static void arp_ip_target_widget_changed(GObject *object, GParamSpec *pspec, gpo /*****************************************************************************/ +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, NmtAddressList *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; + + nm_assert(nm_setting_bond_get_option(s_bond, i, &opt_name, &opt_value)); + + 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) { @@ -155,6 +193,8 @@ bond_options_changed(GObject *object, GParamSpec *pspec, gpointer user_data) 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; } @@ -308,6 +348,47 @@ arp_ip_target_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_d 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; + 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++) { + nm_assert(nm_setting_bond_get_option(priv->s_bond, i, &name, NULL)); + + 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) { @@ -395,6 +476,11 @@ nmt_page_bond_constructed(GObject *object) nmt_editor_grid_append(grid, _("ARP targets"), widget, NULL); priv->arp_ip_target = NMT_ADDRESS_LIST(widget); + widget = nmt_address_list_new(NMT_ADDRESS_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_ADDRESS_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,