diff --git a/src/libnm-client-impl/libnm.ver b/src/libnm-client-impl/libnm.ver
index 9a6c74be56..66538c5e29 100644
--- a/src/libnm-client-impl/libnm.ver
+++ b/src/libnm-client-impl/libnm.ver
@@ -1898,5 +1898,11 @@ global:
nm_setting_loopback_get_type;
nm_setting_loopback_new;
nm_setting_ovs_interface_get_ofport_request;
+ nm_setting_ovs_port_add_trunk;
+ nm_setting_ovs_port_clear_trunks;
+ nm_setting_ovs_port_get_num_trunks;
+ nm_setting_ovs_port_get_trunk;
+ nm_setting_ovs_port_remove_trunk;
+ nm_setting_ovs_port_remove_trunk_by_value;
nm_utils_ensure_gtypes;
} libnm_1_40_0;
diff --git a/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in b/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in
index 42fd7c4773..781f7610ec 100644
--- a/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in
+++ b/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in
@@ -1898,6 +1898,10 @@
dbus-type="u"
gprop-type="guint"
/>
+
keyfile, nm_setting_get_name(setting), key, NULL);
+ if (!value || !value[0])
+ return;
+
+ ranges = g_ptr_array_new_with_free_func((GDestroyNotify) nm_range_unref);
+
+ strv = nm_utils_escaped_tokens_split(value, ",");
+ if (strv) {
+ for (iter = strv; *iter; iter++) {
+ range = nm_range_from_str(*iter, &local);
+ if (!range) {
+ read_handle_warn(info,
+ key,
+ key,
+ NM_KEYFILE_WARN_SEVERITY_WARN,
+ "invalid range: %s",
+ local->message);
+ g_clear_error(&local);
+ continue;
+ }
+ g_ptr_array_add(ranges, range);
+ }
+ }
+
+ if (ranges->len > 0)
+ g_object_set(setting, key, ranges, NULL);
+}
+
static void
qdisc_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
{
@@ -2344,6 +2382,33 @@ bridge_vlan_writer(KeyfileWriterInfo *info,
}
}
+static void
+range_list_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value)
+{
+ GPtrArray *ranges;
+
+ ranges = g_value_get_boxed(value);
+ if (ranges && ranges->len > 0) {
+ const guint string_initial_size = ranges->len * 10u;
+ nm_auto_str_buf NMStrBuf string = NM_STR_BUF_INIT(string_initial_size, FALSE);
+ guint i;
+
+ for (i = 0; i < ranges->len; i++) {
+ gs_free char *range_str = NULL;
+
+ range_str = nm_range_to_str(ranges->pdata[i]);
+ if (i > 0)
+ nm_str_buf_append_c(&string, ',');
+ nm_utils_escaped_tokens_escape_strbuf_assert(range_str, ",", &string);
+ }
+
+ nm_keyfile_plugin_kf_set_string(info->keyfile,
+ nm_setting_get_name(setting),
+ key,
+ nm_str_buf_get_str(&string));
+ }
+}
+
static void
wired_s390_options_parser_full(KeyfileReaderInfo *info,
const NMMetaSettingInfo *setting_info,
@@ -2933,6 +2998,12 @@ static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = {
.parser_no_check_key = TRUE,
.parser = bridge_vlan_parser,
.writer = bridge_vlan_writer, ), ), ),
+ PARSE_INFO_SETTING(
+ NM_META_SETTING_TYPE_OVS_PORT,
+ PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_OVS_PORT_TRUNKS,
+ .parser_no_check_key = TRUE,
+ .parser = range_list_parser,
+ .writer = range_list_writer, ), ), ),
PARSE_INFO_SETTING(
NM_META_SETTING_TYPE_BRIDGE_PORT,
PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_BRIDGE_PORT_VLANS,
diff --git a/src/libnm-core-impl/nm-setting-ovs-port.c b/src/libnm-core-impl/nm-setting-ovs-port.c
index ab9c0a1141..840221edbc 100644
--- a/src/libnm-core-impl/nm-setting-ovs-port.c
+++ b/src/libnm-core-impl/nm-setting-ovs-port.c
@@ -21,12 +21,14 @@
/*****************************************************************************/
-NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_VLAN_MODE,
- PROP_TAG,
- PROP_LACP,
- PROP_BOND_MODE,
- PROP_BOND_UPDELAY,
- PROP_BOND_DOWNDELAY, );
+NM_GOBJECT_PROPERTIES_DEFINE(NMSettingOvsPort,
+ PROP_VLAN_MODE,
+ PROP_TAG,
+ PROP_TRUNKS,
+ PROP_LACP,
+ PROP_BOND_MODE,
+ PROP_BOND_UPDELAY,
+ PROP_BOND_DOWNDELAY, );
/**
* NMSettingOvsPort:
@@ -36,12 +38,13 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_VLAN_MODE,
struct _NMSettingOvsPort {
NMSetting parent;
- char *vlan_mode;
- char *lacp;
- char *bond_mode;
- guint32 tag;
- guint32 bond_updelay;
- guint32 bond_downdelay;
+ GPtrArray *trunks;
+ char *vlan_mode;
+ char *lacp;
+ char *bond_mode;
+ guint32 tag;
+ guint32 bond_updelay;
+ guint32 bond_downdelay;
};
struct _NMSettingOvsPortClass {
@@ -84,6 +87,143 @@ nm_setting_ovs_port_get_tag(NMSettingOvsPort *self)
return self->tag;
}
+/*****************************************************************************/
+
+/**
+ * nm_setting_ovs_port_add_trunk:
+ * @setting: the #NMSettingOvsPort
+ * @trunk: the trunk to add
+ *
+ * Appends a new trunk range to the setting.
+ * This takes a reference to @trunk.
+ *
+ * Since: 1.42
+ **/
+void
+nm_setting_ovs_port_add_trunk(NMSettingOvsPort *self, NMRange *trunk)
+{
+ g_return_if_fail(NM_IS_SETTING_OVS_PORT(self));
+ g_return_if_fail(trunk);
+
+ g_ptr_array_add(self->trunks, nm_range_ref(trunk));
+ _notify(self, PROP_TRUNKS);
+}
+
+/**
+ * nm_setting_ovs_port_get_num_trunks:
+ * @setting: the #NMSettingOvsPort
+ *
+ * Returns: the number of trunk ranges
+ *
+ * Since: 1.42
+ **/
+guint
+nm_setting_ovs_port_get_num_trunks(NMSettingOvsPort *self)
+{
+ g_return_val_if_fail(NM_IS_SETTING_OVS_PORT(self), 0);
+
+ return self->trunks->len;
+}
+
+/**
+ * nm_setting_ovs_port_get_trunk:
+ * @setting: the #NMSettingOvsPort
+ * @idx: index number of the trunk range to return
+ *
+ * Returns: (transfer none): the trunk range at index @idx
+ *
+ * Since: 1.42
+ **/
+NMRange *
+nm_setting_ovs_port_get_trunk(NMSettingOvsPort *self, guint idx)
+{
+ g_return_val_if_fail(NM_IS_SETTING_OVS_PORT(self), NULL);
+
+ g_return_val_if_fail(idx < self->trunks->len, NULL);
+
+ return self->trunks->pdata[idx];
+}
+
+/**
+ * nm_setting_ovs_port_remove_trunk:
+ * @setting: the #NMSettingOvsPort
+ * @idx: index number of the trunk range.
+ *
+ * Removes the trunk range at index @idx.
+ *
+ * Since: 1.42
+ **/
+void
+nm_setting_ovs_port_remove_trunk(NMSettingOvsPort *self, guint idx)
+{
+ g_return_if_fail(NM_IS_SETTING_OVS_PORT(self));
+
+ g_return_if_fail(idx < self->trunks->len);
+
+ g_ptr_array_remove_index(self->trunks, idx);
+ _notify(self, PROP_TRUNKS);
+}
+
+/**
+ * nm_setting_ovs_port_remove_trunk_by_value:
+ * @setting: the #NMSettingOvsPort
+ * @start: the trunk range start index
+ * @end: the trunk range end index
+ *
+ * Remove the trunk range with range @start to @end.
+ *
+ * Returns: %TRUE if the trunk range was found and removed; %FALSE otherwise
+ *
+ * Since: 1.42
+ **/
+gboolean
+nm_setting_ovs_port_remove_trunk_by_value(NMSettingOvsPort *self, guint start, guint end)
+{
+ NMRange *trunk;
+ guint i;
+
+ g_return_val_if_fail(NM_IS_SETTING_OVS_PORT(self), FALSE);
+
+ for (i = 0; i < self->trunks->len; i++) {
+ trunk = (NMRange *) self->trunks->pdata[i];
+ if (trunk->start == start && trunk->end == end) {
+ g_ptr_array_remove_index(self->trunks, i);
+ _notify(self, PROP_TRUNKS);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * nm_setting_ovs_port_clear_trunks:
+ * @setting: the #NMSettingOvsPort
+ *
+ * Removes all configured trunk ranges.
+ *
+ * Since: 1.42
+ **/
+void
+nm_setting_ovs_port_clear_trunks(NMSettingOvsPort *self)
+{
+ g_return_if_fail(NM_IS_SETTING_OVS_PORT(self));
+
+ if (self->trunks->len != 0) {
+ g_ptr_array_set_size(self->trunks, 0);
+ _notify(self, PROP_TRUNKS);
+ }
+}
+
+const GPtrArray *
+_nm_setting_ovs_port_get_trunks_arr(NMSettingOvsPort *self)
+{
+ g_return_val_if_fail(NM_IS_SETTING_OVS_PORT(self), NULL);
+
+ return self->trunks;
+}
+
+/*****************************************************************************/
+
/**
* nm_setting_ovs_port_get_lacp:
* @self: the #NMSettingOvsPort
@@ -150,6 +290,107 @@ nm_setting_ovs_port_get_bond_downdelay(NMSettingOvsPort *self)
/*****************************************************************************/
+static int
+range_cmp(gconstpointer a, gconstpointer b)
+{
+ const NMRange *range_a = *(const NMRange **) a;
+ const NMRange *range_b = *(const NMRange **) b;
+
+ return nm_range_cmp(range_a, range_b);
+}
+
+gboolean
+_nm_setting_ovs_port_sort_trunks(NMSettingOvsPort *self)
+{
+ gboolean need_sort = FALSE;
+ guint i;
+
+ for (i = 1; i < self->trunks->len; i++) {
+ NMRange *range_prev = self->trunks->pdata[i - 1];
+ NMRange *range = self->trunks->pdata[i];
+
+ if (nm_range_cmp(range_prev, range) > 0) {
+ need_sort = TRUE;
+ break;
+ }
+ }
+
+ if (need_sort) {
+ g_ptr_array_sort(self->trunks, range_cmp);
+ _notify(self, PROP_TRUNKS);
+ }
+
+ return need_sort;
+}
+
+static gboolean
+verify_trunks(GPtrArray *ranges, GError **error)
+{
+ gs_unref_hashtable GHashTable *h = NULL;
+ NMRange *range;
+ guint i;
+ guint vlan;
+
+ if (!ranges)
+ return TRUE;
+
+ h = g_hash_table_new(nm_direct_hash, NULL);
+
+ for (i = 0; i < ranges->len; i++) {
+ range = ranges->pdata[i];
+ nm_assert(range->start <= range->end);
+
+ if (range->start > 4095 || range->end > 4095) {
+ g_set_error_literal(error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("VLANs must be between 0 and 4095"));
+ return FALSE;
+ }
+
+ for (vlan = range->start; vlan <= range->end; vlan++) {
+ if (!nm_g_hash_table_add(h, GUINT_TO_POINTER(vlan))) {
+ g_set_error(error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("duplicate VLAN %u"),
+ vlan);
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+verify_trunks_normalizable(GPtrArray *ranges, GError **error)
+{
+ guint i;
+
+ nm_assert(verify_trunks(ranges, NULL));
+
+ if (!ranges || ranges->len <= 1)
+ return TRUE;
+
+ for (i = 1; i < ranges->len; i++) {
+ NMRange *range_prev = ranges->pdata[i - 1];
+ NMRange *range = ranges->pdata[i];
+
+ if (nm_range_cmp(range_prev, range) > 0) {
+ g_set_error(error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("VLANs %u and %u are not sorted in ascending order"),
+ (guint) range_prev->start,
+ (guint) range->start);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
static int
verify(NMSetting *setting, NMConnection *connection, GError **error)
{
@@ -257,14 +498,68 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
return FALSE;
}
+ if (!verify_trunks(self->trunks, error)) {
+ g_prefix_error(error,
+ "%s.%s: ",
+ NM_SETTING_OVS_PORT_SETTING_NAME,
+ NM_SETTING_OVS_PORT_TRUNKS);
+ return FALSE;
+ }
+
+ if (!verify_trunks_normalizable(self->trunks, error)) {
+ g_prefix_error(error,
+ "%s.%s: ",
+ NM_SETTING_OVS_PORT_SETTING_NAME,
+ NM_SETTING_OVS_PORT_TRUNKS);
+ return NM_SETTING_VERIFY_NORMALIZABLE;
+ }
+
return TRUE;
}
/*****************************************************************************/
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ NMSettingOvsPort *self = NM_SETTING_OVS_PORT(object);
+
+ switch (prop_id) {
+ case PROP_TRUNKS:
+ g_value_take_boxed(value,
+ _nm_utils_copy_array(self->trunks,
+ (NMUtilsCopyFunc) nm_range_ref,
+ (GDestroyNotify) nm_range_unref));
+ break;
+ default:
+ _nm_setting_property_get_property_direct(object, prop_id, value, pspec);
+ break;
+ }
+}
+
+static void
+set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ NMSettingOvsPort *self = NM_SETTING_OVS_PORT(object);
+
+ switch (prop_id) {
+ case PROP_TRUNKS:
+ g_ptr_array_unref(self->trunks);
+ self->trunks = _nm_utils_copy_array(g_value_get_boxed(value),
+ (NMUtilsCopyFunc) nm_range_ref,
+ (GDestroyNotify) nm_range_unref);
+ break;
+ default:
+ _nm_setting_property_set_property_direct(object, prop_id, value, pspec);
+ break;
+ }
+}
+
static void
nm_setting_ovs_port_init(NMSettingOvsPort *self)
-{}
+{
+ self->trunks = g_ptr_array_new_with_free_func((GDestroyNotify) nm_range_unref);
+}
/**
* nm_setting_ovs_port_new:
@@ -281,6 +576,16 @@ nm_setting_ovs_port_new(void)
return g_object_new(NM_TYPE_SETTING_OVS_PORT, NULL);
}
+static void
+finalize(GObject *object)
+{
+ NMSettingOvsPort *self = NM_SETTING_OVS_PORT(object);
+
+ g_ptr_array_unref(self->trunks);
+
+ G_OBJECT_CLASS(nm_setting_ovs_port_parent_class)->finalize(object);
+}
+
static void
nm_setting_ovs_port_class_init(NMSettingOvsPortClass *klass)
{
@@ -288,8 +593,9 @@ nm_setting_ovs_port_class_init(NMSettingOvsPortClass *klass)
NMSettingClass *setting_class = NM_SETTING_CLASS(klass);
GArray *properties_override = _nm_sett_info_property_override_create_array();
- object_class->get_property = _nm_setting_property_get_property_direct;
- object_class->set_property = _nm_setting_property_set_property_direct;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->finalize = finalize;
setting_class->verify = verify;
@@ -327,6 +633,31 @@ nm_setting_ovs_port_class_init(NMSettingOvsPortClass *klass)
NMSettingOvsPort,
tag);
+ /**
+ * NMSettingOvsPort:trunks: (type GPtrArray(NMRange))
+ *
+ * A list of VLAN ranges that this port trunks.
+ *
+ * The property is valid only for ports with mode "trunk",
+ * "native-tagged", or "native-untagged port".
+ * If it is empty, the port trunks all VLANs.
+ *
+ * Since: 1.42
+ **/
+ obj_properties[PROP_TRUNKS] = g_param_spec_boxed(NM_SETTING_OVS_PORT_TRUNKS,
+ "",
+ "",
+ G_TYPE_PTR_ARRAY,
+ G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE
+ | G_PARAM_STATIC_STRINGS);
+ _nm_properties_override_gobj(
+ properties_override,
+ obj_properties[PROP_TRUNKS],
+ NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aa{sv}"),
+ .to_dbus_fcn = _nm_utils_ranges_to_dbus,
+ .compare_fcn = _nm_utils_ranges_cmp,
+ .from_dbus_fcn = _nm_utils_ranges_from_dbus));
+
/**
* NMSettingOvsPort:lacp:
*
diff --git a/src/libnm-core-impl/nm-setting-private.h b/src/libnm-core-impl/nm-setting-private.h
index 6791d199e6..e7a26c5d3b 100644
--- a/src/libnm-core-impl/nm-setting-private.h
+++ b/src/libnm-core-impl/nm-setting-private.h
@@ -1073,6 +1073,12 @@ gboolean _nm_utils_bridge_vlans_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS
GVariant *_nm_utils_bridge_vlans_to_dbus(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil);
+gboolean _nm_utils_ranges_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil);
+
+NMTernary _nm_utils_ranges_cmp(_NM_SETT_INFO_PROP_COMPARE_FCN_ARGS _nm_nil);
+
+GVariant *_nm_utils_ranges_to_dbus(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil);
+
NMTernary _nm_setting_ip_config_compare_fcn_addresses(_NM_SETT_INFO_PROP_COMPARE_FCN_ARGS _nm_nil);
NMTernary _nm_setting_ip_config_compare_fcn_routes(_NM_SETT_INFO_PROP_COMPARE_FCN_ARGS _nm_nil);
diff --git a/src/libnm-core-impl/nm-utils.c b/src/libnm-core-impl/nm-utils.c
index b27d839ffe..daac3f73c4 100644
--- a/src/libnm-core-impl/nm-utils.c
+++ b/src/libnm-core-impl/nm-utils.c
@@ -5572,6 +5572,97 @@ _nm_utils_bridge_vlan_verify_list(GPtrArray *vlans,
return TRUE;
}
+GVariant *
+_nm_utils_ranges_to_dbus(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil)
+{
+ gs_unref_ptrarray GPtrArray *ranges = NULL;
+ GVariantBuilder builder;
+ const char *property_name = property_info->name;
+ guint i;
+
+ nm_assert(property_name);
+
+ g_object_get(setting, property_name, &ranges, NULL);
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}"));
+
+ if (ranges) {
+ for (i = 0; i < ranges->len; i++) {
+ NMRange *range = ranges->pdata[i];
+ GVariantBuilder range_builder;
+
+ g_variant_builder_init(&range_builder, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add(&range_builder,
+ "{sv}",
+ "start",
+ g_variant_new_uint64(range->start));
+ g_variant_builder_add(&range_builder, "{sv}", "end", g_variant_new_uint64(range->end));
+
+ g_variant_builder_add(&builder, "a{sv}", &range_builder);
+ }
+ }
+
+ return g_variant_builder_end(&builder);
+}
+
+gboolean
+_nm_utils_ranges_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
+{
+ gs_unref_ptrarray GPtrArray *ranges = NULL;
+ GVariantIter iter;
+ GVariant *range_var;
+
+ g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), FALSE);
+
+ ranges = g_ptr_array_new_with_free_func((GDestroyNotify) nm_range_unref);
+ g_variant_iter_init(&iter, value);
+ while (g_variant_iter_next(&iter, "@a{sv}", &range_var)) {
+ _nm_unused gs_unref_variant GVariant *var_unref = range_var;
+ gint64 start;
+ gint64 end;
+
+ if (!g_variant_lookup(range_var, "start", "t", &start))
+ continue;
+ if (!g_variant_lookup(range_var, "end", "t", &end))
+ continue;
+ if (start > end)
+ continue;
+
+ g_ptr_array_add(ranges, nm_range_new(start, end));
+ }
+
+ g_object_set(setting, property_info->name, ranges, NULL);
+
+ return TRUE;
+}
+
+NMTernary
+_nm_utils_ranges_cmp(_NM_SETT_INFO_PROP_COMPARE_FCN_ARGS _nm_nil)
+{
+ const GPtrArray *ranges_a = NULL;
+ const GPtrArray *ranges_b = NULL;
+ guint len;
+ guint i;
+
+ if (nm_streq0(nm_setting_get_name(set_a), NM_SETTING_OVS_PORT_SETTING_NAME)
+ && nm_streq0(property_info->name, NM_SETTING_OVS_PORT_TRUNKS)) {
+ ranges_a = _nm_setting_ovs_port_get_trunks_arr(NM_SETTING_OVS_PORT(set_a));
+ if (set_b)
+ ranges_b = _nm_setting_ovs_port_get_trunks_arr(NM_SETTING_OVS_PORT(set_b));
+ } else {
+ nm_assert_not_reached();
+ }
+
+ len = nm_g_ptr_array_len(ranges_a);
+ if (len != nm_g_ptr_array_len(ranges_b))
+ return FALSE;
+ for (i = 0; i < len; i++) {
+ if (nm_range_cmp(ranges_a->pdata[i], ranges_b->pdata[i]))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
gboolean
_nm_utils_iaid_verify(const char *str, gint64 *out_value)
{
diff --git a/src/libnm-core-intern/nm-core-internal.h b/src/libnm-core-intern/nm-core-internal.h
index ba28bc75f1..5d07f2c2c8 100644
--- a/src/libnm-core-intern/nm-core-internal.h
+++ b/src/libnm-core-intern/nm-core-internal.h
@@ -566,6 +566,7 @@ gboolean _nm_utils_dhcp_duid_valid(const char *duid, GBytes **out_duid_bin);
gboolean _nm_setting_sriov_sort_vfs(NMSettingSriov *setting);
gboolean _nm_setting_bridge_port_sort_vlans(NMSettingBridgePort *setting);
gboolean _nm_setting_bridge_sort_vlans(NMSettingBridge *setting);
+gboolean _nm_setting_ovs_port_sort_trunks(NMSettingOvsPort *self);
/*****************************************************************************/
@@ -1067,4 +1068,6 @@ GPtrArray *_nm_setting_ip_config_get_dns_array(NMSettingIPConfig *setting);
gboolean nm_connection_need_secrets_for_rerequest(NMConnection *connection);
+const GPtrArray *_nm_setting_ovs_port_get_trunks_arr(NMSettingOvsPort *self);
+
#endif
diff --git a/src/libnm-core-public/nm-setting-ovs-port.h b/src/libnm-core-public/nm-setting-ovs-port.h
index eda23d3e7b..4a452c0c56 100644
--- a/src/libnm-core-public/nm-setting-ovs-port.h
+++ b/src/libnm-core-public/nm-setting-ovs-port.h
@@ -29,6 +29,7 @@ G_BEGIN_DECLS
#define NM_SETTING_OVS_PORT_VLAN_MODE "vlan-mode"
#define NM_SETTING_OVS_PORT_TAG "tag"
+#define NM_SETTING_OVS_PORT_TRUNKS "trunks"
#define NM_SETTING_OVS_PORT_LACP "lacp"
#define NM_SETTING_OVS_PORT_BOND_MODE "bond-mode"
#define NM_SETTING_OVS_PORT_BOND_UPDELAY "bond-updelay"
@@ -54,6 +55,20 @@ guint nm_setting_ovs_port_get_bond_updelay(NMSettingOvsPort *self);
NM_AVAILABLE_IN_1_10
guint nm_setting_ovs_port_get_bond_downdelay(NMSettingOvsPort *self);
+NM_AVAILABLE_IN_1_42
+void nm_setting_ovs_port_add_trunk(NMSettingOvsPort *setting, NMRange *trunk);
+NM_AVAILABLE_IN_1_42
+guint nm_setting_ovs_port_get_num_trunks(NMSettingOvsPort *setting);
+NM_AVAILABLE_IN_1_42
+NMRange *nm_setting_ovs_port_get_trunk(NMSettingOvsPort *setting, guint idx);
+NM_AVAILABLE_IN_1_42
+void nm_setting_ovs_port_remove_trunk(NMSettingOvsPort *setting, guint idx);
+NM_AVAILABLE_IN_1_42
+gboolean
+nm_setting_ovs_port_remove_trunk_by_value(NMSettingOvsPort *setting, guint start, guint end);
+NM_AVAILABLE_IN_1_42
+void nm_setting_ovs_port_clear_trunks(NMSettingOvsPort *setting);
+
G_END_DECLS
#endif /* __NM_SETTING_OVS_PORT_H__ */
diff --git a/src/libnmc-setting/nm-meta-setting-desc.c b/src/libnmc-setting/nm-meta-setting-desc.c
index 40dd28f31b..e1f1bf3a04 100644
--- a/src/libnmc-setting/nm-meta-setting-desc.c
+++ b/src/libnmc-setting/nm-meta-setting-desc.c
@@ -3767,6 +3767,20 @@ _objlist_obj_to_str_fcn_tc_config_qdiscs(NMMetaAccessorGetType get_type,
g_string_append(str, s);
}
+static void
+_objlist_obj_to_str_fcn_ovs_port_trunks(NMMetaAccessorGetType get_type,
+ NMSetting *setting,
+ guint idx,
+ GString *str)
+{
+ gs_free char *s = NULL;
+ NMRange *trunk;
+
+ trunk = nm_setting_ovs_port_get_trunk(NM_SETTING_OVS_PORT(setting), idx);
+ s = nm_range_to_str(trunk);
+ nm_utils_escaped_tokens_escape_gstr_assert(s, ESCAPED_TOKENS_DELIMITERS, str);
+}
+
static void
_objlist_obj_to_str_fcn_bridge_vlans(NMMetaAccessorGetType get_type,
NMSetting *setting,
@@ -3835,6 +3849,37 @@ _objlist_set_fcn_tc_config_qdiscs(NMSetting *setting,
return TRUE;
}
+static gboolean
+_objlist_set_fcn_ovs_port_trunks(NMSetting *setting,
+ gboolean do_add,
+ const char *value,
+ GError **error)
+{
+ NMRange *range;
+ gs_free_error GError *local = NULL;
+ guint64 start;
+ guint64 end;
+
+ range = nm_range_from_str(value, &local);
+ if (!range) {
+ nm_utils_error_set(error,
+ NM_UTILS_ERROR_INVALID_ARGUMENT,
+ "%s. %s",
+ local->message,
+ _("The valid syntax is: '' or '-"));
+ return FALSE;
+ }
+
+ if (do_add)
+ nm_setting_ovs_port_add_trunk(NM_SETTING_OVS_PORT(setting), range);
+ else {
+ nm_range_get_range(range, &start, &end);
+ nm_setting_ovs_port_remove_trunk_by_value(NM_SETTING_OVS_PORT(setting), start, end);
+ }
+
+ return TRUE;
+}
+
static gboolean
_objlist_set_fcn_bridge_vlans(NMSetting *setting,
gboolean do_add,
@@ -6836,6 +6881,17 @@ static const NMMetaPropertyInfo *const property_infos_OVS_PORT[] = {
.values_static = NM_MAKE_STRV ("access", "native-tagged", "native-untagged", "trunk"),
),
),
+ PROPERTY_INFO_WITH_DESC (NM_SETTING_OVS_PORT_TRUNKS,
+ .property_type = &_pt_objlist,
+ .property_typ_data = DEFINE_PROPERTY_TYP_DATA (
+ PROPERTY_TYP_DATA_SUBTYPE (objlist,
+ .get_num_fcn = OBJLIST_GET_NUM_FCN (NMSettingOvsPort, nm_setting_ovs_port_get_num_trunks),
+ .clear_all_fcn = OBJLIST_CLEAR_ALL_FCN (NMSettingOvsPort, nm_setting_ovs_port_clear_trunks),
+ .obj_to_str_fcn = _objlist_obj_to_str_fcn_ovs_port_trunks,
+ .set_fcn = _objlist_set_fcn_ovs_port_trunks,
+ ),
+ ),
+ ),
PROPERTY_INFO_WITH_DESC (NM_SETTING_OVS_PORT_TAG,
.property_type = &_pt_gobject_int,
),
diff --git a/src/libnmc-setting/settings-docs.h.in b/src/libnmc-setting/settings-docs.h.in
index 2f2d561297..11806bf3ca 100644
--- a/src/libnmc-setting/settings-docs.h.in
+++ b/src/libnmc-setting/settings-docs.h.in
@@ -260,6 +260,7 @@
#define DESCRIBE_DOC_NM_SETTING_OVS_PORT_BOND_UPDELAY N_("The time port must be active before it starts forwarding traffic.")
#define DESCRIBE_DOC_NM_SETTING_OVS_PORT_LACP N_("LACP mode. One of \"active\", \"off\", or \"passive\".")
#define DESCRIBE_DOC_NM_SETTING_OVS_PORT_TAG N_("The VLAN tag in the range 0-4095.")
+#define DESCRIBE_DOC_NM_SETTING_OVS_PORT_TRUNKS N_("A list of VLAN ranges that this port trunks. The property is valid only for ports with mode \"trunk\", \"native-tagged\", or \"native-untagged port\". If it is empty, the port trunks all VLANs.")
#define DESCRIBE_DOC_NM_SETTING_OVS_PORT_VLAN_MODE N_("The VLAN mode. One of \"access\", \"native-tagged\", \"native-untagged\", \"trunk\" or unset.")
#define DESCRIBE_DOC_NM_SETTING_PPP_BAUD N_("If non-zero, instruct pppd to set the serial port to the specified baudrate. This value should normally be left as 0 to automatically choose the speed.")
#define DESCRIBE_DOC_NM_SETTING_PPP_CRTSCTS N_("If TRUE, specify that pppd should set the serial port to use hardware flow control with RTS and CTS signals. This value should normally be set to FALSE.")
diff --git a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in
index 41be4a8e8c..c85ef11947 100644
--- a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in
+++ b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in
@@ -849,6 +849,8 @@
+