summary refs log tree commit diff
path: root/libnm-core/nm-setting-bond.c
diff options
context:
space:
mode:
Diffstat (limited to 'libnm-core/nm-setting-bond.c')
-rw-r--r--libnm-core/nm-setting-bond.c809
1 files changed, 515 insertions, 294 deletions
diff --git a/libnm-core/nm-setting-bond.c b/libnm-core/nm-setting-bond.c
index f3ae9ef5..8986e434 100644
--- a/libnm-core/nm-setting-bond.c
+++ b/libnm-core/nm-setting-bond.c
@@ -44,54 +44,285 @@ G_DEFINE_TYPE (NMSettingBond, nm_setting_bond, NM_TYPE_SETTING)
 
 /*****************************************************************************/
 
+static const char *const valid_options_lst[] = {
+	/* mode must be the first element. nm-device-bond.c relies on that. */
+	NM_SETTING_BOND_OPTION_MODE,
+	NM_SETTING_BOND_OPTION_MIIMON,
+	NM_SETTING_BOND_OPTION_DOWNDELAY,
+	NM_SETTING_BOND_OPTION_UPDELAY,
+	NM_SETTING_BOND_OPTION_ARP_INTERVAL,
+	NM_SETTING_BOND_OPTION_ARP_IP_TARGET,
+	NM_SETTING_BOND_OPTION_ARP_VALIDATE,
+	NM_SETTING_BOND_OPTION_PRIMARY,
+	NM_SETTING_BOND_OPTION_PRIMARY_RESELECT,
+	NM_SETTING_BOND_OPTION_FAIL_OVER_MAC,
+	NM_SETTING_BOND_OPTION_USE_CARRIER,
+	NM_SETTING_BOND_OPTION_AD_SELECT,
+	NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY,
+	NM_SETTING_BOND_OPTION_RESEND_IGMP,
+	NM_SETTING_BOND_OPTION_LACP_RATE,
+	NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,
+	NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO,
+	NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,
+	NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY,
+	NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE,
+	NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS,
+	NM_SETTING_BOND_OPTION_MIN_LINKS,
+	NM_SETTING_BOND_OPTION_NUM_GRAT_ARP,
+	NM_SETTING_BOND_OPTION_NUM_UNSOL_NA,
+	NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE,
+	NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,
+	NM_SETTING_BOND_OPTION_LP_INTERVAL,
+	NULL,
+};
+
 typedef struct {
-	const char *opt;
 	const char *val;
-	guint opt_type;
+	NMBondOptionType opt_type;
 	guint min;
 	guint max;
-	char *list[10];
-} BondDefault;
-
-static const BondDefault defaults[] = {
-	{ NM_SETTING_BOND_OPTION_MODE,             "balance-rr", NM_BOND_OPTION_TYPE_BOTH, 0, 6,
-	  { "balance-rr", "active-backup", "balance-xor", "broadcast", "802.3ad", "balance-tlb", "balance-alb", NULL } },
-	{ NM_SETTING_BOND_OPTION_MIIMON,           "100",        NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT },
-	{ NM_SETTING_BOND_OPTION_DOWNDELAY,        "0",          NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT },
-	{ NM_SETTING_BOND_OPTION_UPDELAY,          "0",          NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT },
-	{ NM_SETTING_BOND_OPTION_ARP_INTERVAL,     "0",          NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT },
-	{ NM_SETTING_BOND_OPTION_ARP_IP_TARGET,    "",           NM_BOND_OPTION_TYPE_IP },
-	{ NM_SETTING_BOND_OPTION_ARP_VALIDATE,     "none",       NM_BOND_OPTION_TYPE_BOTH, 0, 6,
-	  { "none", "active", "backup", "all", "filter", "filter_active", "filter_backup", NULL } },
-	{ NM_SETTING_BOND_OPTION_PRIMARY,          "",           NM_BOND_OPTION_TYPE_IFNAME },
-	{ NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, "always",     NM_BOND_OPTION_TYPE_BOTH, 0, 2,
-	  { "always", "better", "failure", NULL } },
-	{ NM_SETTING_BOND_OPTION_FAIL_OVER_MAC,    "none",       NM_BOND_OPTION_TYPE_BOTH, 0, 2,
-	  { "none", "active", "follow", NULL } },
-	{ NM_SETTING_BOND_OPTION_USE_CARRIER,      "1",          NM_BOND_OPTION_TYPE_INT, 0, 1 },
-	{ NM_SETTING_BOND_OPTION_AD_SELECT,        "stable",     NM_BOND_OPTION_TYPE_BOTH, 0, 2,
-	  { "stable", "bandwidth", "count", NULL } },
-	{ NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, "layer2",     NM_BOND_OPTION_TYPE_BOTH, 0, 4,
-	  { "layer2", "layer3+4", "layer2+3", "encap2+3", "encap3+4", NULL } },
-	{ NM_SETTING_BOND_OPTION_RESEND_IGMP,      "1",          NM_BOND_OPTION_TYPE_INT, 0, 255 },
-	{ NM_SETTING_BOND_OPTION_LACP_RATE,        "slow",       NM_BOND_OPTION_TYPE_BOTH, 0, 1,
-	  { "slow", "fast", NULL } },
-	{ NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,     "",           NM_BOND_OPTION_TYPE_IFNAME },
-	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO,"65535",      NM_BOND_OPTION_TYPE_INT, 1, 65535 },
-	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,  NULL,         NM_BOND_OPTION_TYPE_MAC },
-	{ NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY, "0",          NM_BOND_OPTION_TYPE_INT, 0, 1023},
-	{ NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE,"0",          NM_BOND_OPTION_TYPE_INT, 0, 1},
-	{ NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS,  "any",        NM_BOND_OPTION_TYPE_BOTH, 0, 1, {"any", "all"}},
-	{ NM_SETTING_BOND_OPTION_MIN_LINKS,        "0",          NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT },
-	{ NM_SETTING_BOND_OPTION_NUM_GRAT_ARP,     "1",          NM_BOND_OPTION_TYPE_INT, 0, 255 },
-	{ NM_SETTING_BOND_OPTION_NUM_UNSOL_NA,     "1",          NM_BOND_OPTION_TYPE_INT, 0, 255 },
-	{ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE,"1",          NM_BOND_OPTION_TYPE_INT, 0, 65535 },
-	{ NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,   "1",          NM_BOND_OPTION_TYPE_INT, 0, 1 },
-	{ NM_SETTING_BOND_OPTION_LP_INTERVAL,      "1",          NM_BOND_OPTION_TYPE_INT, 1, G_MAXINT },
-};
+	const char *const*list;
+} OptionMeta;
+
+static gboolean
+_nm_assert_bond_meta (const OptionMeta *option_meta)
+{
+	nm_assert (option_meta);
+
+	switch (option_meta->opt_type) {
+	case NM_BOND_OPTION_TYPE_BOTH:
+		nm_assert (option_meta->val);
+		nm_assert (option_meta->list);
+		nm_assert (option_meta->list[0]);
+		nm_assert (option_meta->min == 0);
+		nm_assert (option_meta->max == NM_PTRARRAY_LEN (option_meta->list) - 1);
+		nm_assert (g_strv_contains (option_meta->list, option_meta->val));
+		return TRUE;
+	case NM_BOND_OPTION_TYPE_INT:
+		nm_assert (option_meta->val);
+		nm_assert (!option_meta->list);
+		nm_assert (option_meta->min < option_meta->max);
+		nm_assert (NM_STRCHAR_ALL (option_meta->val, ch, g_ascii_isdigit (ch)));
+		nm_assert (NM_STRCHAR_ALL (option_meta->val, ch, g_ascii_isdigit (ch)));
+		nm_assert (({
+		              _nm_utils_ascii_str_to_uint64 (option_meta->val, 10, option_meta->min, option_meta->max, 0);
+		              errno == 0;
+		            }));
+		return TRUE;
+	case NM_BOND_OPTION_TYPE_IP:
+	case NM_BOND_OPTION_TYPE_IFNAME:
+		nm_assert (option_meta->val);
+		/* fall-through */
+	case NM_BOND_OPTION_TYPE_MAC:
+		nm_assert (!option_meta->list);
+		nm_assert (option_meta->min == 0);
+		nm_assert (option_meta->max == 0);
+		return TRUE;
+	}
+
+	nm_assert_not_reached ();
+	return FALSE;
+}
+
+static char const *const _option_default_strv_ad_select[]        = NM_MAKE_STRV ("stable", "bandwidth", "count");
+static char const *const _option_default_strv_arp_all_targets[]  = NM_MAKE_STRV ("any", "all");
+static char const *const _option_default_strv_arp_validate[]     = NM_MAKE_STRV ("none", "active", "backup", "all", "filter", "filter_active", "filter_backup");
+static char const *const _option_default_strv_fail_over_mac[]    = NM_MAKE_STRV ("none", "active", "follow");
+static char const *const _option_default_strv_lacp_rate[]        = NM_MAKE_STRV ("slow", "fast");
+static char const *const _option_default_strv_mode[]             = NM_MAKE_STRV ("balance-rr", "active-backup", "balance-xor", "broadcast", "802.3ad", "balance-tlb", "balance-alb");
+static char const *const _option_default_strv_primary_reselect[] = NM_MAKE_STRV ("always", "better", "failure");
+static char const *const _option_default_strv_xmit_hash_policy[] = NM_MAKE_STRV ("layer2", "layer3+4", "layer2+3", "encap2+3", "encap3+4");
+
+static
+NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE (
+	_get_option_meta,
+	OptionMeta,
+	{
+		G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (LIST) == G_N_ELEMENTS (valid_options_lst) - 1);
+
+		if (NM_MORE_ASSERT_ONCE (5)) {
+			int i;
+
+			nm_assert (G_N_ELEMENTS (LIST) == NM_PTRARRAY_LEN (valid_options_lst));
+			for (i = 0; i < G_N_ELEMENTS (LIST); i++)
+				_nm_assert_bond_meta (&LIST[i].value);
+			nm_assert (nm_streq (valid_options_lst[0], NM_SETTING_BOND_OPTION_MODE));
+		}
+	},
+	{ return NULL; },
+	{ NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,      { "",           NM_BOND_OPTION_TYPE_IFNAME                                                   } },
+	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, { "65535",      NM_BOND_OPTION_TYPE_INT,   1, 65535                                          } },
+	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,   { NULL,         NM_BOND_OPTION_TYPE_MAC                                                      } },
+	{ NM_SETTING_BOND_OPTION_AD_SELECT,         { "stable",     NM_BOND_OPTION_TYPE_BOTH,  0, 2,       _option_default_strv_ad_select        } },
+	{ NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY,  { "0",          NM_BOND_OPTION_TYPE_INT,   0, 1023                                           } },
+	{ NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE, { "0",          NM_BOND_OPTION_TYPE_INT,   0, 1                                              } },
+	{ NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS,   { "any",        NM_BOND_OPTION_TYPE_BOTH,  0, 1,       _option_default_strv_arp_all_targets  } },
+	{ NM_SETTING_BOND_OPTION_ARP_INTERVAL,      { "0",          NM_BOND_OPTION_TYPE_INT,   0, G_MAXINT                                       } },
+	{ NM_SETTING_BOND_OPTION_ARP_IP_TARGET,     { "",           NM_BOND_OPTION_TYPE_IP                                                       } },
+	{ NM_SETTING_BOND_OPTION_ARP_VALIDATE,      { "none",       NM_BOND_OPTION_TYPE_BOTH,  0, 6,       _option_default_strv_arp_validate     } },
+	{ NM_SETTING_BOND_OPTION_DOWNDELAY,         { "0",          NM_BOND_OPTION_TYPE_INT,   0, G_MAXINT                                       } },
+	{ NM_SETTING_BOND_OPTION_FAIL_OVER_MAC,     { "none",       NM_BOND_OPTION_TYPE_BOTH,  0, 2,       _option_default_strv_fail_over_mac    } },
+	{ NM_SETTING_BOND_OPTION_LACP_RATE,         { "slow",       NM_BOND_OPTION_TYPE_BOTH,  0, 1,       _option_default_strv_lacp_rate        } },
+	{ NM_SETTING_BOND_OPTION_LP_INTERVAL,       { "1",          NM_BOND_OPTION_TYPE_INT,   1, G_MAXINT                                       } },
+	{ NM_SETTING_BOND_OPTION_MIIMON,            { "100",        NM_BOND_OPTION_TYPE_INT,   0, G_MAXINT                                       } },
+	{ NM_SETTING_BOND_OPTION_MIN_LINKS,         { "0",          NM_BOND_OPTION_TYPE_INT,   0, G_MAXINT                                       } },
+	{ NM_SETTING_BOND_OPTION_MODE,              { "balance-rr", NM_BOND_OPTION_TYPE_BOTH,  0, 6,       _option_default_strv_mode             } },
+	{ NM_SETTING_BOND_OPTION_NUM_GRAT_ARP,      { "1",          NM_BOND_OPTION_TYPE_INT,   0, 255                                            } },
+	{ NM_SETTING_BOND_OPTION_NUM_UNSOL_NA,      { "1",          NM_BOND_OPTION_TYPE_INT,   0, 255                                            } },
+	{ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, { "1",          NM_BOND_OPTION_TYPE_INT,   0, 65535                                          } },
+	{ NM_SETTING_BOND_OPTION_PRIMARY,           { "",           NM_BOND_OPTION_TYPE_IFNAME                                                   } },
+	{ NM_SETTING_BOND_OPTION_PRIMARY_RESELECT,  { "always",     NM_BOND_OPTION_TYPE_BOTH,  0, 2,       _option_default_strv_primary_reselect } },
+	{ NM_SETTING_BOND_OPTION_RESEND_IGMP,       { "1",          NM_BOND_OPTION_TYPE_INT,   0, 255                                            } },
+	{ NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,    { "1",          NM_BOND_OPTION_TYPE_INT,   0, 1                                              } },
+	{ NM_SETTING_BOND_OPTION_UPDELAY,           { "0",          NM_BOND_OPTION_TYPE_INT,   0, G_MAXINT                                       } },
+	{ NM_SETTING_BOND_OPTION_USE_CARRIER,       { "1",          NM_BOND_OPTION_TYPE_INT,   0, 1                                              } },
+	{ NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY,  { "layer2",     NM_BOND_OPTION_TYPE_BOTH,  0, 4,       _option_default_strv_xmit_hash_policy } },
+);
 
 /*****************************************************************************/
 
+#define BIT(x) (((guint32) 1) << (x))
+
+static
+NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
+	_bond_option_unsupp_mode,
+	guint32,
+	{ ; },
+	{ return 0; },
+	{ NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,      ~(BIT (NM_BOND_MODE_ACTIVEBACKUP) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
+	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, ~(BIT (NM_BOND_MODE_8023AD)) },
+	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,   ~(BIT (NM_BOND_MODE_8023AD)) },
+	{ NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY,  ~(BIT (NM_BOND_MODE_8023AD)) },
+	{ NM_SETTING_BOND_OPTION_ARP_INTERVAL,       (BIT (NM_BOND_MODE_8023AD)       | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
+	{ NM_SETTING_BOND_OPTION_ARP_IP_TARGET,      (BIT (NM_BOND_MODE_8023AD)       | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
+	{ NM_SETTING_BOND_OPTION_ARP_VALIDATE,       (BIT (NM_BOND_MODE_8023AD)       | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
+	{ NM_SETTING_BOND_OPTION_LACP_RATE,         ~(BIT (NM_BOND_MODE_8023AD)) },
+	{ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, ~(BIT (NM_BOND_MODE_ROUNDROBIN)) },
+	{ NM_SETTING_BOND_OPTION_PRIMARY,           ~(BIT (NM_BOND_MODE_ACTIVEBACKUP) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
+	{ NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,    ~(BIT (NM_BOND_MODE_TLB)) },
+)
+
+gboolean
+_nm_setting_bond_option_supported (const char *option, NMBondMode mode)
+{
+	nm_assert (option);
+	nm_assert (mode != NM_BOND_MODE_UNKNOWN);
+	nm_assert (_NM_INT_NOT_NEGATIVE (mode) && mode < 32);
+
+	return !NM_FLAGS_ANY (_bond_option_unsupp_mode (option), BIT (mode));
+}
+
+static const char*
+_bond_get_option (NMSettingBond *self,
+                  const char *option)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BOND (self), NULL);
+	g_return_val_if_fail (option, NULL);
+
+	return g_hash_table_lookup (NM_SETTING_BOND_GET_PRIVATE (self)->options, option);
+}
+
+static const char*
+_bond_get_option_default (NMSettingBond *self,
+                          const char *option)
+{
+	const OptionMeta *option_meta;
+
+	g_return_val_if_fail (NM_IS_SETTING_BOND (self), NULL);
+
+	option_meta = _get_option_meta (option);
+
+	g_return_val_if_fail (option_meta, NULL);
+
+	return option_meta->val;
+}
+
+static const char*
+_bond_get_option_or_default (NMSettingBond *self,
+                             const char *option)
+{
+	return    _bond_get_option (self, option)
+	       ?: _bond_get_option_default (self, option);
+}
+
+static const char*
+_bond_get_option_normalized (NMSettingBond* self,
+                             const char* option,
+                             gboolean get_default_only)
+{
+	const char *arp_interval_str;
+	const char *mode_str;
+	gint64 arp_interval;
+	NMBondMode mode;
+	const char *value = NULL;
+
+	g_return_val_if_fail (NM_IS_SETTING_BOND (self), NULL);
+	g_return_val_if_fail (option, NULL);
+
+	mode_str = _bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_MODE);
+	mode = _nm_setting_bond_mode_from_string (mode_str);
+	g_return_val_if_fail (mode != NM_BOND_MODE_UNKNOWN, NULL);
+
+	if (!_nm_setting_bond_option_supported (option, mode))
+		return NULL;
+
+	/* Apply custom NetworkManager policies here */
+	if (!get_default_only) {
+		if (NM_IN_STRSET (option,
+		                  NM_SETTING_BOND_OPTION_UPDELAY,
+		                  NM_SETTING_BOND_OPTION_DOWNDELAY,
+		                  NM_SETTING_BOND_OPTION_MIIMON)) {
+			/* if arp_interval is explicitly set and miimon is not, then disable miimon
+			 * (and related updelay and downdelay) as recommended by the kernel docs */
+			arp_interval_str = _bond_get_option (self, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
+			arp_interval = _nm_utils_ascii_str_to_int64 (arp_interval_str, 10, 0, G_MAXINT, 0);
+
+			if (!arp_interval || _bond_get_option (self, NM_SETTING_BOND_OPTION_MIIMON)) {
+				value = _bond_get_option (self, option);
+			} else {
+				return NULL;
+			}
+		} else if (NM_IN_STRSET (option,
+		                         NM_SETTING_BOND_OPTION_NUM_GRAT_ARP,
+		                         NM_SETTING_BOND_OPTION_NUM_UNSOL_NA)) {
+			/* just get one of the 2, at kernel level they're the same bond option */
+			value = _bond_get_option (self, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP);
+			if (!value) {
+				value = _bond_get_option (self, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA);
+			}
+		} else {
+			value = _bond_get_option (self, option);
+		}
+	}
+
+	if (!value) {
+		/* Apply rules that change the default value of an option */
+		if (nm_streq (option, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM)) {
+			/* The default value depends on the current mode */
+			if (NM_IN_STRSET (mode_str, "4", "802.3ad"))
+				return "00:00:00:00:00:00";
+			else
+				return "";
+		} else {
+			return _bond_get_option_or_default (self, option);
+		}
+	}
+
+	return value;
+}
+
+const char*
+nm_setting_bond_get_option_or_default (NMSettingBond *self,
+                                       const char *option)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BOND (self), NULL);
+	g_return_val_if_fail (option, NULL);
+
+	return _bond_get_option_normalized (self,
+	                                    option,
+	                                    FALSE);
+}
+
 static int
 _atoi (const char *value)
 {
@@ -120,6 +351,26 @@ nm_setting_bond_get_num_options (NMSettingBond *setting)
 	return g_hash_table_size (NM_SETTING_BOND_GET_PRIVATE (setting)->options);
 }
 
+static int
+_get_option_sort (gconstpointer p_a, gconstpointer p_b, gpointer _unused)
+{
+	const char *a = *((const char *const*) p_a);
+	const char *b = *((const char *const*) p_b);
+
+	NM_CMP_DIRECT (nm_streq (b, NM_SETTING_BOND_OPTION_MODE),
+	               nm_streq (a, NM_SETTING_BOND_OPTION_MODE));
+	NM_CMP_DIRECT_STRCMP (a, b);
+	nm_assert_not_reached ();
+	return 0;
+}
+
+static void
+_ensure_options_idx_cache (NMSettingBondPrivate *priv)
+{
+	if (!G_UNLIKELY (priv->options_idx_cache))
+		priv->options_idx_cache = nm_utils_named_values_from_str_dict_with_sort (priv->options, NULL, _get_option_sort, NULL);
+}
+
 /**
  * nm_setting_bond_get_option:
  * @setting: the #NMSettingBond
@@ -158,8 +409,7 @@ nm_setting_bond_get_option (NMSettingBond *setting,
 	if (idx >= len)
 		return FALSE;
 
-	if (!G_UNLIKELY (priv->options_idx_cache))
-		priv->options_idx_cache = nm_utils_named_values_from_str_dict (priv->options, NULL);
+	_ensure_options_idx_cache (priv);
 
 	NM_SET_OUT (out_name, priv->options_idx_cache[idx].name);
 	NM_SET_OUT (out_value, priv->options_idx_cache[idx].value_str);
@@ -167,14 +417,14 @@ nm_setting_bond_get_option (NMSettingBond *setting,
 }
 
 static gboolean
-validate_int (const char *name, const char *value, const BondDefault *def)
+validate_int (const char *name, const char *value, const OptionMeta *option_meta)
 {
 	guint64 num;
 
 	if (!NM_STRCHAR_ALL (value, ch, g_ascii_isdigit (ch)))
 		return FALSE;
 
-	num = _nm_utils_ascii_str_to_uint64 (value, 10, def->min, def->max, G_MAXUINT64);
+	num = _nm_utils_ascii_str_to_uint64 (value, 10, option_meta->min, option_meta->max, G_MAXUINT64);
 	if (   num == G_MAXUINT64
 	    && errno != 0)
 		return FALSE;
@@ -183,17 +433,17 @@ validate_int (const char *name, const char *value, const BondDefault *def)
 }
 
 static gboolean
-validate_list (const char *name, const char *value, const BondDefault *def)
+validate_list (const char *name, const char *value, const OptionMeta *option_meta)
 {
-	guint i;
+	int i;
+
+	nm_assert (option_meta->list);
 
-	for (i = 0; i < G_N_ELEMENTS (def->list) && def->list[i]; i++) {
-		if (g_strcmp0 (def->list[i], value) == 0)
+	for (i = 0; option_meta->list[i]; i++) {
+		if (nm_streq (option_meta->list[i], value))
 			return TRUE;
 	}
-
-	/* empty validation list means all values pass */
-	return def->list[0] == NULL ? TRUE : FALSE;
+	return FALSE;
 }
 
 static gboolean
@@ -232,9 +482,6 @@ validate_ip (const char *name, const char *value)
 static gboolean
 validate_ifname (const char *name, const char *value)
 {
-	if (!value || !value[0])
-		return FALSE;
-
 	return nm_utils_ifname_valid_kernel (value, NULL);
 }
 
@@ -253,33 +500,30 @@ gboolean
 nm_setting_bond_validate_option (const char *name,
                                  const char *value)
 {
-	guint i;
+	const OptionMeta *option_meta;
 
-	if (!name || !name[0])
+	option_meta = _get_option_meta (name);
+	if (!option_meta)
 		return FALSE;
 
-	for (i = 0; i < G_N_ELEMENTS (defaults); i++) {
-		if (g_strcmp0 (defaults[i].opt, name) == 0) {
-			if (value == NULL)
-				return TRUE;
-			switch (defaults[i].opt_type) {
-			case NM_BOND_OPTION_TYPE_INT:
-				return validate_int (name, value, &defaults[i]);
-			case NM_BOND_OPTION_TYPE_STRING:
-				return validate_list (name, value, &defaults[i]);
-			case NM_BOND_OPTION_TYPE_BOTH:
-				return (   validate_int (name, value, &defaults[i])
-				        || validate_list (name, value, &defaults[i]));
-			case NM_BOND_OPTION_TYPE_IP:
-				return validate_ip (name, value);
-			case NM_BOND_OPTION_TYPE_MAC:
-				return nm_utils_hwaddr_valid (value, ETH_ALEN);
-			case NM_BOND_OPTION_TYPE_IFNAME:
-				return validate_ifname (name, value);
-			}
-			return FALSE;
-		}
+	if (!value)
+		return TRUE;
+
+	switch (option_meta->opt_type) {
+	case NM_BOND_OPTION_TYPE_INT:
+		return validate_int (name, value, option_meta);
+	case NM_BOND_OPTION_TYPE_BOTH:
+		return (   validate_int (name, value, option_meta)
+		        || validate_list (name, value, option_meta));
+	case NM_BOND_OPTION_TYPE_IP:
+		return validate_ip (name, value);
+	case NM_BOND_OPTION_TYPE_MAC:
+		return nm_utils_hwaddr_valid (value, ETH_ALEN);
+	case NM_BOND_OPTION_TYPE_IFNAME:
+		return validate_ifname (name, value);
 	}
+
+	nm_assert_not_reached ();
 	return FALSE;
 }
 
@@ -303,7 +547,7 @@ nm_setting_bond_get_option_by_name (NMSettingBond *setting,
 	if (!nm_setting_bond_validate_option (name, NULL))
 		return NULL;
 
-	return g_hash_table_lookup (NM_SETTING_BOND_GET_PRIVATE (setting)->options, name);
+	return _bond_get_option (setting, name);
 }
 
 /**
@@ -340,17 +584,6 @@ nm_setting_bond_add_option (NMSettingBond *setting,
 	nm_clear_g_free (&priv->options_idx_cache);
 	g_hash_table_insert (priv->options, g_strdup (name), g_strdup (value));
 
-	if (   !strcmp (name, NM_SETTING_BOND_OPTION_MIIMON)
-	    && strcmp (value, "0") != 0) {
-		g_hash_table_remove (priv->options, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-		g_hash_table_remove (priv->options, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
-	} else if (   !strcmp (name, NM_SETTING_BOND_OPTION_ARP_INTERVAL)
-	           && strcmp (value, "0") != 0) {
-		g_hash_table_remove (priv->options, NM_SETTING_BOND_OPTION_MIIMON);
-		g_hash_table_remove (priv->options, NM_SETTING_BOND_OPTION_DOWNDELAY);
-		g_hash_table_remove (priv->options, NM_SETTING_BOND_OPTION_UPDELAY);
-	}
-
 	_notify (setting, PROP_OPTIONS);
 
 	return TRUE;
@@ -401,16 +634,7 @@ nm_setting_bond_remove_option (NMSettingBond *setting,
 const char **
 nm_setting_bond_get_valid_options  (NMSettingBond *setting)
 {
-	static const char *array[G_N_ELEMENTS (defaults) + 1] = { NULL };
-	int i;
-
-	/* initialize the array once */
-	if (G_UNLIKELY (array[0] == NULL)) {
-		for (i = 0; i < G_N_ELEMENTS (defaults); i++)
-			array[i] = defaults[i].opt;
-		array[i] = NULL;
-	}
-	return array;
+	return (const char **) valid_options_lst;
 }
 
 /**
@@ -424,28 +648,38 @@ nm_setting_bond_get_valid_options  (NMSettingBond *setting)
 const char *
 nm_setting_bond_get_option_default (NMSettingBond *setting, const char *name)
 {
-	const char *mode;
-	guint i;
-
 	g_return_val_if_fail (NM_IS_SETTING_BOND (setting), NULL);
-	g_return_val_if_fail (nm_setting_bond_validate_option (name, NULL), NULL);
-
-	if (nm_streq (name, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM)) {
-		/* The default value depends on the current mode */
-		mode = nm_setting_bond_get_option_by_name (setting, NM_SETTING_BOND_OPTION_MODE);
-		if (   nm_streq0 (mode, "4")
-		    || nm_streq0 (mode, "802.3ad"))
-			return "00:00:00:00:00:00";
-		else
-			return "";
-	}
 
-	for (i = 0; i < G_N_ELEMENTS (defaults); i++) {
-		if (g_strcmp0 (defaults[i].opt, name) == 0)
-			return defaults[i].val;
+	if (!name) {
+		return NULL;
 	}
-	/* Any option that passes nm_setting_bond_validate_option() should also be found in defaults */
-	g_assert_not_reached ();
+
+	return _bond_get_option_normalized (setting,
+	                                    name,
+	                                    TRUE);
+}
+
+/**
+ * nm_setting_bond_get_option_normalized:
+ * @setting: the #NMSettingBond
+ * @name: the name of the option
+ *
+ * Since: 1.24
+ *
+ * Returns: the value of the bond option after normalization, which is what NetworkManager
+ *   will actually apply when activating the connection. %NULL if the option won't be applied
+ *   to the connection.
+ **/
+const char *
+nm_setting_bond_get_option_normalized (NMSettingBond *setting,
+                                       const char *name)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BOND (setting), NULL);
+	g_return_val_if_fail (name, NULL);
+
+	return _bond_get_option_normalized (setting,
+	                                    name,
+	                                    FALSE);
 }
 
 /**
@@ -458,131 +692,97 @@ nm_setting_bond_get_option_default (NMSettingBond *setting, const char *name)
 NMBondOptionType
 _nm_setting_bond_get_option_type (NMSettingBond *setting, const char *name)
 {
-	guint i;
+	const OptionMeta *option_meta;
 
 	g_return_val_if_fail (NM_IS_SETTING_BOND (setting), NM_BOND_OPTION_TYPE_INT);
-	g_return_val_if_fail (nm_setting_bond_validate_option (name, NULL), NM_BOND_OPTION_TYPE_INT);
 
-	for (i = 0; i < G_N_ELEMENTS (defaults); i++) {
-		if (nm_streq0 (defaults[i].opt, name))
-			return defaults[i].opt_type;
-	}
-	/* Any option that passes nm_setting_bond_validate_option() should also be found in defaults */
-	g_assert_not_reached ();
-}
+	option_meta = _get_option_meta (name);
 
-NMBondMode
-_nm_setting_bond_mode_from_string (const char *str)
-{
-	g_return_val_if_fail (str, NM_BOND_MODE_UNKNOWN);
+	g_return_val_if_fail (option_meta, NM_BOND_OPTION_TYPE_INT);
 
-	if (nm_streq (str, "balance-rr"))
-		return NM_BOND_MODE_ROUNDROBIN;
-	if (nm_streq (str, "active-backup"))
-		return NM_BOND_MODE_ACTIVEBACKUP;
-	if (nm_streq (str, "balance-xor"))
-		return NM_BOND_MODE_XOR;
-	if (nm_streq (str, "broadcast"))
-		return NM_BOND_MODE_BROADCAST;
-	if (nm_streq (str, "802.3ad"))
-		return NM_BOND_MODE_8023AD;
-	if (nm_streq (str, "balance-tlb"))
-		return NM_BOND_MODE_TLB;
-	if (nm_streq (str, "balance-alb"))
-		return NM_BOND_MODE_ALB;
-
-	return NM_BOND_MODE_UNKNOWN;
+	return option_meta->opt_type;
 }
 
-/*****************************************************************************/
-
-#define BIT(x) (1 << (x))
-
-static const struct {
-	const char *option;
-	NMBondMode unsupp_modes;
-} bond_unsupp_modes[] = {
-	{ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, ~(BIT (NM_BOND_MODE_ROUNDROBIN)) },
-	{ NM_SETTING_BOND_OPTION_ARP_VALIDATE,      BIT (NM_BOND_MODE_8023AD) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB) },
-	{ NM_SETTING_BOND_OPTION_ARP_INTERVAL,      BIT (NM_BOND_MODE_8023AD) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB) },
-	{ NM_SETTING_BOND_OPTION_ARP_IP_TARGET,     BIT (NM_BOND_MODE_8023AD) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB) },
-	{ NM_SETTING_BOND_OPTION_LACP_RATE,         ~(BIT (NM_BOND_MODE_8023AD)) },
-	{ NM_SETTING_BOND_OPTION_PRIMARY,           ~(BIT (NM_BOND_MODE_ACTIVEBACKUP) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
-	{ NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,      ~(BIT (NM_BOND_MODE_ACTIVEBACKUP) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
-	{ NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,    ~(BIT (NM_BOND_MODE_TLB)) },
-	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, ~(BIT (NM_BOND_MODE_8023AD)) },
-	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,   ~(BIT (NM_BOND_MODE_8023AD)) },
-	{ NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY,  ~(BIT (NM_BOND_MODE_8023AD)) },
-};
-
-gboolean
-_nm_setting_bond_option_supported (const char *option, NMBondMode mode)
-{
-	guint i;
-
-	for (i = 0; i < G_N_ELEMENTS (bond_unsupp_modes); i++) {
-		if (nm_streq (option, bond_unsupp_modes[i].option))
-			return !NM_FLAGS_ANY (bond_unsupp_modes[i].unsupp_modes, BIT (mode));
-	}
+NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
+	_nm_setting_bond_mode_from_string,
+	NMBondMode,
+	{ g_return_val_if_fail (name, NM_BOND_MODE_UNKNOWN); },
+	{ return NM_BOND_MODE_UNKNOWN; },
+	{ "0",             NM_BOND_MODE_ROUNDROBIN   },
+	{ "1",             NM_BOND_MODE_ACTIVEBACKUP },
+	{ "2",             NM_BOND_MODE_XOR          },
+	{ "3",             NM_BOND_MODE_BROADCAST    },
+	{ "4",             NM_BOND_MODE_8023AD       },
+	{ "5",             NM_BOND_MODE_TLB          },
+	{ "6",             NM_BOND_MODE_ALB          },
+	{ "802.3ad",       NM_BOND_MODE_8023AD       },
+	{ "active-backup", NM_BOND_MODE_ACTIVEBACKUP },
+	{ "balance-alb",   NM_BOND_MODE_ALB          },
+	{ "balance-rr",    NM_BOND_MODE_ROUNDROBIN   },
+	{ "balance-tlb",   NM_BOND_MODE_TLB          },
+	{ "balance-xor",   NM_BOND_MODE_XOR          },
+	{ "broadcast",     NM_BOND_MODE_BROADCAST    },
+);
 
-	return TRUE;
-}
+/*****************************************************************************/
 
 static gboolean
 verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
+	NMSettingBond *self = NM_SETTING_BOND (setting);
 	NMSettingBondPrivate *priv = NM_SETTING_BOND_GET_PRIVATE (setting);
-	GHashTableIter iter;
-	const char *key, *value;
-	int mode, miimon = 0, arp_interval = 0;
-	int num_grat_arp = -1, num_unsol_na = -1;
-	const char *mode_orig, *mode_new;
+	int miimon;
+	int arp_interval;
+	int num_grat_arp;
+	int num_unsol_na;
+	const char *mode_str;
 	const char *arp_ip_target = NULL;
 	const char *lacp_rate;
 	const char *primary;
 	NMBondMode bond_mode;
+	guint i;
+	const NMUtilsNamedValue *n;
 
-	g_hash_table_iter_init (&iter, priv->options);
-	while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) {
-		if (!value[0] || !nm_setting_bond_validate_option (key, value)) {
-			g_set_error (error,
-			             NM_CONNECTION_ERROR,
-			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			             _("invalid option '%s' or its value '%s'"),
-			             key, value);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
-			return FALSE;
+	_ensure_options_idx_cache (priv);
+
+	if (priv->options_idx_cache) {
+		for (i = 0; priv->options_idx_cache[i].name; i++) {
+			n = &priv->options_idx_cache[i];
+
+			if (   !n->value_str
+			    || !nm_setting_bond_validate_option (n->name, n->value_str)) {
+				g_set_error (error,
+				             NM_CONNECTION_ERROR,
+				             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				             _("invalid option '%s' or its value '%s'"),
+				             n->name, n->value_str);
+				g_prefix_error (error,
+				                "%s.%s: ",
+				                NM_SETTING_BOND_SETTING_NAME,
+				                NM_SETTING_BOND_OPTIONS);
+				return FALSE;
+			}
 		}
 	}
 
-	value = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_MIIMON);
-	if (value)
-		miimon = atoi (value);
-	value = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-	if (value)
-		arp_interval = atoi (value);
-	value = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP);
-	if (value)
-		num_grat_arp = atoi (value);
-	value = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA);
-	if (value)
-		num_unsol_na = atoi (value);
-
-	/* Can only set one of miimon and arp_interval */
-	if (miimon > 0 && arp_interval > 0) {
-		g_set_error (error,
-		             NM_CONNECTION_ERROR,
-		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-		             _("only one of '%s' and '%s' can be set"),
-		             NM_SETTING_BOND_OPTION_MIIMON,
-		             NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-		g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
-		return FALSE;
-	}
+	miimon = _atoi (_bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_MIIMON));
+	arp_interval = _atoi (_bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_ARP_INTERVAL));
+	num_grat_arp = _atoi (_bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP));
+	num_unsol_na = _atoi (_bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA));
+
+	/* Option restrictions:
+	 *
+	 * arp_interval conflicts [ alb, tlb ]
+	 * arp_interval needs arp_ip_target
+	 * arp_validate does not work with [ BOND_MODE_8023AD, BOND_MODE_TLB, BOND_MODE_ALB ]
+	 * downdelay needs miimon
+	 * updelay needs miimon
+	 * primary needs [ active-backup, tlb, alb ]
+	 */
 
 	/* Verify bond mode */
-	mode_orig = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_MODE);
-	if (!mode_orig) {
+	mode_str = _bond_get_option (self, NM_SETTING_BOND_OPTION_MODE);
+	if (!mode_str) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -591,34 +791,42 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
 		return FALSE;
 	}
-	mode = nm_utils_bond_mode_string_to_int (mode_orig);
-	if (mode == -1) {
+	bond_mode = _nm_setting_bond_mode_from_string (mode_str);
+	if (bond_mode == NM_BOND_MODE_UNKNOWN) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 		             _("'%s' is not a valid value for '%s'"),
-		             value, NM_SETTING_BOND_OPTION_MODE);
-		g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+		             mode_str,
+		             NM_SETTING_BOND_OPTION_MODE);
+		g_prefix_error (error,
+		                "%s.%s: ",
+		                NM_SETTING_BOND_SETTING_NAME,
+		                NM_SETTING_BOND_OPTIONS);
 		return FALSE;
 	}
-	mode_new = nm_utils_bond_mode_int_to_string (mode);
 
 	/* Make sure mode is compatible with other settings */
-	if (   strcmp (mode_new, "balance-alb") == 0
-	    || strcmp (mode_new, "balance-tlb") == 0) {
+	if (NM_IN_SET (bond_mode, NM_BOND_MODE_TLB,
+	                          NM_BOND_MODE_ALB)) {
 		if (arp_interval > 0) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s=%s' is incompatible with '%s > 0'"),
-			             NM_SETTING_BOND_OPTION_MODE, mode_new, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			             NM_SETTING_BOND_OPTION_MODE,
+			             mode_str,
+			             NM_SETTING_BOND_OPTION_ARP_INTERVAL);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			return FALSE;
 		}
 	}
 
-	primary = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_PRIMARY);
-	if (strcmp (mode_new, "active-backup") == 0) {
+	primary = _bond_get_option (self, NM_SETTING_BOND_OPTION_PRIMARY);
+	if (bond_mode == NM_BOND_MODE_ACTIVEBACKUP) {
 		GError *tmp_error = NULL;
 
 		if (primary && !nm_utils_ifname_valid_kernel (primary, &tmp_error)) {
@@ -627,58 +835,64 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' is not valid for the '%s' option: %s"),
 			             primary, NM_SETTING_BOND_OPTION_PRIMARY, tmp_error->message);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			g_error_free (tmp_error);
 			return FALSE;
 		}
-	} else {
-		if (primary) {
-			g_set_error (error,
-			             NM_CONNECTION_ERROR,
-			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			             _("'%s' option is only valid for '%s=%s'"),
-			             NM_SETTING_BOND_OPTION_PRIMARY,
-			             NM_SETTING_BOND_OPTION_MODE, "active-backup");
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
-			return FALSE;
-		}
+	} else if (primary) {
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             _("'%s' option is only valid for '%s=%s'"),
+		             NM_SETTING_BOND_OPTION_PRIMARY,
+		             NM_SETTING_BOND_OPTION_MODE, "active-backup");
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+		return FALSE;
 	}
 
-	if (connection && nm_connection_get_setting_infiniband (connection)) {
-		if (strcmp (mode_new, "active-backup") != 0) {
+	if (   connection
+	    && nm_connection_get_setting_infiniband (connection)) {
+		if (bond_mode != NM_BOND_MODE_ACTIVEBACKUP) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s=%s' is not a valid configuration for '%s'"),
-			             NM_SETTING_BOND_OPTION_MODE, mode_new, NM_SETTING_INFINIBAND_SETTING_NAME);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			             NM_SETTING_BOND_OPTION_MODE, mode_str, NM_SETTING_INFINIBAND_SETTING_NAME);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
 			return FALSE;
 		}
 	}
 
 	if (miimon == 0) {
-		gpointer delayopt;
-
 		/* updelay and downdelay need miimon to be enabled to be valid */
-		delayopt = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_UPDELAY);
-		if (delayopt && _atoi (delayopt) > 0) {
+		if (_atoi (_bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_UPDELAY))) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' option requires '%s' option to be enabled"),
 			             NM_SETTING_BOND_OPTION_UPDELAY, NM_SETTING_BOND_OPTION_MIIMON);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			return FALSE;
 		}
 
-		delayopt = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_DOWNDELAY);
-		if (delayopt && _atoi (delayopt) > 0) {
+		if (_atoi (_bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_DOWNDELAY))) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' option requires '%s' option to be enabled"),
 			             NM_SETTING_BOND_OPTION_DOWNDELAY, NM_SETTING_BOND_OPTION_MIIMON);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			return FALSE;
 		}
 	}
@@ -686,19 +900,22 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	/* arp_ip_target can only be used with arp_interval, and must
 	 * contain a comma-separated list of IPv4 addresses.
 	 */
-	arp_ip_target = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
+	arp_ip_target = _bond_get_option (self, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
 	if (arp_interval > 0) {
 		char **addrs;
 		guint32 addr;
-		int i;
 
 		if (!arp_ip_target) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' option requires '%s' option to be set"),
-			             NM_SETTING_BOND_OPTION_ARP_INTERVAL, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			             NM_SETTING_BOND_OPTION_ARP_INTERVAL,
+			             NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			return FALSE;
 		}
 
@@ -709,7 +926,9 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' option is empty"),
 			             NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			g_prefix_error (error, "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			g_strfreev (addrs);
 			return FALSE;
 		}
@@ -720,8 +939,12 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 				             NM_CONNECTION_ERROR,
 				             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 				             _("'%s' is not a valid IPv4 address for '%s' option"),
-				             NM_SETTING_BOND_OPTION_ARP_IP_TARGET, addrs[i]);
-				g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+				             NM_SETTING_BOND_OPTION_ARP_IP_TARGET,
+				             addrs[i]);
+				g_prefix_error (error,
+				                "%s.%s: ",
+				                NM_SETTING_BOND_SETTING_NAME,
+				                NM_SETTING_BOND_OPTIONS);
 				g_strfreev (addrs);
 				return FALSE;
 			}
@@ -733,17 +956,19 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' option requires '%s' option to be set"),
-			             NM_SETTING_BOND_OPTION_ARP_IP_TARGET, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			             NM_SETTING_BOND_OPTION_ARP_IP_TARGET,
+			             NM_SETTING_BOND_OPTION_ARP_INTERVAL);
+			g_prefix_error (error, "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			return FALSE;
 		}
 	}
 
-	lacp_rate = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_LACP_RATE);
+	lacp_rate = _bond_get_option (self, NM_SETTING_BOND_OPTION_LACP_RATE);
 	if (   lacp_rate
-	    && g_strcmp0 (mode_new, "802.3ad")
-	    && strcmp (lacp_rate, "slow") != 0
-	    && strcmp (lacp_rate, "0") != 0) {
+	    && bond_mode != NM_BOND_MODE_8023AD
+	    && !NM_IN_STRSET (lacp_rate, "0", "slow")) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -753,8 +978,9 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (   (num_grat_arp != -1 && num_unsol_na != -1)
-	    && (num_grat_arp != num_unsol_na)) {
+	if (   _bond_get_option (self, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP)
+	    && _bond_get_option (self, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA)
+	    && num_grat_arp != num_unsol_na) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -770,7 +996,14 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 
 	/* *** errors above here should be always fatal, below NORMALIZABLE_ERROR *** */
 
-	if (g_strcmp0 (mode_orig, mode_new) != 0) {
+	if (!NM_IN_STRSET (mode_str,
+	                   "802.3ad",
+	                   "active-backup",
+	                   "balance-rr",
+	                   "balance-alb",
+	                   "balance-tlb",
+	                   "balance-xor",
+	                   "broadcast")) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -781,18 +1014,18 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	}
 
 	/* normalize unsupported options for the current mode */
-	bond_mode = _nm_setting_bond_mode_from_string (mode_new);
-	g_hash_table_iter_init (&iter, priv->options);
-	while (g_hash_table_iter_next (&iter, (gpointer) &key, NULL)) {
-		if (nm_streq (key, "mode"))
-			continue;
-		if (!_nm_setting_bond_option_supported (key, bond_mode)) {
+	for (i = 0; priv->options_idx_cache[i].name; i++) {
+		n = &priv->options_idx_cache[i];
+		if (!_nm_setting_bond_option_supported (n->name, bond_mode)) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' option is not valid with mode '%s'"),
-			             key, mode_new);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			             n->name, mode_str);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			return NM_SETTING_VERIFY_NORMALIZABLE;
 		}
 	}
@@ -807,9 +1040,8 @@ options_equal_asym (NMSettingBond *s_bond,
                     NMSettingBond *s_bond2,
                     NMSettingCompareFlags flags)
 {
-	GHashTable *options2 = NM_SETTING_BOND_GET_PRIVATE (s_bond2)->options;
 	GHashTableIter iter;
-	const char *key, *value, *value2;
+	const char *key, *value;
 
 	g_hash_table_iter_init (&iter, NM_SETTING_BOND_GET_PRIVATE (s_bond)->options);
 	while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) {
@@ -824,18 +1056,7 @@ options_equal_asym (NMSettingBond *s_bond,
 				continue;
 		}
 
-		value2 = g_hash_table_lookup (options2, key);
-
-		if (!value2) {
-			if (nm_streq (key, "num_grat_arp"))
-				value2 = g_hash_table_lookup (options2, "num_unsol_na");
-			else if (nm_streq (key, "num_unsol_na"))
-				value2 = g_hash_table_lookup (options2, "num_grat_arp");
-		}
-
-		if (!value2)
-			value2 = nm_setting_bond_get_option_default (s_bond2, key);
-		if (!nm_streq (value, value2))
+		if (!nm_streq0 (value, _bond_get_option (s_bond2, key)))
 			return FALSE;
 	}