diff --git a/Makefile.am b/Makefile.am index 1bfe2459f4..f957816141 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3045,6 +3045,7 @@ EXTRA_DIST += \ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected \ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc \ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected \ + src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected \ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1 \ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2 \ src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid \ diff --git a/clients/common/settings-docs.h.in b/clients/common/settings-docs.h.in index c10b6975a6..e7f98e2dd5 100644 --- a/clients/common/settings-docs.h.in +++ b/clients/common/settings-docs.h.in @@ -312,8 +312,8 @@ #define DESCRIBE_DOC_NM_SETTING_SRIOV_AUTOPROBE_DRIVERS N_("Whether to autoprobe virtual functions by a compatible driver. If set to NM_TERNARY_TRUE (1), the kernel will try to bind VFs to a compatible driver and if this succeeds a new network interface will be instantiated for each VF. If set to NM_TERNARY_FALSE (0), VFs will not be claimed and no network interfaces will be created for them. When set to NM_TERNARY_DEFAULT (-1), the global default is used; in case the global default is unspecified it is assumed to be NM_TERNARY_TRUE (1).") #define DESCRIBE_DOC_NM_SETTING_SRIOV_TOTAL_VFS N_("The total number of virtual functions to create. Note that when the sriov setting is present NetworkManager enforces the number of virtual functions on the interface also when it is zero. To prevent any changes to SR-IOV parameters don't add a sriov setting to the connection.") #define DESCRIBE_DOC_NM_SETTING_SRIOV_VFS N_("Array of virtual function descriptors. Each VF descriptor is a dictionary mapping attribute names to GVariant values. The 'index' entry is mandatory for each VF. When represented as string a VF is in the form: \"INDEX [ATTR=VALUE[ ATTR=VALUE]...]\". for example: \"2 mac=00:11:22:33:44:55 spoof-check=true\". Multiple VFs can be specified using a comma as separator. Currently the following attributes are supported: mac, spoof-check, trust, min-tx-rate, max-tx-rate, vlans. The \"vlans\" attribute is represented as a semicolon-separated list of VLAN descriptors, where each descriptor has the form \"ID[.PRIORITY[.PROTO]]\". PROTO can be either 'q' for 802.1Q (the default) or 'ad' for 802.1ad.") -#define DESCRIBE_DOC_NM_SETTING_TC_CONFIG_QDISCS N_("Array of TC queueing disciplines.") -#define DESCRIBE_DOC_NM_SETTING_TC_CONFIG_TFILTERS N_("Array of TC traffic filters.") +#define DESCRIBE_DOC_NM_SETTING_TC_CONFIG_QDISCS N_("Array of TC queueing disciplines. When the \"tc\" setting is present, qdiscs from this property are applied upon activation. If the property is empty, all qdiscs are removed and the device will only have the default qdisc assigned by kernel according to the \"net.core.default_qdisc\" sysctl. If the \"tc\" setting is not present, NetworkManager doesn't touch the qdiscs present on the interface.") +#define DESCRIBE_DOC_NM_SETTING_TC_CONFIG_TFILTERS N_("Array of TC traffic filters. When the \"tc\" setting is present, filters from this property are applied upon activation. If the property is empty, NetworkManager removes all the filters. If the \"tc\" setting is not present, NetworkManager doesn't touch the filters present on the interface.") #define DESCRIBE_DOC_NM_SETTING_TEAM_CONFIG N_("The JSON configuration for the team network interface. The property should contain raw JSON configuration data suitable for teamd, because the value is passed directly to teamd. If not specified, the default configuration is used. See man teamd.conf for the format details.") #define DESCRIBE_DOC_NM_SETTING_TEAM_LINK_WATCHERS N_("Link watchers configuration for the connection: each link watcher is defined by a dictionary, whose keys depend upon the selected link watcher. Available link watchers are 'ethtool', 'nsna_ping' and 'arp_ping' and it is specified in the dictionary with the key 'name'. Available keys are: ethtool: 'delay-up', 'delay-down', 'init-wait'; nsna_ping: 'init-wait', 'interval', 'missed-max', 'target-host'; arp_ping: all the ones in nsna_ping and 'source-host', 'validate-active', 'validate-inactive', 'send-always'. See teamd.conf man for more details.") #define DESCRIBE_DOC_NM_SETTING_TEAM_MCAST_REJOIN_COUNT N_("Corresponds to the teamd mcast_rejoin.count.") diff --git a/libnm-core/nm-setting-tc-config.c b/libnm-core/nm-setting-tc-config.c index af503741dd..93987edf16 100644 --- a/libnm-core/nm-setting-tc-config.c +++ b/libnm-core/nm-setting-tc-config.c @@ -1786,11 +1786,23 @@ nm_setting_tc_config_class_init (NMSettingTCConfigClass *klass) * NMSettingTCConfig:qdiscs: (type GPtrArray(NMTCQdisc)) * * Array of TC queueing disciplines. + * + * When the #NMSettingTCConfig setting is present, qdiscs from this + * property are applied upon activation. If the property is empty, + * all qdiscs are removed and the device will only + * have the default qdisc assigned by kernel according to the + * "net.core.default_qdisc" sysctl. + * + * If the #NMSettingTCConfig setting is not present, NetworkManager + * doesn't touch the qdiscs present on the interface. **/ /* ---ifcfg-rh--- * property: qdiscs - * variable: QDISC1(+), QDISC2(+), ... - * description: Queueing disciplines + * variable: QDISC1(+), QDISC2(+), ..., TC_COMMIT(+) + * description: Queueing disciplines to set on the interface. When no + * QDISC1, QDISC2, ..., FILTER1, FILTER2, ... keys are present, + * NetworkManager doesn't touch qdiscs and filters present on the + * interface, unless TC_COMMIT is set to 'yes'. * example: QDISC1=ingress, QDISC2="root handle 1234: fq_codel" * ---end--- */ @@ -1812,11 +1824,21 @@ nm_setting_tc_config_class_init (NMSettingTCConfigClass *klass) * NMSettingTCConfig:tfilters: (type GPtrArray(NMTCTfilter)) * * Array of TC traffic filters. + * + * When the #NMSettingTCConfig setting is present, filters from this + * property are applied upon activation. If the property is empty, + * NetworkManager removes all the filters. + * + * If the #NMSettingTCConfig setting is not present, NetworkManager + * doesn't touch the filters present on the interface. **/ /* ---ifcfg-rh--- * property: qdiscs - * variable: FILTER1(+), FILTER2(+), ... - * description: Traffic filters + * variable: FILTER1(+), FILTER2(+), ..., TC_COMMIT(+) + * description: Traffic filters to set on the interface. When no + * QDISC1, QDISC2, ..., FILTER1, FILTER2, ... keys are present, + * NetworkManager doesn't touch qdiscs and filters present on the + * interface, unless TC_COMMIT is set to 'yes'. * example: FILTER1="parent ffff: matchall action simple sdata Input", ... * ---end--- */ diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c index 3f2f44d3cd..f90ea2cc54 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -9,6 +9,7 @@ #include "NetworkManagerUtils.h" #include +#include #include "nm-glib-aux/nm-c-list.h" @@ -18,6 +19,7 @@ #include "nm-setting-ip4-config.h" #include "nm-setting-ip6-config.h" #include "nm-core-internal.h" +#include "platform/nmp-object.h" #include "platform/nm-platform.h" #include "nm-auth-utils.h" @@ -1125,3 +1127,128 @@ nm_utils_file_is_in_path (const char *abs_filename, ? path : NULL; } + +/* The returned qdisc array is valid as long as s_tc is not modified */ +GPtrArray * +nm_utils_qdiscs_from_tc_setting (NMPlatform *platform, + NMSettingTCConfig *s_tc, + int ip_ifindex) +{ + GPtrArray *qdiscs; + guint nqdiscs; + guint i; + + nqdiscs = nm_setting_tc_config_get_num_qdiscs (s_tc); + qdiscs = g_ptr_array_new_full (nqdiscs, (GDestroyNotify) nmp_object_unref); + + for (i = 0; i < nqdiscs; i++) { + NMTCQdisc *s_qdisc = nm_setting_tc_config_get_qdisc (s_tc, i); + NMPObject *q = nmp_object_new (NMP_OBJECT_TYPE_QDISC, NULL); + NMPlatformQdisc *qdisc = NMP_OBJECT_CAST_QDISC (q); + + qdisc->ifindex = ip_ifindex; + qdisc->kind = nm_tc_qdisc_get_kind (s_qdisc); + + qdisc->addr_family = AF_UNSPEC; + qdisc->handle = nm_tc_qdisc_get_handle (s_qdisc); + qdisc->parent = nm_tc_qdisc_get_parent (s_qdisc); + qdisc->info = 0; + +#define GET_ATTR(name, dst, variant_type, type, dflt) G_STMT_START { \ + GVariant *_variant = nm_tc_qdisc_get_attribute (s_qdisc, ""name""); \ + \ + if ( _variant \ + && g_variant_is_of_type (_variant, G_VARIANT_TYPE_ ## variant_type)) \ + (dst) = g_variant_get_ ## type (_variant); \ + else \ + (dst) = (dflt); \ +} G_STMT_END + + if (strcmp (qdisc->kind, "fq_codel") == 0) { + GET_ATTR ("limit", qdisc->fq_codel.limit, UINT32, uint32, 0); + GET_ATTR ("flows", qdisc->fq_codel.flows, UINT32, uint32, 0); + GET_ATTR ("target", qdisc->fq_codel.target, UINT32, uint32, 0); + GET_ATTR ("interval", qdisc->fq_codel.interval, UINT32, uint32, 0); + GET_ATTR ("quantum", qdisc->fq_codel.quantum, UINT32, uint32, 0); + GET_ATTR ("ce_threshold", qdisc->fq_codel.ce_threshold, UINT32, uint32, NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED); + GET_ATTR ("memory_limit", qdisc->fq_codel.memory_limit, UINT32, uint32, NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET); + GET_ATTR ("ecn", qdisc->fq_codel.ecn, BOOLEAN, boolean, FALSE); + } + +#undef GET_ADDR + + g_ptr_array_add (qdiscs, q); + } + + return qdiscs; +} + +/* The returned tfilter array is valid as long as s_tc is not modified */ +GPtrArray * +nm_utils_tfilters_from_tc_setting (NMPlatform *platform, + NMSettingTCConfig *s_tc, + int ip_ifindex) +{ + GPtrArray *tfilters; + guint ntfilters; + guint i; + + ntfilters = nm_setting_tc_config_get_num_tfilters (s_tc); + tfilters = g_ptr_array_new_full (ntfilters, (GDestroyNotify) nmp_object_unref); + + for (i = 0; i < ntfilters; i++) { + NMTCTfilter *s_tfilter = nm_setting_tc_config_get_tfilter (s_tc, i); + NMTCAction *action; + NMPObject *t = nmp_object_new (NMP_OBJECT_TYPE_TFILTER, NULL); + NMPlatformTfilter *tfilter = NMP_OBJECT_CAST_TFILTER (t); + + tfilter->ifindex = ip_ifindex; + tfilter->kind = nm_tc_tfilter_get_kind (s_tfilter); + tfilter->addr_family = AF_UNSPEC; + tfilter->handle = nm_tc_tfilter_get_handle (s_tfilter); + tfilter->parent = nm_tc_tfilter_get_parent (s_tfilter); + tfilter->info = TC_H_MAKE (0, htons (ETH_P_ALL)); + + action = nm_tc_tfilter_get_action (s_tfilter); + if (action) { + GVariant *var; + + tfilter->action.kind = nm_tc_action_get_kind (action); + + if (strcmp (tfilter->action.kind, "simple") == 0) { + var = nm_tc_action_get_attribute (action, "sdata"); + if (var && g_variant_is_of_type (var, G_VARIANT_TYPE_BYTESTRING)) { + g_strlcpy (tfilter->action.simple.sdata, + g_variant_get_bytestring (var), + sizeof (tfilter->action.simple.sdata)); + } + } else if (strcmp (tfilter->action.kind, "mirred") == 0) { + if (nm_tc_action_get_attribute (action, "egress")) + tfilter->action.mirred.egress = TRUE; + + if (nm_tc_action_get_attribute (action, "ingress")) + tfilter->action.mirred.ingress = TRUE; + + if (nm_tc_action_get_attribute (action, "mirror")) + tfilter->action.mirred.mirror = TRUE; + + if (nm_tc_action_get_attribute (action, "redirect")) + tfilter->action.mirred.redirect = TRUE; + + var = nm_tc_action_get_attribute (action, "dev"); + if (var && g_variant_is_of_type (var, G_VARIANT_TYPE_STRING)) { + int ifindex; + + ifindex = nm_platform_link_get_ifindex (platform, + g_variant_get_string (var, NULL)); + if (ifindex > 0) + tfilter->action.mirred.ifindex = ifindex; + } + } + } + + g_ptr_array_add (tfilters, t); + } + + return tfilters; +} diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h index 558a262b98..38386c3dab 100644 --- a/src/NetworkManagerUtils.h +++ b/src/NetworkManagerUtils.h @@ -134,4 +134,11 @@ nm_utils_file_is_in_path (const char *abs_filename, /*****************************************************************************/ +GPtrArray *nm_utils_qdiscs_from_tc_setting (NMPlatform *platform, + NMSettingTCConfig *s_tc, + int ip_ifindex); +GPtrArray *nm_utils_tfilters_from_tc_setting (NMPlatform *platform, + NMSettingTCConfig *s_tc, + int ip_ifindex); + #endif /* __NETWORKMANAGER_UTILS_H__ */ diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 0cfc8102b8..223ac02027 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -19,7 +19,6 @@ #include #include #include -#include #include "nm-std-aux/unaligned.h" #include "nm-glib-aux/nm-dedup-multi.h" @@ -6873,139 +6872,28 @@ _routing_rules_sync (NMDevice *self, static gboolean tc_commit (NMDevice *self) { - NMConnection *connection = NULL; gs_unref_ptrarray GPtrArray *qdiscs = NULL; gs_unref_ptrarray GPtrArray *tfilters = NULL; - NMSettingTCConfig *s_tc = NULL; + NMSettingTCConfig *s_tc; + NMPlatform *platform; int ip_ifindex; - guint nqdiscs, ntfilters; - guint i; - connection = nm_device_get_applied_connection (self); - if (connection) - s_tc = nm_connection_get_setting_tc_config (connection); + s_tc = nm_device_get_applied_setting (self, NM_TYPE_SETTING_TC_CONFIG); + if (!s_tc) + return TRUE; ip_ifindex = nm_device_get_ip_ifindex (self); if (!ip_ifindex) - return s_tc == NULL; - - if (s_tc) { - nqdiscs = nm_setting_tc_config_get_num_qdiscs (s_tc); - qdiscs = g_ptr_array_new_full (nqdiscs, (GDestroyNotify) nmp_object_unref); - - for (i = 0; i < nqdiscs; i++) { - NMTCQdisc *s_qdisc = nm_setting_tc_config_get_qdisc (s_tc, i); - NMPObject *q = nmp_object_new (NMP_OBJECT_TYPE_QDISC, NULL); - NMPlatformQdisc *qdisc = NMP_OBJECT_CAST_QDISC (q); - - qdisc->ifindex = ip_ifindex; - - /* Note: kind string is still owned by NMTCTfilter. - * This qdisc instance must not be kept alive beyond this function. - * nm_platform_qdisc_sync() promises to do that. */ - qdisc->kind = nm_tc_qdisc_get_kind (s_qdisc); - - qdisc->addr_family = AF_UNSPEC; - qdisc->handle = nm_tc_qdisc_get_handle (s_qdisc); - qdisc->parent = nm_tc_qdisc_get_parent (s_qdisc); - qdisc->info = 0; - -#define GET_ATTR(name, dst, variant_type, type, dflt) G_STMT_START { \ - GVariant *_variant = nm_tc_qdisc_get_attribute (s_qdisc, ""name""); \ - \ - if ( _variant \ - && g_variant_is_of_type (_variant, G_VARIANT_TYPE_ ## variant_type)) \ - (dst) = g_variant_get_ ## type (_variant); \ - else \ - (dst) = (dflt); \ -} G_STMT_END - - if (strcmp (qdisc->kind, "fq_codel") == 0) { - GET_ATTR ("limit", qdisc->fq_codel.limit, UINT32, uint32, 0); - GET_ATTR ("flows", qdisc->fq_codel.flows, UINT32, uint32, 0); - GET_ATTR ("target", qdisc->fq_codel.target, UINT32, uint32, 0); - GET_ATTR ("interval", qdisc->fq_codel.interval, UINT32, uint32, 0); - GET_ATTR ("quantum", qdisc->fq_codel.quantum, UINT32, uint32, 0); - GET_ATTR ("ce_threshold", qdisc->fq_codel.ce_threshold, UINT32, uint32, NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED); - GET_ATTR ("memory_limit", qdisc->fq_codel.memory_limit, UINT32, uint32, NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET); - GET_ATTR ("ecn", qdisc->fq_codel.ecn, BOOLEAN, boolean, FALSE); - } - -#undef GET_ADDR - - g_ptr_array_add (qdiscs, q); - } - - ntfilters = nm_setting_tc_config_get_num_tfilters (s_tc); - tfilters = g_ptr_array_new_full (ntfilters, (GDestroyNotify) nmp_object_unref); - - for (i = 0; i < ntfilters; i++) { - NMTCTfilter *s_tfilter = nm_setting_tc_config_get_tfilter (s_tc, i); - NMTCAction *action; - NMPObject *q = nmp_object_new (NMP_OBJECT_TYPE_TFILTER, NULL); - NMPlatformTfilter *tfilter = NMP_OBJECT_CAST_TFILTER (q); - - tfilter->ifindex = ip_ifindex; - - /* Note: kind string is still owned by NMTCTfilter. - * This tfilter instance must not be kept alive beyond this function. - * nm_platform_tfilter_sync() promises to do that. */ - tfilter->kind = nm_tc_tfilter_get_kind (s_tfilter); - - tfilter->addr_family = AF_UNSPEC; - tfilter->handle = nm_tc_tfilter_get_handle (s_tfilter); - tfilter->parent = nm_tc_tfilter_get_parent (s_tfilter); - tfilter->info = TC_H_MAKE (0, htons (ETH_P_ALL)); - - action = nm_tc_tfilter_get_action (s_tfilter); - if (action) { - GVariant *var; - - /* Note: kind string is still owned by NMTCAction. - * This tfilter instance must not be kept alive beyond this function. - * nm_platform_tfilter_sync() promises to do that. */ - tfilter->action.kind = nm_tc_action_get_kind (action); - - if (strcmp (tfilter->action.kind, "simple") == 0) { - var = nm_tc_action_get_attribute (action, "sdata"); - if (var && g_variant_is_of_type (var, G_VARIANT_TYPE_BYTESTRING)) { - g_strlcpy (tfilter->action.simple.sdata, - g_variant_get_bytestring (var), - sizeof (tfilter->action.simple.sdata)); - } - } else if (strcmp (tfilter->action.kind, "mirred") == 0) { - if (nm_tc_action_get_attribute (action, "egress")) - tfilter->action.mirred.egress = TRUE; - - if (nm_tc_action_get_attribute (action, "ingress")) - tfilter->action.mirred.ingress = TRUE; - - if (nm_tc_action_get_attribute (action, "mirror")) - tfilter->action.mirred.mirror = TRUE; - - if (nm_tc_action_get_attribute (action, "redirect")) - tfilter->action.mirred.redirect = TRUE; - - var = nm_tc_action_get_attribute (action, "dev"); - if (var && g_variant_is_of_type (var, G_VARIANT_TYPE_STRING)) { - int ifindex; - - ifindex = nm_platform_link_get_ifindex (nm_device_get_platform (self), - g_variant_get_string (var, NULL)); - if (ifindex > 0) - tfilter->action.mirred.ifindex = ifindex; - } - } - } - - g_ptr_array_add (tfilters, q); - } - } - - if (!nm_platform_qdisc_sync (nm_device_get_platform (self), ip_ifindex, qdiscs)) return FALSE; - if (!nm_platform_tfilter_sync (nm_device_get_platform (self), ip_ifindex, tfilters)) + platform = nm_device_get_platform (self); + qdiscs = nm_utils_qdiscs_from_tc_setting (platform, s_tc, ip_ifindex); + tfilters = nm_utils_tfilters_from_tc_setting (platform, s_tc, ip_ifindex); + + if (!nm_platform_qdisc_sync (platform, ip_ifindex, qdiscs)) + return FALSE; + + if (!nm_platform_tfilter_sync (platform, ip_ifindex, tfilters)) return FALSE; return TRUE; @@ -15284,8 +15172,11 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean nm_platform_ip_route_flush (platform, AF_UNSPEC, ifindex); nm_platform_ip_address_flush (platform, AF_UNSPEC, ifindex); - nm_platform_tfilter_sync (platform, ifindex, NULL); - nm_platform_qdisc_sync (platform, ifindex, NULL); + + if (nm_device_get_applied_setting (self, NM_TYPE_SETTING_TC_CONFIG)) { + nm_platform_tfilter_sync (platform, ifindex, NULL); + nm_platform_qdisc_sync (platform, ifindex, NULL); + } } } diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c index 14d94db803..3e221cae31 100644 --- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c +++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c @@ -2431,7 +2431,8 @@ make_tc_setting (shvarFile *ifcfg) } if ( nm_setting_tc_config_get_num_qdiscs (s_tc) > 0 - || nm_setting_tc_config_get_num_tfilters (s_tc) > 0) + || nm_setting_tc_config_get_num_tfilters (s_tc) > 0 + || svGetValueBoolean(ifcfg, "TC_COMMIT", FALSE)) return NM_SETTING (s_tc); g_object_unref (s_tc); diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c index 017938ef12..ca222f08ce 100644 --- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c +++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c @@ -2332,48 +2332,52 @@ write_sriov_setting (NMConnection *connection, shvarFile *ifcfg) } } -static gboolean -write_tc_setting (NMConnection *connection, shvarFile *ifcfg, GError **error) +static void +write_tc_setting (NMConnection *connection, shvarFile *ifcfg) { NMSettingTCConfig *s_tc; - guint i, num, n; + guint num_qdiscs; + guint num_filters; + guint i; + guint n; char tag[64]; svUnsetAll (ifcfg, SV_KEY_TYPE_TC); s_tc = nm_connection_get_setting_tc_config (connection); - if (!s_tc) - return TRUE; + if (!s_tc) { + svUnsetValue (ifcfg, "TC_COMMIT"); + return; + } - num = nm_setting_tc_config_get_num_qdiscs (s_tc); - for (n = 1, i = 0; i < num; i++) { + num_qdiscs = nm_setting_tc_config_get_num_qdiscs (s_tc); + for (n = 1, i = 0; i < num_qdiscs; i++) { NMTCQdisc *qdisc; gs_free char *str = NULL; qdisc = nm_setting_tc_config_get_qdisc (s_tc, i); - str = nm_utils_tc_qdisc_to_str (qdisc, error); - if (!str) - return FALSE; - + str = nm_utils_tc_qdisc_to_str (qdisc, NULL); + nm_assert (str); svSetValueStr (ifcfg, numbered_tag (tag, "QDISC", n), str); n++; } - num = nm_setting_tc_config_get_num_tfilters (s_tc); - for (n = 1, i = 0; i < num; i++) { + num_filters = nm_setting_tc_config_get_num_tfilters (s_tc); + for (n = 1, i = 0; i < num_filters; i++) { NMTCTfilter *tfilter; gs_free char *str = NULL; tfilter = nm_setting_tc_config_get_tfilter (s_tc, i); - str = nm_utils_tc_tfilter_to_str (tfilter, error); - if (!str) - return FALSE; - + str = nm_utils_tc_tfilter_to_str (tfilter, NULL); + nm_assert (str); svSetValueStr (ifcfg, numbered_tag (tag, "FILTER", n), str); n++; } - return TRUE; + if (num_qdiscs == 0 && num_filters == 0) + svSetValueBoolean (ifcfg, "TC_COMMIT", TRUE); + else + svUnsetValue (ifcfg, "TC_COMMIT"); } static gboolean @@ -3242,8 +3246,7 @@ do_write_construct (NMConnection *connection, write_sriov_setting (connection, ifcfg); - if (!write_tc_setting (connection, ifcfg, error)) - return FALSE; + write_tc_setting (connection, ifcfg); svUnsetValue (ifcfg, "DHCP_HOSTNAME"); svUnsetValue (ifcfg, "DHCP_FQDN"); diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected new file mode 100644 index 0000000000..4df768b463 --- /dev/null +++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected @@ -0,0 +1,15 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +TC_COMMIT=yes +BOOTPROTO=none +IPADDR=1.1.1.3 +PREFIX=24 +GATEWAY=1.1.1.1 +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=no +NAME="Test Write TC config" +UUID=${UUID} +DEVICE=eth0 +ONBOOT=yes diff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c index a782ccc621..442a7ddb30 100644 --- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c @@ -10123,6 +10123,85 @@ test_tc_read (void) g_object_unref (connection); } +static void +test_tc_write_empty (void) +{ + nmtst_auto_unlinkfile char *testfile = NULL; + gs_unref_object NMConnection *connection = NULL; + gs_unref_object NMConnection *reread = NULL; + NMSettingConnection *s_con; + NMSettingIPConfig *s_ip4; + NMSettingIPConfig *s_ip6; + NMSettingWired *s_wired; + NMSettingTCConfig *s_tc; + NMIPAddress *addr; + GError *error = NULL; + + connection = nm_simple_connection_new (); + + /* Connection setting */ + s_con = (NMSettingConnection*) nm_setting_connection_new (); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + + g_object_set (s_con, + NM_SETTING_CONNECTION_ID, + "Test Write TC config", + NM_SETTING_CONNECTION_UUID, + nm_utils_uuid_generate_a (), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "eth0", + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + /* Wired setting */ + s_wired = (NMSettingWired*) nm_setting_wired_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wired)); + + /* IP4 setting */ + s_ip4 = (NMSettingIPConfig*) nm_setting_ip4_config_new (); + nm_connection_add_setting (connection, NM_SETTING (s_ip4)); + + g_object_set (s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.1", + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + + addr = nm_ip_address_new (AF_INET, "1.1.1.3", 24, &error); + g_assert_no_error (error); + nm_setting_ip_config_add_address (s_ip4, addr); + nm_ip_address_unref (addr); + + /* IP6 setting */ + s_ip6 = (NMSettingIPConfig*) nm_setting_ip6_config_new (); + nm_connection_add_setting (connection, NM_SETTING (s_ip6)); + + g_object_set (s_ip6, NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL); + + /* TC setting */ + s_tc = (NMSettingTCConfig*) nm_setting_tc_config_new (); + nm_connection_add_setting (connection, NM_SETTING (s_tc)); + + nm_connection_add_setting (connection, nm_setting_proxy_new ()); + + nmtst_assert_connection_verifies_without_normalization (connection); + + _writer_new_connec_exp (connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-test-tc-write-empty.cexpected", &testfile); + + reread = _connection_from_file (testfile, NULL, TYPE_BOND, NULL); + + nmtst_assert_connection_equals (connection, FALSE, reread, FALSE); +} + static void test_tc_write (void) { @@ -10505,6 +10584,7 @@ int main (int argc, char **argv) g_test_add_func (TPATH "tc/read", test_tc_read); g_test_add_func (TPATH "tc/write", test_tc_write); + g_test_add_func (TPATH "tc/write_empty", test_tc_write_empty); return g_test_run (); }