about summary refs log tree commit diff
path: root/src/core/nm-connectivity.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/nm-connectivity.c')
-rw-r--r--src/core/nm-connectivity.c209
1 files changed, 189 insertions, 20 deletions
diff --git a/src/core/nm-connectivity.c b/src/core/nm-connectivity.c
index c84d8a56..eea8adb2 100644
--- a/src/core/nm-connectivity.c
+++ b/src/core/nm-connectivity.c
@@ -622,6 +622,75 @@ _timeout_cb(gpointer user_data)
     cb_data_complete(cb_data, NM_CONNECTIVITY_LIMITED, "timeout");
     return G_SOURCE_REMOVE;
 }
+
+static gboolean
+easy_debug_enabled(void)
+{
+    static int enabled = 0;
+    int        e;
+
+    /* libcurl debug logging can be useful, but is very verbose.
+     * Only enable it when we have a certain environment variable set. */
+
+again:
+    e = g_atomic_int_get(&enabled);
+    if (G_UNLIKELY(e == 0)) {
+        e = _nm_utils_ascii_str_to_bool(g_getenv("NM_LOG_CONCHECK"), FALSE) ? 1 : -1;
+        if (G_UNLIKELY(!g_atomic_int_compare_and_exchange(&enabled, 0, e)))
+            goto again;
+    }
+    return e >= 0;
+}
+
+static int
+easy_debug_cb(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr)
+{
+    NMConnectivityCheckHandle *cb_data      = userptr;
+    gs_free char              *data_escaped = NULL;
+    const char                *msg;
+    gboolean                   print_data = FALSE;
+
+    switch (type) {
+    case CURLINFO_TEXT:
+        print_data = TRUE;
+        msg        = "== Info: ";
+        break;
+    case CURLINFO_DATA_OUT:
+        msg = "=> Send data";
+        break;
+    case CURLINFO_SSL_DATA_OUT:
+        msg = "=> Send SSL data";
+        break;
+    case CURLINFO_HEADER_IN:
+        msg = "<= Recv header";
+        break;
+    case CURLINFO_DATA_IN:
+        msg = "<= Recv data";
+        break;
+    case CURLINFO_SSL_DATA_IN:
+        msg = "<= Recv SSL data";
+        break;
+    case CURLINFO_HEADER_OUT:
+        print_data = TRUE;
+        msg        = "=> Send header: ";
+        return 0;
+    default:
+        return 0;
+    }
+
+    _LOG2T("libcurl %s%s%s%s",
+           msg,
+           NM_PRINT_FMT_QUOTED(print_data,
+                               "[",
+                               (data_escaped = nm_utils_buf_utf8safe_escape_cp(
+                                    data,
+                                    size,
+                                    NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL))
+                                   ?: "",
+                               "]",
+                               ""));
+    return 0;
+}
 #endif
 
 static gboolean
@@ -641,12 +710,14 @@ _idle_cb(gpointer user_data)
 
 #if WITH_CONCHECK
 static void
-do_curl_request(NMConnectivityCheckHandle *cb_data)
+do_curl_request(NMConnectivityCheckHandle *cb_data, const char *hosts)
 {
     CURLM *mhandle;
     CURL  *ehandle;
     long   resolve;
 
+    _LOG2T("set curl resolve list to '%s'", hosts);
+
     mhandle = curl_multi_init();
     if (!mhandle) {
         cb_data_complete(cb_data, NM_CONNECTIVITY_ERROR, "curl error");
@@ -660,6 +731,8 @@ do_curl_request(NMConnectivityCheckHandle *cb_data)
         return;
     }
 
+    cb_data->concheck.hosts = curl_slist_append(NULL, hosts);
+
     cb_data->concheck.curl_mhandle    = mhandle;
     cb_data->concheck.curl_ehandle    = ehandle;
     cb_data->concheck.request_headers = curl_slist_append(NULL, "Connection: close");
@@ -696,12 +769,103 @@ do_curl_request(NMConnectivityCheckHandle *cb_data)
     curl_easy_setopt(ehandle, CURLOPT_RESOLVE, cb_data->concheck.hosts);
     curl_easy_setopt(ehandle, CURLOPT_IPRESOLVE, resolve);
     curl_easy_setopt(ehandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
+    if (_LOGT_ENABLED() && easy_debug_enabled()) {
+        curl_easy_setopt(ehandle, CURLOPT_DEBUGFUNCTION, easy_debug_cb);
+        curl_easy_setopt(ehandle, CURLOPT_DEBUGDATA, cb_data);
+        curl_easy_setopt(ehandle, CURLOPT_VERBOSE, 1L);
+    }
 
     curl_multi_add_handle(mhandle, ehandle);
 }
 
 static void
-resolve_cb(GObject *object, GAsyncResult *res, gpointer user_data)
+system_resolver_resolve_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+    nm_auto_str_buf NMStrBuf   strbuf_hosts = NM_STR_BUF_INIT(0, FALSE);
+    gs_free_error GError      *resolv_error = NULL;
+    GList                     *list;
+    GList                     *iter;
+    NMConnectivityCheckHandle *cb_data;
+
+    list = g_resolver_lookup_by_name_finish(G_RESOLVER(source_object), res, &resolv_error);
+
+    if (nm_utils_error_is_cancelled(resolv_error))
+        return;
+
+    cb_data = user_data;
+
+    nm_assert(cb_data);
+    nm_assert(NM_IS_CONNECTIVITY(cb_data->self));
+
+    if (resolv_error) {
+        _LOG2D("failure to resolve name: %s", resolv_error->message);
+        cb_data_complete(cb_data, NM_CONNECTIVITY_LIMITED, "resolve-error");
+        return;
+    }
+
+    for (iter = list; iter; iter = iter->next) {
+        GInetAddress *a = iter->data;
+        char          str_addr[NM_UTILS_INET_ADDRSTRLEN];
+        int           addr_family;
+
+        switch (g_inet_address_get_family(a)) {
+        case G_SOCKET_FAMILY_IPV4:
+            addr_family = AF_INET;
+            break;
+        case G_SOCKET_FAMILY_IPV6:
+            addr_family = AF_INET6;
+            break;
+        default:
+            addr_family = AF_UNSPEC;
+            break;
+        }
+        if (cb_data->addr_family != AF_UNSPEC && cb_data->addr_family != addr_family)
+            continue;
+
+        if (strbuf_hosts.len == 0) {
+            nm_str_buf_append_printf(&strbuf_hosts,
+                                     "%s:%s:",
+                                     cb_data->concheck.con_config->host,
+                                     cb_data->concheck.con_config->port ?: "80");
+        } else
+            nm_str_buf_append_c(&strbuf_hosts, ',');
+
+        nm_str_buf_append(&strbuf_hosts,
+                          nm_utils_inet_ntop(addr_family, g_inet_address_to_bytes(a), str_addr));
+    }
+
+    g_list_free_full(list, g_object_unref);
+
+    if (strbuf_hosts.len == 0) {
+        _LOG2D("system resolver returned no usable IPv%c addresses",
+               nm_utils_addr_family_to_char(cb_data->addr_family));
+        cb_data_complete(cb_data, NM_CONNECTIVITY_LIMITED, "resolve-error");
+        return;
+    }
+
+    do_curl_request(cb_data, nm_str_buf_get_str(&strbuf_hosts));
+}
+
+static void
+system_resolver_resolve(NMConnectivityCheckHandle *cb_data)
+{
+    gs_unref_object GResolver *resolver = NULL;
+
+    _LOG2D("start request to '%s' (try resolving '%s' using system resolver)",
+           cb_data->concheck.con_config->uri,
+           cb_data->concheck.con_config->host);
+
+    resolver = g_resolver_get_default();
+
+    g_resolver_lookup_by_name_async(resolver,
+                                    cb_data->concheck.con_config->host,
+                                    cb_data->concheck.resolve_cancellable,
+                                    system_resolver_resolve_cb,
+                                    cb_data);
+}
+
+static void
+systemd_resolved_resolve_cb(GObject *object, GAsyncResult *res, gpointer user_data)
 {
     NMConnectivityCheckHandle *cb_data;
     gs_unref_variant GVariant *result    = NULL;
@@ -715,17 +879,24 @@ resolve_cb(GObject *object, GAsyncResult *res, gpointer user_data)
     nm_auto_str_buf NMStrBuf   strbuf_hosts = NM_STR_BUF_INIT(0, FALSE);
 
     result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), res, &error);
-    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    if (nm_utils_error_is_cancelled(error))
         return;
 
     cb_data = user_data;
 
-    g_clear_object(&cb_data->concheck.resolve_cancellable);
-
     if (!result) {
-        /* Never mind. Just let do curl do its own resolving. */
+        gs_free char *dbus_error = NULL;
+
         _LOG2D("can't resolve a name via systemd-resolved: %s", error->message);
-        do_curl_request(cb_data);
+
+        dbus_error = g_dbus_error_get_remote_error(error);
+        if (nm_streq0(dbus_error, "org.freedesktop.resolve1.NoNameServers")) {
+            cb_data_complete(cb_data, NM_CONNECTIVITY_LIMITED, "resolve-error");
+            return;
+        }
+
+        /* Never mind. Fallback to the system resolver. */
+        system_resolver_resolve(cb_data);
         return;
     }
 
@@ -759,14 +930,14 @@ resolve_cb(GObject *object, GAsyncResult *res, gpointer user_data)
 
         nm_str_buf_append(&strbuf_hosts, nm_utils_inet_ntop(addr_family, address_buf, str_addr));
     }
-    if (strbuf_hosts.len > 0) {
-        const char *s = nm_str_buf_get_str(&strbuf_hosts);
-
-        cb_data->concheck.hosts = curl_slist_append(NULL, s);
-        _LOG2T("set curl resolve list to '%s'", s);
+    if (strbuf_hosts.len == 0) {
+        _LOG2D("systemd-resolve returned no usable IPv%c addresses",
+               nm_utils_addr_family_to_char(cb_data->addr_family));
+        cb_data_complete(cb_data, NM_CONNECTIVITY_LIMITED, "resolve-error");
+        return;
     }
 
-    do_curl_request(cb_data);
+    do_curl_request(cb_data, nm_str_buf_get_str(&strbuf_hosts));
 }
 #endif
 
@@ -892,6 +1063,8 @@ nm_connectivity_check_start(NMConnectivity             *self,
             }
         }
 
+        cb_data->concheck.resolve_cancellable = g_cancellable_new();
+
         /* note that we pick up support for systemd-resolved right away when we need it.
          * We don't need to remember the setting, because we can (cheaply) check anew
          * on each request.
@@ -927,8 +1100,6 @@ nm_connectivity_check_start(NMConnectivity             *self,
                 return cb_data;
             }
 
-            cb_data->concheck.resolve_cancellable = g_cancellable_new();
-
             g_dbus_connection_call(dbus_connection,
                                    "org.freedesktop.resolve1",
                                    "/org/freedesktop/resolve1",
@@ -943,17 +1114,15 @@ nm_connectivity_check_start(NMConnectivity             *self,
                                    G_DBUS_CALL_FLAGS_NONE,
                                    -1,
                                    cb_data->concheck.resolve_cancellable,
-                                   resolve_cb,
+                                   systemd_resolved_resolve_cb,
                                    cb_data);
             _LOG2D("start request to '%s' (try resolving '%s' using systemd-resolved)",
                    cb_data->concheck.con_config->uri,
                    cb_data->concheck.con_config->host);
-        } else {
-            _LOG2D("start request to '%s' (systemd-resolved not available)",
-                   cb_data->concheck.con_config->uri);
-            do_curl_request(cb_data);
+            return cb_data;
         }
 
+        system_resolver_resolve(cb_data);
         return cb_data;
     }
 #endif