diff options
author | Lubomir Rintel <lkundrak@v3.sk> | 2017-06-22 16:46:13 +0200 |
---|---|---|
committer | Lubomir Rintel <lkundrak@v3.sk> | 2018-09-24 15:17:02 +0200 |
commit | 2cec94bacce4a09c0e5ffa241b8d50fd4702dddc (patch) | |
tree | cd6861a3d1d0656855d722e0c4ffc45fd2423c73 | |
parent | 753ffdbca8354e83f990d509f5e471cff89e161c (diff) | |
download | NetworkManager-2cec94bacce4a09c0e5ffa241b8d50fd4702dddc.tar.gz |
connectivity: use systemd-resolved for resolving the check endpoint
This allows us to use the correct DNS server for the particular interface
independent of what the system resolver is configured to use.
The ugly part:
Unfortunately, it is not all that easy. The libc's libresolv.so API does
not provide means for influencing neither interface nor name servers used
for DNS resolving.
Curl can also be compiled with c-ares resolver backend that does provide
the necessary functionality, but it requires and extra library and the
Linux distributions don't seem to enable it. (Fedora doesn't, which is a
good sign we don't have an option of relying on it.)
systemd-resolved does provide everything we need. If we take care to
keep its congfiguration up to date, we can use it to do the resolving on
a particular interface with that interface's DNS configuration. Great!
There's one more problem: Curl doesn't provide callbacks for resolving
host names. It doesn't, however, allow us to pass in the pre-resolved
hostnames in form of an CURLOPT_RESOLVE(3) option. This means we have to
parse the host name out of the URL ourselves. Fair enough I guess...
-rw-r--r-- | src/devices/nm-device.c | 1 | ||||
-rw-r--r-- | src/nm-connectivity.c | 237 | ||||
-rw-r--r-- | src/nm-connectivity.h | 1 |
3 files changed, 214 insertions, 25 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index b47b8914dd..035165dd34 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -2967,6 +2967,7 @@ concheck_start (NMDevice *self, is_periodic ? ", periodic-check" : ""); handle->c_handle = nm_connectivity_check_start (concheck_get_mgr (self), + nm_device_get_ip_ifindex (self), nm_device_get_ip_iface (self), concheck_cb, handle); diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c index 7ef99e54f9..2ce5481b49 100644 --- a/src/nm-connectivity.c +++ b/src/nm-connectivity.c @@ -17,12 +17,13 @@ * * Copyright (C) 2011 Thomas Bechtold <thomasbechtold@jpberlin.de> * Copyright (C) 2011 Dan Williams <dcbw@redhat.com> - * Copyright (C) 2016,2017 Red Hat, Inc. + * Copyright (C) 2016 - 2018 Red Hat, Inc. */ #include "nm-default.h" #include "nm-connectivity.h" +#include "nm-dbus-manager.h" #include <string.h> @@ -72,8 +73,11 @@ struct _NMConnectivityCheckHandle { struct { char *response; + int ifindex; + GCancellable *resolve_cancellable; CURL *curl_ehandle; struct curl_slist *request_headers; + struct curl_slist *hosts; GString *recv_msg; } concheck; @@ -98,6 +102,8 @@ typedef struct { CList handles_lst_head; CList completed_handles_lst_head; char *uri; + char *host; + char *port; char *response; gboolean enabled; guint interval; @@ -193,7 +199,9 @@ cb_data_complete (NMConnectivityCheckHandle *cb_data, curl_easy_cleanup (cb_data->concheck.curl_ehandle); curl_slist_free_all (cb_data->concheck.request_headers); + curl_slist_free_all (cb_data->concheck.hosts); } + nm_clear_g_cancellable (&cb_data->concheck.resolve_cancellable); #endif nm_clear_g_source (&cb_data->timeout_id); @@ -559,8 +567,139 @@ _idle_cb (gpointer user_data) return G_SOURCE_REMOVE; } +static void +do_curl_request (NMConnectivityCheckHandle *cb_data) +{ + NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (cb_data->self); + CURL *ehandle; + + ehandle = curl_easy_init (); + if (!ehandle) { + cb_data_free (cb_data, NM_CONNECTIVITY_ERROR, NULL, "curl error"); + return; + } + + cb_data->concheck.response = g_strdup (priv->response); + cb_data->concheck.curl_ehandle = ehandle; + cb_data->concheck.request_headers = curl_slist_append (NULL, "Connection: close"); + cb_data->timeout_id = g_timeout_add_seconds (20, _timeout_cb, cb_data); + + curl_easy_setopt (ehandle, CURLOPT_URL, priv->uri); + curl_easy_setopt (ehandle, CURLOPT_WRITEFUNCTION, easy_write_cb); + curl_easy_setopt (ehandle, CURLOPT_WRITEDATA, cb_data); + curl_easy_setopt (ehandle, CURLOPT_HEADERFUNCTION, easy_header_cb); + curl_easy_setopt (ehandle, CURLOPT_HEADERDATA, cb_data); + curl_easy_setopt (ehandle, CURLOPT_PRIVATE, cb_data); + curl_easy_setopt (ehandle, CURLOPT_HTTPHEADER, cb_data->concheck.request_headers); + curl_easy_setopt (ehandle, CURLOPT_INTERFACE, cb_data->ifspec); + curl_easy_setopt (ehandle, CURLOPT_RESOLVE, cb_data->concheck.hosts); + + curl_multi_add_handle (priv->concheck.curl_mhandle, ehandle); +} + +static void +resolve_cb (GObject *object, GAsyncResult *res, gpointer user_data) +{ + NMConnectivityCheckHandle *cb_data = user_data; + NMConnectivity *self; + NMConnectivityPrivate *priv; + GVariant *result; + GVariant *addresses; + gsize no_addresses; + int ifindex; + int family; + GVariant *address = NULL; + const guchar *address_buf; + gsize len = 0; + char str[INET6_ADDRSTRLEN + 1] = { 0, }; + char *host; + gsize i; + gs_free_error GError *error = NULL; + + result = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), res, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = cb_data->self; + priv = NM_CONNECTIVITY_GET_PRIVATE (self); + + if (!result) { + /* Never mind. Just let do curl do its own resolving. */ + _LOG2D ("can't resolve a name via systemd-resolved: %s", error->message); + do_curl_request (cb_data); + return; + } + + addresses = g_variant_get_child_value (result, 0); + no_addresses = g_variant_n_children (addresses); + g_variant_unref (result); + + for (i = 0; i < no_addresses; i++) { + g_variant_get_child (addresses, i, "(ii@ay)", &ifindex, &family, &address); + address_buf = g_variant_get_fixed_array (address, &len, 1); + + if ( (family == AF_INET && len == sizeof (struct in_addr)) + || (family == AF_INET6 && len == sizeof (struct in6_addr))) { + inet_ntop (family, address_buf, str, sizeof (str)); + host = g_strdup_printf ("%s:%s:%s", priv->host, + priv->port ? priv->port : "80", str); + cb_data->concheck.hosts = curl_slist_append (cb_data->concheck.hosts, host); + _LOG2T ("adding '%s' to curl resolve list", host); + g_free (host); + } + + g_variant_unref (address); + } + + g_variant_unref (addresses); + do_curl_request (cb_data); +} + +#define SD_RESOLVED_DNS 1 + +static void +resolved_proxy_created (GObject *object, GAsyncResult *res, gpointer user_data) +{ + NMConnectivityCheckHandle *cb_data = user_data; + NMConnectivity *self; + NMConnectivityPrivate *priv; + gs_free_error GError *error = NULL; + GDBusProxy *proxy; + + proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + self = cb_data->self; + priv = NM_CONNECTIVITY_GET_PRIVATE (self); + + if (!proxy) { + /* Log a warning, but still proceed without systemd-resolved */ + _LOG2D ("failed to connect to resolved via DBus: %s", error->message); + do_curl_request (cb_data); + return; + } + + g_dbus_proxy_call (proxy, + "ResolveHostname", + g_variant_new ("(isit)", + cb_data->concheck.ifindex, + priv->host, + (gint32) AF_UNSPEC, + (guint64) SD_RESOLVED_DNS), + G_DBUS_CALL_FLAGS_NONE, + -1, + cb_data->concheck.resolve_cancellable, + resolve_cb, + cb_data); + g_object_unref (proxy); + + _LOG2D ("resolving '%s' for '%s' using systemd-resolved", priv->host, priv->uri); +} + NMConnectivityCheckHandle * nm_connectivity_check_start (NMConnectivity *self, + int ifindex, const char *iface, NMConnectivityCheckCallback callback, gpointer user_data) @@ -585,35 +724,29 @@ nm_connectivity_check_start (NMConnectivity *self, cb_data->ifspec = g_strdup_printf ("if!%s", iface); #if WITH_CONCHECK - if (iface) { - CURL *ehandle; - - if ( priv->enabled - && (ehandle = curl_easy_init ())) { - - cb_data->concheck.response = g_strdup (priv->response); - cb_data->concheck.curl_ehandle = ehandle; - cb_data->concheck.request_headers = curl_slist_append (NULL, "Connection: close"); - curl_easy_setopt (ehandle, CURLOPT_URL, priv->uri); - curl_easy_setopt (ehandle, CURLOPT_WRITEFUNCTION, easy_write_cb); - curl_easy_setopt (ehandle, CURLOPT_WRITEDATA, cb_data); - curl_easy_setopt (ehandle, CURLOPT_HEADERFUNCTION, easy_header_cb); - curl_easy_setopt (ehandle, CURLOPT_HEADERDATA, cb_data); - curl_easy_setopt (ehandle, CURLOPT_PRIVATE, cb_data); - curl_easy_setopt (ehandle, CURLOPT_HTTPHEADER, cb_data->concheck.request_headers); - curl_easy_setopt (ehandle, CURLOPT_INTERFACE, cb_data->ifspec); - curl_multi_add_handle (priv->concheck.curl_mhandle, ehandle); - - cb_data->timeout_id = g_timeout_add_seconds (20, _timeout_cb, cb_data); - - _LOG2D ("start request to '%s'", priv->uri); - return cb_data; - } + if (iface && ifindex > 0 && priv->enabled && priv->host) { + cb_data->concheck.ifindex = ifindex; + cb_data->concheck.resolve_cancellable = g_cancellable_new (); + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + NULL, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + cb_data->concheck.resolve_cancellable, + resolved_proxy_created, + cb_data); + + _LOG2D ("start request to '%s'", priv->uri); + return cb_data; } #endif _LOG2D ("start fake request"); cb_data->timeout_id = g_idle_add (_idle_cb, cb_data); + return cb_data; } @@ -649,6 +782,54 @@ nm_connectivity_get_interval (NMConnectivity *self) : 0; } +static gboolean +host_and_port_from_uri (const char *uri, char **host, char **port) +{ + const char *p = uri; + const char *host_begin = NULL; + size_t host_len = 0; + const char *port_begin = NULL; + size_t port_len = 0; + + /* scheme */ + while (*p != ':' && *p != '/') { + if (!*p++) + return FALSE; + } + + /* :// */ + if (*p++ != ':') + return FALSE; + if (*p++ != '/') + return FALSE; + if (*p++ != '/') + return FALSE; + /* host */ + if (*p == '[') + return FALSE; + host_begin = p; + while (*p && *p != ':' && *p != '/') { + host_len++; + p++; + } + if (host_len == 0) + return FALSE; + *host = strndup (host_begin, host_len); + + /* port */ + if (*p++ == ':') { + port_begin = p; + while (*p && *p != '/') { + port_len++; + p++; + } + if (port_len) + *port = strndup (port_begin, port_len); + } + + return TRUE; +} + static void update_config (NMConnectivity *self, NMConfigData *config_data) { @@ -682,6 +863,10 @@ update_config (NMConnectivity *self, NMConfigData *config_data) if (changed) { g_free (priv->uri); priv->uri = g_strdup (uri); + + g_clear_pointer (&priv->host, g_free); + g_clear_pointer (&priv->port, g_free); + host_and_port_from_uri (uri, &priv->host, &priv->port); } /* Set the interval. */ @@ -785,6 +970,8 @@ dispose (GObject *object) cb_data_complete (cb_data, NM_CONNECTIVITY_DISPOSING, "shutting down"); g_clear_pointer (&priv->uri, g_free); + g_clear_pointer (&priv->host, g_free); + g_clear_pointer (&priv->port, g_free); g_clear_pointer (&priv->response, g_free); #if WITH_CONCHECK diff --git a/src/nm-connectivity.h b/src/nm-connectivity.h index 178f27ad9a..2bf07bc080 100644 --- a/src/nm-connectivity.h +++ b/src/nm-connectivity.h @@ -58,6 +58,7 @@ typedef void (*NMConnectivityCheckCallback) (NMConnectivity *self, gpointer user_data); NMConnectivityCheckHandle *nm_connectivity_check_start (NMConnectivity *self, + int ifindex, const char *iface, NMConnectivityCheckCallback callback, gpointer user_data); |