diff --git a/NEWS b/NEWS index ffd9dc43d9..da6ad4e371 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,14 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! suffixes when appropriate. This affects, for example, the URL and filename of the release tarball and the version reported by nmcli and the daemon. As an exception, the C API will continue to use the 90+ scheme for RC versions. +* Connection profiles with manual IP addressing and with gateways that are not + directly reachable will generate a warning on activation and when they are + added/modified via nmcli and nmtui. NetworkManager currently adds on-link + routes for them automatically, but this will change in the future. To fix the + warning, users should add addresses or routes whose subnets cover these + gateways. A gateway (either the default gateway or the next-hop of a route) is + considered directly reachable if it falls within the subnet of a direct route + (a route without a next hop) or of a prefix route from a static address. * Restrict the connectivity check to use the DNS servers defined on the same link. If the link has no DNS servers, the connectivity check will use any servers available in the system. diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index f3f09db18c..9adcbd67f0 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -3684,6 +3684,7 @@ nm_device_create_l3_config_data_from_connection(NMDevice *self, NMConnection *co { NML3ConfigData *l3cd; int ifindex; + gs_free char *gw_warning = NULL; nm_assert(NM_IS_DEVICE(self)); nm_assert(!connection || NM_IS_CONNECTION(connection)); @@ -3704,6 +3705,10 @@ nm_device_create_l3_config_data_from_connection(NMDevice *self, NMConnection *co nm_l3_config_data_set_ip6_privacy(l3cd, _prop_get_ipv6_ip6_privacy(self, connection)); nm_l3_config_data_set_mptcp_flags(l3cd, _prop_get_connection_mptcp_flags(self, connection)); + gw_warning = nm_connection_get_unreachable_gateways_warning(connection, FALSE); + if (gw_warning) + _LOGW(LOGD_IP, "%s", gw_warning); + return l3cd; } diff --git a/src/libnm-core-aux-intern/nm-libnm-core-utils.c b/src/libnm-core-aux-intern/nm-libnm-core-utils.c index a39f645468..60b6572444 100644 --- a/src/libnm-core-aux-intern/nm-libnm-core-utils.c +++ b/src/libnm-core-aux-intern/nm-libnm-core-utils.c @@ -1149,3 +1149,202 @@ nm_setting_ovs_other_config_check_val(const char *val, GError **error) return TRUE; } + +/*****************************************************************************/ + +typedef struct { + NMIPAddr dest; + guint prefix; +} DirectRoute; + +static void +_setting_ip_config_collect_unreachable_gateways(NMSettingIPConfig *s_ip, GHashTable **result) +{ + const int addr_family = nm_setting_ip_config_get_addr_family(s_ip); + guint num_routes; + guint num_addrs; + guint n_direct_routes = 0; + NMIPAddr gw_bin = NM_IP_ADDR_INIT; + guint i; + guint j; + const char *gateway; + gs_free DirectRoute *direct_routes = NULL; + + num_routes = nm_setting_ip_config_get_num_routes(s_ip); + num_addrs = nm_setting_ip_config_get_num_addresses(s_ip); + + direct_routes = g_new0(DirectRoute, num_routes + num_addrs); + + /* Collect direct routes (routes without a gateway) from the setting. */ + for (i = 0; i < num_routes; i++) { + NMIPRoute *route = nm_setting_ip_config_get_route(s_ip, i); + + if (nm_ip_route_get_next_hop(route)) + continue; + + nm_ip_route_get_dest_binary(route, &direct_routes[n_direct_routes].dest); + direct_routes[n_direct_routes].prefix = nm_ip_route_get_prefix(route); + n_direct_routes++; + } + + /* Add prefix routes (device routes) for each static address. */ + for (i = 0; i < num_addrs; i++) { + NMIPAddress *addr = nm_setting_ip_config_get_address(s_ip, i); + + nm_ip_address_get_address_binary(addr, &direct_routes[n_direct_routes].dest); + direct_routes[n_direct_routes].prefix = nm_ip_address_get_prefix(addr); + n_direct_routes++; + } + + /* Check the setting's default gateway. */ + gateway = nm_setting_ip_config_get_gateway(s_ip); + if (gateway && nm_inet_parse_bin(addr_family, gateway, NULL, &gw_bin)) { + gboolean reachable = FALSE; + + if (addr_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&gw_bin.addr6)) + reachable = TRUE; + + if (!reachable) { + for (j = 0; j < n_direct_routes; j++) { + if (nm_ip_addr_same_prefix(addr_family, + &gw_bin, + &direct_routes[j].dest, + direct_routes[j].prefix)) { + reachable = TRUE; + break; + } + } + } + + if (!reachable) { + if (!*result) + *result = g_hash_table_new(nm_str_hash, g_str_equal); + g_hash_table_add(*result, (gpointer) gateway); + } + } + + /* Check gateways of each route in the setting. */ + for (i = 0; i < num_routes; i++) { + NMIPRoute *route = nm_setting_ip_config_get_route(s_ip, i); + NMIPAddr next_hop = NM_IP_ADDR_INIT; + gboolean reachable = FALSE; + GVariant *attribute; + + if (!nm_ip_route_get_next_hop_binary(route, &next_hop)) + continue; + + if (addr_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&next_hop.addr6)) + continue; + + attribute = nm_ip_route_get_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK); + if (attribute && g_variant_is_of_type(attribute, G_VARIANT_TYPE("b")) + && g_variant_get_boolean(attribute)) { + /* the gateway of a onlink route is reachable */ + continue; + } + + for (j = 0; j < n_direct_routes; j++) { + if (nm_ip_addr_same_prefix(addr_family, + &next_hop, + &direct_routes[j].dest, + direct_routes[j].prefix)) { + reachable = TRUE; + break; + } + } + + if (!reachable) { + if (!*result) + *result = g_hash_table_new(nm_str_hash, g_str_equal); + g_hash_table_add(*result, (gpointer) nm_ip_route_get_next_hop(route)); + } + } +} + +/** + * nm_connection_get_unreachable_gateways: + * @connection: the #NMConnection + * + * Checks whether there are gateways (either the default gateway or gateways + * of routes) that are not directly reachable in the IPv4 and IPv6 settings + * of the connection. A gateway is considered directly reachable if it falls + * within the subnet of a direct route (a route without a next hop) or of a + * prefix route from a static address. + * + * Returns: a %NULL-terminated array of gateway strings not directly reachable, + * or %NULL if all gateways are reachable. The individual strings are owned + * by the setting. Free the returned array with g_free(). + */ +const char ** +nm_connection_get_unreachable_gateways(NMConnection *connection) +{ + gs_unref_hashtable GHashTable *result = NULL; + NMSettingIPConfig *s_ip; + guint len; + const char **strv; + + if (!connection) + return NULL; + + s_ip = nm_connection_get_setting_ip4_config(connection); + if (s_ip + && nm_streq0(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) { + _setting_ip_config_collect_unreachable_gateways(s_ip, &result); + } + + s_ip = nm_connection_get_setting_ip6_config(connection); + if (s_ip + && nm_streq0(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) { + _setting_ip_config_collect_unreachable_gateways(s_ip, &result); + } + + if (result) { + strv = (const char **) g_hash_table_get_keys_as_array(result, &len); + nm_strv_sort(strv, len); + return strv; + } + + return NULL; +} + +/** + * nm_connection_get_unreachable_gateways_warning: + * @connection: the #NMConnection + * @translate: whether to translate the message (use %TRUE for user-facing + * tools like nmcli, %FALSE for daemon logs) + * + * Checks whether there are unreachable gateways in the connection and returns + * a formatted warning message if so. + * + * Returns: a warning message string, or %NULL if all gateways are reachable. + * Free with g_free(). + */ +char * +nm_connection_get_unreachable_gateways_warning(NMConnection *connection, gboolean translate) +{ + gs_free const char **gateways = NULL; + gs_free char *gw_list = NULL; + + gateways = nm_connection_get_unreachable_gateways(connection); + if (!gateways) + return NULL; + + gw_list = g_strjoinv(", ", (char **) gateways); + + /* Keep the following two strings in sync. */ + if (translate) { + return g_strdup_printf( + _("the following gateways are not directly reachable from any configured address or " + "route: %s. NetworkManager currently adds on-link routes for them automatically, " + "but this will change in the future. Consider adding addresses or routes whose " + "subnets cover these gateways"), + gw_list); + } + + return g_strdup_printf( + "the following gateways are not directly reachable from any configured address or " + "route: %s. NetworkManager currently adds on-link routes for them automatically, " + "but this will change in the future. Consider adding addresses or routes whose " + "subnets cover these gateways", + gw_list); +} diff --git a/src/libnm-core-aux-intern/nm-libnm-core-utils.h b/src/libnm-core-aux-intern/nm-libnm-core-utils.h index e14444bb9a..ad0bad442d 100644 --- a/src/libnm-core-aux-intern/nm-libnm-core-utils.h +++ b/src/libnm-core-aux-intern/nm-libnm-core-utils.h @@ -344,6 +344,12 @@ gboolean nm_setting_ovs_other_config_check_val(const char *val, GError **error); /*****************************************************************************/ +const char **nm_connection_get_unreachable_gateways(NMConnection *connection); + +char *nm_connection_get_unreachable_gateways_warning(NMConnection *connection, gboolean translate); + +/*****************************************************************************/ + /* Wi-Fi frequencies range for each band */ #define _NM_WIFI_FREQ_MIN_2GHZ 2412 #define _NM_WIFI_FREQ_MAX_2GHZ 2484 diff --git a/src/libnm-core-impl/tests/test-general.c b/src/libnm-core-impl/tests/test-general.c index 9143da0435..1e2904a152 100644 --- a/src/libnm-core-impl/tests/test-general.c +++ b/src/libnm-core-impl/tests/test-general.c @@ -11613,6 +11613,328 @@ test_dhcp_iaid_hexstr(void) /*****************************************************************************/ +static void +test_unreachable_gateways(void) +{ + NMConnection *conn; + NMSettingIPConfig *s_ip4; + NMSettingIPConfig *s_ip6; + gs_free const char **result = NULL; + + /* IPv4 gateway reachable via address prefix route */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "192.168.1.1", + NULL); + nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(!result); + g_object_unref(conn); + } + + /* IPv4 gateway NOT reachable */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "10.0.0.1", + NULL); + nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24); + nmtst_setting_ip_config_add_address(s_ip4, "172.16.1.1", 16); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(result); + g_assert_cmpint(g_strv_length((char **) result), ==, 1); + g_assert_cmpstr(result[0], ==, "10.0.0.1"); + nm_clear_g_free(&result); + g_object_unref(conn); + } + + /* IPv4 gateway NOT reachable. It's ignored because of method "auto" */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP_CONFIG_GATEWAY, + "10.0.0.1", + NULL); + nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24); + nmtst_setting_ip_config_add_address(s_ip4, "172.16.1.1", 16); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(!result); + g_object_unref(conn); + } + + /* Route gateway reachable via address prefix route */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL); + nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24); + nmtst_setting_ip_config_add_route(s_ip4, "10.0.0.0", 8, "192.168.1.254", 100); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(!result); + g_object_unref(conn); + } + + /* Route gateway NOT reachable, check sorting */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL); + nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24); + nmtst_setting_ip_config_add_route(s_ip4, "10.0.0.0", 16, "172.16.0.2", 100); + nmtst_setting_ip_config_add_route(s_ip4, "10.1.0.0", 16, "172.16.0.4", 100); + nmtst_setting_ip_config_add_route(s_ip4, "10.2.0.0", 16, "172.16.0.3", 100); + nmtst_setting_ip_config_add_route(s_ip4, "10.3.0.0", 16, "172.16.0.1", 100); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(result); + g_assert_cmpint(g_strv_length((char **) result), ==, 4); + g_assert_cmpstr(result[0], ==, "172.16.0.1"); + g_assert_cmpstr(result[1], ==, "172.16.0.2"); + g_assert_cmpstr(result[2], ==, "172.16.0.3"); + g_assert_cmpstr(result[3], ==, "172.16.0.4"); + nm_clear_g_free(&result); + g_object_unref(conn); + } + + /* Route gateway reachable via a direct route in the setting */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL); + nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24); + nmtst_setting_ip_config_add_route(s_ip4, "172.16.0.0", 16, NULL, 100); + nmtst_setting_ip_config_add_route(s_ip4, "10.0.0.0", 8, "172.16.0.1", 100); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(!result); + g_object_unref(conn); + } + + /* No gateways */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL); + nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(!result); + g_object_unref(conn); + } + + /* Both default gateway and route gateway unreachable */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "10.0.0.1", + NULL); + nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24); + nmtst_setting_ip_config_add_route(s_ip4, "10.0.0.0", 8, "172.16.0.1", 100); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(result); + g_assert_cmpint(g_strv_length((char **) result), ==, 2); + g_assert_cmpstr(result[0], ==, "10.0.0.1"); + g_assert_cmpstr(result[1], ==, "172.16.0.1"); + nm_clear_g_free(&result); + g_object_unref(conn); + } + + /* Test deduplication */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "192.168.1.1", + NULL); + nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24); + nmtst_setting_ip_config_add_route(s_ip4, "10.0.0.0", 16, "172.16.0.1", 100); + nmtst_setting_ip_config_add_route(s_ip4, "10.1.0.0", 16, "172.16.0.1", 100); + nmtst_setting_ip_config_add_route(s_ip4, "10.2.0.0", 16, "172.16.0.1", 100); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(result); + g_assert_cmpint(g_strv_length((char **) result), ==, 1); + g_assert_cmpstr(result[0], ==, "172.16.0.1"); + nm_clear_g_free(&result); + g_object_unref(conn); + } + + /* IPv6 gateway reachable via address prefix route */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "fd01::1", + NULL); + nmtst_setting_ip_config_add_address(s_ip6, "fd01::10", 64); + nm_connection_add_setting(conn, NM_SETTING(s_ip6)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(!result); + g_object_unref(conn); + } + + /* IPv6 gateway NOT reachable */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "fd02::1", + NULL); + nmtst_setting_ip_config_add_address(s_ip6, "fd01::10", 64); + nm_connection_add_setting(conn, NM_SETTING(s_ip6)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(result); + g_assert_cmpint(g_strv_length((char **) result), ==, 1); + g_assert_cmpstr(result[0], ==, "fd02::1"); + nm_clear_g_free(&result); + g_object_unref(conn); + } + + /* Multiple addresses, gateway reachable via second address */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "10.0.0.1", + NULL); + nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24); + nmtst_setting_ip_config_add_address(s_ip4, "10.0.0.5", 24); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(!result); + g_object_unref(conn); + } + + /* Unreachable gateways in both IPv4 and IPv6 */ + { + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "10.0.0.1", + NULL); + nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "fd02::1", + NULL); + nmtst_setting_ip_config_add_address(s_ip6, "fd01::10", 64); + nm_connection_add_setting(conn, NM_SETTING(s_ip6)); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(result); + g_assert_cmpint(g_strv_length((char **) result), ==, 2); + g_assert_cmpstr(result[0], ==, "10.0.0.1"); + g_assert_cmpstr(result[1], ==, "fd02::1"); + nm_clear_g_free(&result); + g_object_unref(conn); + } + + /* Onlink and IPv6-link-local routes */ + { + NMIPRoute *route; + + conn = + nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL); + nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24); + + route = nm_ip_route_new(AF_INET, "10.0.0.1", 8, "192.168.20.1", 100, NULL); + g_assert(route); + nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, g_variant_new_boolean(TRUE)); + g_assert(nm_setting_ip_config_add_route(s_ip4, route)); + nm_ip_route_unref(route); + + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL); + nmtst_setting_ip_config_add_address(s_ip6, "fd01::10", 64); + nm_connection_add_setting(conn, NM_SETTING(s_ip6)); + + route = nm_ip_route_new(AF_INET6, "fd02::", 64, "fd03::1111", 100, NULL); + g_assert(route); + nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, g_variant_new_boolean(TRUE)); + g_assert(nm_setting_ip_config_add_route(s_ip6, route)); + nm_ip_route_unref(route); + + nmtst_setting_ip_config_add_route(s_ip6, "fd04::", 64, "fe80::1111", 100); + + result = nm_connection_get_unreachable_gateways(conn); + g_assert(!result); + g_object_unref(conn); + } +} + +/*****************************************************************************/ + NMTST_DEFINE(); int @@ -11963,6 +12285,7 @@ main(int argc, char **argv) g_test_add_func("/core/general/test_dns_uri_get_legacy", test_dns_uri_parse_plain); g_test_add_func("/core/general/test_dns_uri_normalize", test_dns_uri_normalize); g_test_add_func("/core/general/test_dhcp_iaid_hexstr", test_dhcp_iaid_hexstr); + g_test_add_func("/core/general/test_unreachable_gateways", test_unreachable_gateways); return g_test_run(); } diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c index a3b03efe31..8dd90684e6 100644 --- a/src/nmcli/connections.c +++ b/src/nmcli/connections.c @@ -5645,6 +5645,7 @@ connection_warnings(NmCli *nmc, NMConnection *connection) guint i, found; const char *id; const char *deprecated; + gs_free char *gw_warning = NULL; deprecated = nmc_connection_check_deprecated(NM_CONNECTION(connection)); if (deprecated) @@ -5673,6 +5674,10 @@ connection_warnings(NmCli *nmc, NMConnection *connection) nm_connection_get_uuid(NM_CONNECTION(connection)), found); } + + gw_warning = nm_connection_get_unreachable_gateways_warning(connection, TRUE); + if (gw_warning) + nmc_printerr("Warning: %s.\n", gw_warning); } static void diff --git a/src/nmtui/nmt-editor.c b/src/nmtui/nmt-editor.c index f1dd43860d..723d4edce9 100644 --- a/src/nmtui/nmt-editor.c +++ b/src/nmtui/nmt-editor.c @@ -14,6 +14,7 @@ #include "nmt-editor.h" +#include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "nm-utils.h" #include "nmtui.h" @@ -153,10 +154,16 @@ save_connection_and_exit(NmtNewtButton *button, gpointer user_data) NmtEditor *editor = user_data; NmtEditorPrivate *priv = NMT_EDITOR_GET_PRIVATE(editor); NmtSyncOp op; - GError *error = NULL; + GError *error = NULL; + gs_free char *gw_warning = NULL; nm_connection_replace_settings_from_connection(priv->orig_connection, priv->edit_connection); + gw_warning = nm_connection_get_unreachable_gateways_warning(priv->orig_connection, TRUE); + if (gw_warning) { + nmt_newt_message_dialog(_("Warning: %s"), gw_warning); + } + nmt_sync_op_init(&op); if (NM_IS_REMOTE_CONNECTION(priv->orig_connection)) { nm_remote_connection_commit_changes_async(NM_REMOTE_CONNECTION(priv->orig_connection), diff --git a/src/tests/client/test-client.check-on-disk/test_005.expected b/src/tests/client/test-client.check-on-disk/test_005.expected new file mode 100644 index 0000000000..04f2f8adde --- /dev/null +++ b/src/tests/client/test-client.check-on-disk/test_005.expected @@ -0,0 +1,15 @@ +size: 683 +location: src/tests/client/test-client.py:test_005()/1 +cmd: $NMCLI c add type ethernet ifname eth0 con-name con-xx1 ipv4.method manual ipv4.addresses 192.168.1.1/24 ipv4.gateway 192.168.2.1 ipv4.routes '192.168.4.4 192.168.4.1' +lang: C +returncode: 0 +stdout: 80 bytes +>>> +Connection 'con-xx1' (UUID-con-xx1-REPLACED-REPLACED-REPLA) successfully added. + +<<< +stderr: 300 bytes +>>> +Warning: the following gateways are not directly reachable from any configured address or route: 192.168.2.1, 192.168.4.1. NetworkManager currently adds on-link routes for them automatically, but this will change in the future. Consider adding addresses or routes whose subnets cover these gateways. + +<<< diff --git a/src/tests/client/test-client.py b/src/tests/client/test-client.py index 37149d0530..3d618e35d5 100755 --- a/src/tests/client/test-client.py +++ b/src/tests/client/test-client.py @@ -2222,6 +2222,41 @@ class TestNmcli(unittest.TestCase): replace_stdout=replace_uuids, ) + @nm_test + def test_005(self): + self.init_001() + + replace_uuids = [] + + replace_uuids.append( + self.ctx.srv.ReplaceTextConUuid( + "con-xx1", "UUID-con-xx1-REPLACED-REPLACED-REPLA" + ) + ) + + # Check the warning about unreachable gateways + self.call_nmcli( + [ + "c", + "add", + "type", + "ethernet", + "ifname", + "eth0", + "con-name", + "con-xx1", + "ipv4.method", + "manual", + "ipv4.addresses", + "192.168.1.1/24", + "ipv4.gateway", + "192.168.2.1", + "ipv4.routes", + "192.168.4.4 192.168.4.1", + ], + replace_stdout=replace_uuids, + ) + @nm_test_no_dbus def test_offline(self): # Make sure we're not using D-Bus