summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLubomir Rintel <lkundrak@v3.sk>2017-06-22 16:46:13 +0200
committerLubomir Rintel <lkundrak@v3.sk>2018-09-24 15:17:02 +0200
commit2cec94bacce4a09c0e5ffa241b8d50fd4702dddc (patch)
treecd6861a3d1d0656855d722e0c4ffc45fd2423c73
parent753ffdbca8354e83f990d509f5e471cff89e161c (diff)
downloadNetworkManager-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.c1
-rw-r--r--src/nm-connectivity.c237
-rw-r--r--src/nm-connectivity.h1
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);