about summary refs log tree commit diff
path: root/src/libnm-core-aux-intern/nm-libnm-core-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-core-aux-intern/nm-libnm-core-utils.c')
-rw-r--r--src/libnm-core-aux-intern/nm-libnm-core-utils.c193
1 files changed, 193 insertions, 0 deletions
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 a39f6454..9a0b5e7c 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,196 @@ 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;
+    const char          *msg =
+        N_("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");
+
+    gateways = nm_connection_get_unreachable_gateways(connection);
+    if (!gateways)
+        return NULL;
+
+    gw_list = g_strjoinv(", ", (char **) gateways);
+
+    NM_PRAGMA_WARNING_DISABLE("-Wformat-nonliteral")
+    if (translate)
+        return g_strdup_printf(_(msg), gw_list);
+    return g_strdup_printf(msg, gw_list);
+    NM_PRAGMA_WARNING_REENABLE
+}