diff options
Diffstat (limited to 'src/core')
13 files changed, 253 insertions, 21 deletions
diff --git a/src/core/NetworkManagerUtils.c b/src/core/NetworkManagerUtils.c index 8b0b4845..f5b7666b 100644 --- a/src/core/NetworkManagerUtils.c +++ b/src/core/NetworkManagerUtils.c @@ -978,6 +978,7 @@ nm_ip_routing_rule_to_platform(const NMIPRoutingRule *rule, NMPlatformRoutingRul .start = uid_range_start, .end = uid_range_end, }, + .protocol = RTPROT_STATIC, }; nm_ip_routing_rule_get_xifname_bin(rule, TRUE, out_pl->iifname); diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c index 40c6d208..4788cdc6 100644 --- a/src/core/devices/nm-device-ethernet.c +++ b/src/core/devices/nm-device-ethernet.c @@ -1354,6 +1354,11 @@ wake_on_lan_enable(NMDevice *device) if (s_wired) { wol = nm_setting_wired_get_wake_on_lan(s_wired); password = nm_setting_wired_get_wake_on_lan_password(s_wired); + + /* NMSettingWired does not reject invalid flags. Filter them out here. */ + wol = (wol + & (NM_SETTING_WIRED_WAKE_ON_LAN_ALL | NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS)); + if (wol != NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT) goto found; } @@ -1370,9 +1375,14 @@ wake_on_lan_enable(NMDevice *device) nm_log_dbg(LOGD_ETHER, "invalid default value %u for wake-on-lan", (guint) wol); wol = NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT; } + + wol = wol & (NM_SETTING_WIRED_WAKE_ON_LAN_ALL | NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS); + if (wol != NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT) goto found; + wol = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE; + found: return nm_platform_ethtool_set_wake_on_lan(nm_device_get_platform(device), nm_device_get_ifindex(device), diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 6be9bc7c..2cda9b0d 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -6657,7 +6657,6 @@ device_link_changed(gpointer user_data) NMDeviceClass *klass = NM_DEVICE_GET_CLASS(self); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); gboolean ip_ifname_changed = FALSE; - gboolean hw_addr_changed; nm_auto_nmpobj const NMPObject *pllink_keep_alive = NULL; const NMPlatformLink *pllink; const char *str; @@ -6704,9 +6703,9 @@ device_link_changed(gpointer user_data) if (ifindex == nm_device_get_ip_ifindex(self)) _stats_update_counters_from_pllink(self, pllink); - had_hw_addr = (priv->hw_addr != NULL); - hw_addr_changed = nm_device_update_hw_address(self); - got_hw_addr = (!had_hw_addr && priv->hw_addr); + had_hw_addr = (priv->hw_addr != NULL); + nm_device_update_hw_address(self); + got_hw_addr = (!had_hw_addr && priv->hw_addr); nm_device_update_permanent_hw_address(self, FALSE); if (pllink->name[0] && !nm_streq(priv->iface, pllink->name)) { @@ -6757,8 +6756,6 @@ device_link_changed(gpointer user_data) /* Update DHCP, etc, if needed */ if (ip_ifname_changed) nm_device_update_dynamic_ip_setup(self, "IP interface changed"); - else if (hw_addr_changed) - nm_device_update_dynamic_ip_setup(self, "hw-address changed"); was_up = priv->up; priv->up = NM_FLAGS_HAS(pllink->n_ifi_flags, IFF_UP); diff --git a/src/core/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c index c003880e..fc27c3fd 100644 --- a/src/core/devices/wwan/nm-modem-ofono.c +++ b/src/core/devices/wwan/nm-modem-ofono.c @@ -739,7 +739,7 @@ handle_settings(GVariant *v_dict, gpointer user_data) gboolean ret = FALSE; const char *interface; const char *s; - const char **array; + gs_free const char **array = NULL; guint32 address_network, gateway_network; int ifindex; GError *error = NULL; @@ -838,15 +838,16 @@ handle_settings(GVariant *v_dict, gpointer user_data) } if (array) { gboolean any_good = FALSE; + gsize i; - for (; array[0]; array++) { - if (!nm_utils_parse_inaddr_bin(AF_INET, *array, NULL, &address_network) + for (i = 0; array[i]; i++) { + if (!nm_utils_parse_inaddr_bin(AF_INET, array[i], NULL, &address_network) || !address_network) { - _LOGW("invalid NameServer: %s", *array); + _LOGW("invalid NameServer: %s", array[i]); continue; } any_good = TRUE; - _LOGI("DNS: %s", *array); + _LOGI("DNS: %s", array[i]); nm_l3_config_data_add_nameserver(priv->l3cd_4, AF_INET, &address_network); } if (!any_good) { diff --git a/src/core/nm-connectivity.c b/src/core/nm-connectivity.c index 25ac3c7f..cd30853d 100644 --- a/src/core/nm-connectivity.c +++ b/src/core/nm-connectivity.c @@ -82,7 +82,6 @@ struct _NMConnectivityCheckHandle { gsize response_good_cnt; guint curl_timer; - int ch_ifindex; } concheck; #endif @@ -1049,8 +1048,6 @@ nm_connectivity_check_start(NMConnectivity *self, NMConnectivityState state; const char *reason; - cb_data->concheck.ch_ifindex = ifindex; - if (platform) { state = check_platform_config(self, platform, ifindex, addr_family, &reason); nm_assert((state == NM_CONNECTIVITY_UNKNOWN) == !reason); @@ -1106,7 +1103,7 @@ nm_connectivity_check_start(NMConnectivity *self, "org.freedesktop.resolve1.Manager", "ResolveHostname", g_variant_new("(isit)", - (gint32) cb_data->concheck.ch_ifindex, + 0, cb_data->concheck.con_config->host, (gint32) cb_data->addr_family, SD_RESOLVED_DNS), diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 69ea0d26..8fa1e07d 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -4010,7 +4010,7 @@ _l3_commit_ndisc_params(NML3Cfg *self, NML3CfgCommitType commit_type) if (retrans_set && (!self->priv.p->ndisc_retrans_timer_msec_set - || self->priv.p->ndisc_reachable_time_msec != retrans)) { + || self->priv.p->ndisc_retrans_timer_msec != retrans)) { self->priv.p->ndisc_retrans_timer_msec = retrans; self->priv.p->ndisc_retrans_timer_msec_set = TRUE; if (ifname) { diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c index 96efc9e5..e8948c3d 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c @@ -1312,6 +1312,7 @@ write_ethtool_setting(NMConnection *connection, shvarFile *ifcfg, GError **error gboolean is_first; guint32 u32; gboolean b; + gboolean any_option = FALSE; s_con = nm_connection_get_setting_connection(connection); if (s_con) { @@ -1341,6 +1342,7 @@ write_ethtool_setting(NMConnection *connection, shvarFile *ifcfg, GError **error g_string_append_c(str, ' '); g_string_append(str, nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id)); g_string_append(str, b ? " on" : " off"); + any_option = TRUE; } is_first = TRUE; @@ -1356,6 +1358,7 @@ write_ethtool_setting(NMConnection *connection, shvarFile *ifcfg, GError **error g_string_append_c(str, ' '); g_string_append(str, nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id)); g_string_append_printf(str, " %" G_GUINT32_FORMAT, u32); + any_option = TRUE; } is_first = TRUE; @@ -1371,7 +1374,10 @@ write_ethtool_setting(NMConnection *connection, shvarFile *ifcfg, GError **error g_string_append_c(str, ' '); g_string_append(str, nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id)); g_string_append_printf(str, " %" G_GUINT32_FORMAT, u32); + any_option = TRUE; } + + is_first = TRUE; for (ethtool_id = _NM_ETHTOOL_ID_PAUSE_FIRST; ethtool_id <= _NM_ETHTOOL_ID_PAUSE_LAST; ethtool_id++) { nm_assert(nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id)); @@ -1384,9 +1390,10 @@ write_ethtool_setting(NMConnection *connection, shvarFile *ifcfg, GError **error g_string_append_c(str, ' '); g_string_append(str, nms_ifcfg_rh_utils_get_ethtool_name(ethtool_id)); g_string_append(str, b ? " on" : " off"); + any_option = TRUE; } - if (!str) { + if (!any_option) { /* Write an empty dummy "-A" option without arguments. This is to * ensure that the reader will create an (all default) NMSettingEthtool. * Also, it seems that `ethtool -A "$IFACE"` is silently accepted. */ diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_roundtrip_ethtool-6.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_roundtrip_ethtool-6.cexpected new file mode 100644 index 00000000..784ad224 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_roundtrip_ethtool-6.cexpected @@ -0,0 +1,15 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +ETHTOOL_OPTS="autoneg on ; -A net0 pause-autoneg off" +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=default +NAME=test_roundtrip_ethtool +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_roundtrip_ethtool-7.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_roundtrip_ethtool-7.cexpected new file mode 100644 index 00000000..02ab14fe --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_roundtrip_ethtool-7.cexpected @@ -0,0 +1,15 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +ETHTOOL_OPTS="autoneg on ; -G net0 rx 512" +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=default +NAME=test_roundtrip_ethtool +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_roundtrip_ethtool-8.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_roundtrip_ethtool-8.cexpected new file mode 100644 index 00000000..fd760839 --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_roundtrip_ethtool-8.cexpected @@ -0,0 +1,15 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +ETHTOOL_OPTS="autoneg on ; -A net0" +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=default +NAME=test_roundtrip_ethtool +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_roundtrip_ethtool-9.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_roundtrip_ethtool-9.cexpected new file mode 100644 index 00000000..1703b0dc --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_roundtrip_ethtool-9.cexpected @@ -0,0 +1,15 @@ +TYPE=Ethernet +PROXY_METHOD=none +BROWSER_ONLY=no +ETHTOOL_OPTS="autoneg on ; -G net0 rx 512 ; -A net0 pause-autoneg off" +BOOTPROTO=dhcp +DEFROUTE=yes +IPV4_FAILURE_FATAL=no +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +IPV6_ADDR_GEN_MODE=default +NAME=test_roundtrip_ethtool +UUID=${UUID} +ONBOOT=yes diff --git a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c index f9fd7fd5..886a605f 100644 --- a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c +++ b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c @@ -3425,6 +3425,7 @@ test_roundtrip_ethtool(void) gs_unref_object NMConnection *connection = NULL; NMSetting *s_ethtool; NMSetting *s_wired; + int i_run; connection = nmtst_create_minimal_connection("test_roundtrip_ethtool", NULL, @@ -3487,6 +3488,159 @@ test_roundtrip_ethtool(void) TEST_IFCFG_DIR "/ifcfg-test_roundtrip_ethtool-5.cexpected", NULL); g_clear_object(&connection); + + connection = nmtst_create_minimal_connection("test_roundtrip_ethtool", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED); + g_object_set(s_wired, NM_SETTING_WIRED_AUTO_NEGOTIATE, TRUE, NULL); + + s_ethtool = _nm_connection_new_setting(connection, NM_TYPE_SETTING_ETHTOOL); + nm_setting_option_set_boolean(s_ethtool, NM_ETHTOOL_OPTNAME_PAUSE_AUTONEG, FALSE); + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-test_roundtrip_ethtool-6.cexpected", + NULL); + g_clear_object(&connection); + + connection = nmtst_create_minimal_connection("test_roundtrip_ethtool", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED); + g_object_set(s_wired, NM_SETTING_WIRED_AUTO_NEGOTIATE, TRUE, NULL); + + s_ethtool = _nm_connection_new_setting(connection, NM_TYPE_SETTING_ETHTOOL); + nm_setting_option_set_uint32(s_ethtool, NM_ETHTOOL_OPTNAME_RING_RX, 512); + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-test_roundtrip_ethtool-7.cexpected", + NULL); + g_clear_object(&connection); + + connection = nmtst_create_minimal_connection("test_roundtrip_ethtool", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED); + g_object_set(s_wired, NM_SETTING_WIRED_AUTO_NEGOTIATE, TRUE, NULL); + + s_ethtool = _nm_connection_new_setting(connection, NM_TYPE_SETTING_ETHTOOL); + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-test_roundtrip_ethtool-8.cexpected", + NULL); + g_clear_object(&connection); + + connection = nmtst_create_minimal_connection("test_roundtrip_ethtool", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED); + g_object_set(s_wired, NM_SETTING_WIRED_AUTO_NEGOTIATE, TRUE, NULL); + + s_ethtool = _nm_connection_new_setting(connection, NM_TYPE_SETTING_ETHTOOL); + nm_setting_option_set_boolean(s_ethtool, NM_ETHTOOL_OPTNAME_PAUSE_AUTONEG, FALSE); + nm_setting_option_set_uint32(s_ethtool, NM_ETHTOOL_OPTNAME_RING_RX, 512); + _writer_new_connec_exp(connection, + TEST_SCRATCH_DIR, + TEST_IFCFG_DIR "/ifcfg-test_roundtrip_ethtool-9.cexpected", + NULL); + g_clear_object(&connection); + + for (i_run = 0; i_run < 20; i_run++) { + gs_unref_object NMConnection *con2 = NULL; + gs_unref_object NMConnection *reread = NULL; + nmtst_auto_unlinkfile char *testfile = NULL; + gboolean reread_same; + gboolean v_bool; + NMEthtoolID ethtool_ids[_NM_ETHTOOL_ID_NUM]; + guint i; + guint l; + NMSettingWiredWakeOnLan wake_on_lan; + + con2 = nmtst_create_minimal_connection("test_roundtrip_ethtool", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + s_wired = nmtst_connection_assert_setting(con2, NM_TYPE_SETTING_WIRED); + g_object_set(s_wired, NM_SETTING_WIRED_AUTO_NEGOTIATE, nmtst_get_rand_bool(), NULL); + + if (nmtst_get_rand_bool()) { + g_object_set(s_wired, + NM_SETTING_WIRED_SPEED, + 1000u, + NM_SETTING_WIRED_DUPLEX, + nmtst_get_rand_bool() ? "full" : "half", + NULL); + } + + wake_on_lan = nmtst_get_rand_uint32(); + wake_on_lan = wake_on_lan & NM_SETTING_WIRED_WAKE_ON_LAN_ALL; + wake_on_lan = nmtst_rand_select(wake_on_lan, + NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT, + NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE); + g_object_set(s_wired, NM_SETTING_WIRED_WAKE_ON_LAN, (guint) wake_on_lan, NULL); + + if (NM_FLAGS_HAS(wake_on_lan, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC) + && nmtst_get_rand_bool()) { + g_object_set(s_wired, NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD, "aa:bb:cc:dd:ee:ff", NULL); + } + + l = nmtst_get_rand_uint32() % (G_N_ELEMENTS(ethtool_ids) + 2); + if (l == 0) { + /* pass. No ethtool setting. */ + goto check_roundtrip; + } + l--; + + s_ethtool = _nm_connection_new_setting(con2, NM_TYPE_SETTING_ETHTOOL); + + for (i = 0; i < (int) G_N_ELEMENTS(ethtool_ids); i++) + ethtool_ids[i] = i; + nmtst_rand_perm(NULL, ethtool_ids, NULL, sizeof(ethtool_ids[0]), G_N_ELEMENTS(ethtool_ids)); + + for (i = 0; i < l; i++) { + NMEthtoolID ethtool_id = ethtool_ids[i]; + const GVariantType *vtype; + const char *optname; + + optname = nm_ethtool_data[ethtool_id]->optname; + vtype = nm_ethtool_id_get_variant_type(ethtool_id); + + if (NM_IN_SET(ethtool_id, + NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX, + NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)) { + nm_setting_option_set_uint32(s_ethtool, optname, nmtst_get_rand_uint32() % 2); + } else if (g_variant_type_equal(vtype, G_VARIANT_TYPE_BOOLEAN)) + nm_setting_option_set_boolean(s_ethtool, optname, nmtst_get_rand_bool()); + else if (g_variant_type_equal(vtype, G_VARIANT_TYPE_UINT32)) + nm_setting_option_set_uint32(s_ethtool, optname, nmtst_get_rand_uint32()); + else + g_assert_not_reached(); + } + + if ((nm_setting_option_get_boolean(s_ethtool, NM_ETHTOOL_OPTNAME_PAUSE_RX, NULL) + || nm_setting_option_get_boolean(s_ethtool, NM_ETHTOOL_OPTNAME_PAUSE_TX, NULL)) + && nm_setting_option_get_boolean(s_ethtool, NM_ETHTOOL_OPTNAME_PAUSE_AUTONEG, &v_bool) + && v_bool) { + /* don't accidentally create an invalid profile. */ + nm_setting_option_set(s_ethtool, + NM_ETHTOOL_OPTNAME_PAUSE_AUTONEG, + nmtst_get_rand_bool() ? g_variant_new_boolean(FALSE) : NULL); + } + +check_roundtrip: + _writer_new_connection_reread(con2, + TEST_SCRATCH_DIR, + &testfile, + NO_EXPECTED, + &reread, + &reread_same); + g_assert(NM_IS_CONNECTION(reread)); + g_assert(reread_same); + } } static void diff --git a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c index 1307d4b2..47be3a5e 100644 --- a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c +++ b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c @@ -2292,14 +2292,19 @@ test_read_missing_vlan_flags(void) static void test_read_missing_id_uuid(void) { - gs_unref_object NMConnection *connection = NULL; - gs_free char *expected_uuid = NULL; - const char *FILENAME = TEST_KEYFILES_DIR "/Test_Missing_ID_UUID"; + gs_unref_object NMConnection *connection = NULL; + gs_free char *expected_uuid = NULL; + gs_free char *expected_uuid2 = NULL; + const char *FILENAME = TEST_KEYFILES_DIR "/Test_Missing_ID_UUID"; + const char F[] = "keyfile\0" TEST_KEYFILES_DIR "/Test_Missing_ID_UUID"; expected_uuid = nm_uuid_generate_from_strings("keyfile", FILENAME, NULL); - connection = keyfile_read_connection_from_file(FILENAME); + expected_uuid2 = + nm_uuid_generate_from_string_str(F, sizeof(F), NM_UUID_TYPE_VERSION3, &nm_uuid_ns_1); + g_assert_cmpstr(expected_uuid, ==, expected_uuid2); + connection = keyfile_read_connection_from_file(FILENAME); g_assert_cmpstr(nm_connection_get_id(connection), ==, "Test_Missing_ID_UUID"); g_assert_cmpstr(nm_connection_get_uuid(connection), ==, expected_uuid); } |