diff options
| author | Michael Biebl <biebl@debian.org> | 2021-02-11 18:11:46 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-02-11 18:11:46 +0100 |
| commit | 80ec1decc49c72efec2a8b87c06245c92c0ab807 (patch) | |
| tree | e3b229aa94e8dcf0590f2317664176e7b8f7607b /src/nm-connectivity.c | |
| parent | 65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (diff) | |
New upstream version 1.29.90 upstream/1.29.90
Diffstat (limited to 'src/nm-connectivity.c')
| -rw-r--r-- | src/nm-connectivity.c | 1229 |
1 files changed, 0 insertions, 1229 deletions
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c deleted file mode 100644 index 3cd1d4b8..00000000 --- a/src/nm-connectivity.c +++ /dev/null @@ -1,1229 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Copyright (C) 2011 Thomas Bechtold <thomasbechtold@jpberlin.de> - * Copyright (C) 2011 Dan Williams <dcbw@redhat.com> - * Copyright (C) 2016 - 2018 Red Hat, Inc. - */ - -#include "nm-default.h" - -#include "nm-connectivity.h" - -#if WITH_CONCHECK - #include <curl/curl.h> -#endif -#include <linux/rtnetlink.h> -#include <glib-unix.h> - -#include "c-list/src/c-list.h" -#include "nm-core-internal.h" -#include "nm-config.h" -#include "NetworkManagerUtils.h" -#include "nm-dbus-manager.h" -#include "dns/nm-dns-manager.h" - -#define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n" - -/*****************************************************************************/ - -static NM_UTILS_LOOKUP_STR_DEFINE(_state_to_string, - int /*NMConnectivityState*/, - NM_UTILS_LOOKUP_DEFAULT_WARN("???"), - NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_UNKNOWN, "UNKNOWN"), - NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_NONE, "NONE"), - NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_LIMITED, "LIMITED"), - NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_PORTAL, "PORTAL"), - NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_FULL, "FULL"), - - NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_ERROR, "ERROR"), - NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_FAKE, "FAKE"), - NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_CANCELLED, "CANCELLED"), - NM_UTILS_LOOKUP_STR_ITEM(NM_CONNECTIVITY_DISPOSING, - "DISPOSING"), ); - -const char * -nm_connectivity_state_to_string(NMConnectivityState state) -{ - return _state_to_string(state); -} - -/*****************************************************************************/ - -typedef struct { - guint ref_count; - char *uri; - char *host; - char *port; - char *response; -} ConConfig; - -struct _NMConnectivityCheckHandle { - CList handles_lst; - NMConnectivity * self; - NMConnectivityCheckCallback callback; - gpointer user_data; - - char *ifspec; - - const char *completed_log_message; - char * completed_log_message_free; - -#if WITH_CONCHECK - struct { - ConConfig *con_config; - - GCancellable * resolve_cancellable; - CURLM * curl_mhandle; - CURL * curl_ehandle; - struct curl_slist *request_headers; - struct curl_slist *hosts; - - gsize response_good_cnt; - - guint curl_timer; - int ch_ifindex; - } concheck; -#endif - - guint64 request_counter; - - int addr_family; - - guint timeout_id; - - NMConnectivityState completed_state; - const char * completed_reason; -}; - -enum { - CONFIG_CHANGED, - - LAST_SIGNAL -}; - -static guint signals[LAST_SIGNAL] = {0}; - -typedef struct { - CList handles_lst_head; - CList completed_handles_lst_head; - NMConfig * config; - ConConfig *con_config; - guint interval; - - bool enabled : 1; - bool uri_valid : 1; -} NMConnectivityPrivate; - -struct _NMConnectivity { - GObject parent; - NMConnectivityPrivate _priv; -}; - -struct _NMConnectivityClass { - GObjectClass parent; -}; - -G_DEFINE_TYPE(NMConnectivity, nm_connectivity, G_TYPE_OBJECT) - -#define NM_CONNECTIVITY_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMConnectivity, NM_IS_CONNECTIVITY) - -NM_DEFINE_SINGLETON_GETTER(NMConnectivity, nm_connectivity_get, NM_TYPE_CONNECTIVITY); - -/*****************************************************************************/ - -#define _NMLOG_DOMAIN LOGD_CONCHECK -#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "connectivity", __VA_ARGS__) - -#define _NMLOG2_DOMAIN LOGD_CONCHECK -#define _NMLOG2(level, ...) \ - G_STMT_START \ - { \ - const NMLogLevel __level = (level); \ - \ - if (nm_logging_enabled(__level, _NMLOG2_DOMAIN)) { \ - _nm_log(__level, \ - _NMLOG2_DOMAIN, \ - 0, \ - (cb_data->ifspec ? &cb_data->ifspec[3] : NULL), \ - NULL, \ - "connectivity: (%s,IPv%c,%" G_GUINT64_FORMAT \ - ") " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ - (cb_data->ifspec ? &cb_data->ifspec[3] : ""), \ - nm_utils_addr_family_to_char(cb_data->addr_family), \ - cb_data->request_counter _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ - } \ - } \ - G_STMT_END - -/*****************************************************************************/ - -#if WITH_CONCHECK -static ConConfig * -_con_config_ref(ConConfig *con_config) -{ - if (con_config) { - nm_assert(con_config->ref_count > 0); - ++con_config->ref_count; - } - return con_config; -} -#endif - -static void -_con_config_unref(ConConfig *con_config) -{ - if (!con_config) - return; - - nm_assert(con_config->ref_count > 0); - - if (--con_config->ref_count != 0) - return; - - g_free(con_config->uri); - g_free(con_config->host); - g_free(con_config->port); - g_free(con_config->response); - g_slice_free(ConConfig, con_config); -} - -#if WITH_CONCHECK -static const char * -_con_config_get_response(const ConConfig *con_config) -{ - return con_config->response ?: NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE; -} -#endif - -/*****************************************************************************/ - -static void -cb_data_complete(NMConnectivityCheckHandle *cb_data, - NMConnectivityState state, - const char * log_message) -{ - NMConnectivity *self; - - nm_assert(cb_data); - nm_assert(NM_IS_CONNECTIVITY(cb_data->self)); - nm_assert(cb_data->callback); - nm_assert(state != NM_CONNECTIVITY_UNKNOWN); - nm_assert(log_message); - - self = cb_data->self; - - /* mark the handle as completing. After this point, nm_connectivity_check_cancel() - * is no longer possible. */ - cb_data->self = NULL; - - c_list_unlink_stale(&cb_data->handles_lst); - -#if WITH_CONCHECK - if (cb_data->concheck.curl_ehandle) { - /* Contrary to what cURL manual claim it is *not* safe to remove - * the easy handle "at any moment"; specifically it's not safe to - * remove *any* handle from within a libcurl callback. That is - * why we queue completed handles in this case. - * - * cb_data_complete() is however only called *not* from within a - * libcurl callback. So, this is fine. */ - curl_easy_setopt(cb_data->concheck.curl_ehandle, CURLOPT_WRITEFUNCTION, NULL); - curl_easy_setopt(cb_data->concheck.curl_ehandle, CURLOPT_WRITEDATA, NULL); - curl_easy_setopt(cb_data->concheck.curl_ehandle, CURLOPT_HEADERFUNCTION, NULL); - curl_easy_setopt(cb_data->concheck.curl_ehandle, CURLOPT_HEADERDATA, NULL); - curl_easy_setopt(cb_data->concheck.curl_ehandle, CURLOPT_PRIVATE, NULL); - curl_easy_setopt(cb_data->concheck.curl_ehandle, CURLOPT_HTTPHEADER, NULL); - - curl_multi_remove_handle(cb_data->concheck.curl_mhandle, cb_data->concheck.curl_ehandle); - curl_easy_cleanup(cb_data->concheck.curl_ehandle); - curl_multi_cleanup(cb_data->concheck.curl_mhandle); - - curl_slist_free_all(cb_data->concheck.request_headers); - curl_slist_free_all(cb_data->concheck.hosts); - } - nm_clear_g_source(&cb_data->concheck.curl_timer); - nm_clear_g_cancellable(&cb_data->concheck.resolve_cancellable); -#endif - - nm_clear_g_source(&cb_data->timeout_id); - - _LOG2D("check completed: %s; %s", nm_connectivity_state_to_string(state), log_message); - - cb_data->callback(self, cb_data, state, cb_data->user_data); - - /* Note: self might be a danling pointer at this point. It must not be used - * after this point, and all callers must either take a reference first, or - * not use the self pointer too. */ - -#if WITH_CONCHECK - _con_config_unref(cb_data->concheck.con_config); -#endif - g_free(cb_data->ifspec); - if (cb_data->completed_log_message_free) - g_free(cb_data->completed_log_message_free); - g_slice_free(NMConnectivityCheckHandle, cb_data); -} - -/*****************************************************************************/ - -#if WITH_CONCHECK - -static void -cb_data_queue_completed(NMConnectivityCheckHandle *cb_data, - NMConnectivityState state, - const char * log_message_static, - char * log_message_take /* take */) -{ - nm_assert(cb_data); - nm_assert(NM_IS_CONNECTIVITY(cb_data->self)); - nm_assert(state != NM_CONNECTIVITY_UNKNOWN); - nm_assert(log_message_static || log_message_take); - nm_assert(cb_data->completed_state == NM_CONNECTIVITY_UNKNOWN); - nm_assert(!cb_data->completed_log_message); - nm_assert(c_list_contains(&NM_CONNECTIVITY_GET_PRIVATE(cb_data->self)->handles_lst_head, - &cb_data->handles_lst)); - - cb_data->completed_state = state; - cb_data->completed_log_message = log_message_static ?: log_message_take; - cb_data->completed_log_message_free = log_message_take; - - c_list_unlink_stale(&cb_data->handles_lst); - c_list_link_tail(&NM_CONNECTIVITY_GET_PRIVATE(cb_data->self)->completed_handles_lst_head, - &cb_data->handles_lst); -} - -static void -_complete_queued(NMConnectivity *self) -{ - NMConnectivity * self_keep_alive = NULL; - NMConnectivityPrivate * priv = NM_CONNECTIVITY_GET_PRIVATE(self); - NMConnectivityCheckHandle *cb_data; - - while ((cb_data = c_list_first_entry(&priv->completed_handles_lst_head, - NMConnectivityCheckHandle, - handles_lst))) { - if (!self_keep_alive) - self_keep_alive = g_object_ref(self); - cb_data_complete(cb_data, cb_data->completed_state, cb_data->completed_log_message); - } - nm_g_object_unref(self_keep_alive); -} - -static gboolean -_con_curl_check_connectivity(CURLM *mhandle, int sockfd, int ev_bitmask) -{ - NMConnectivityCheckHandle *cb_data; - CURLMsg * msg; - int m_left; - long response_code; - CURLMcode ret; - int running_handles; - gboolean success = TRUE; - - ret = curl_multi_socket_action(mhandle, sockfd, ev_bitmask, &running_handles); - if (ret != CURLM_OK) { - _LOGD("connectivity check failed: (%d) %s", ret, curl_multi_strerror(ret)); - success = FALSE; - } - - while ((msg = curl_multi_info_read(mhandle, &m_left))) { - const char *response; - CURLcode eret; - - if (msg->msg != CURLMSG_DONE) - continue; - - /* Here we have completed a session. Check easy session result. */ - eret = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char **) &cb_data); - if (eret != CURLE_OK) { - _LOGD("curl cannot extract cb_data for easy handle, skipping msg: (%d) %s", - eret, - curl_easy_strerror(eret)); - success = FALSE; - continue; - } - - nm_assert(cb_data); - nm_assert(NM_IS_CONNECTIVITY(cb_data->self)); - - if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) { - /* callback was already invoked earlier. Nothing to do. */ - continue; - } - - if (msg->data.result != CURLE_OK) { - cb_data_queue_completed(cb_data, - NM_CONNECTIVITY_LIMITED, - NULL, - g_strdup_printf("check failed: (%d) %s", - msg->data.result, - curl_easy_strerror(msg->data.result))); - continue; - } - - response = _con_config_get_response(cb_data->concheck.con_config); - - if (response[0] == '\0' - && (curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code) - == CURLE_OK)) { - if (response_code == 204) { - /* We expected an empty response, and we got a 204 response code (no content). - * We may or may not have received any content (we would ignore it). - * Anyway, the response_code 204 means we are good. */ - cb_data_queue_completed(cb_data, - NM_CONNECTIVITY_FULL, - "no content, as expected", - NULL); - continue; - } - - if (response_code == 200 && cb_data->concheck.response_good_cnt == 0) { - /* we expected no response, and indeed we got an empty reply (with status code 200) */ - cb_data_queue_completed(cb_data, - NM_CONNECTIVITY_FULL, - "empty response, as expected", - NULL); - continue; - } - } - - /* If we get here, it means that easy_write_cb() didn't read enough - * bytes to be able to do a match, or that we were asking for no content - * (204 response code) and we actually got some. Either way, that is - * an indication of a captive portal */ - cb_data_queue_completed(cb_data, NM_CONNECTIVITY_PORTAL, "unexpected short response", NULL); - } - - /* if we return a failure, we don't know what went wrong. It's likely serious, because - * a failure here is not expected. Return FALSE, so that we stop polling the file descriptor. - * Worst case, this leaves the pending connectivity check unhandled, until our regular - * time-out kicks in. */ - return success; -} - -static gboolean -_con_curl_timeout_cb(gpointer user_data) -{ - NMConnectivityCheckHandle *cb_data = user_data; - - _con_curl_check_connectivity(cb_data->concheck.curl_mhandle, CURL_SOCKET_TIMEOUT, 0); - _complete_queued(cb_data->self); - return G_SOURCE_CONTINUE; -} - -static int -multi_timer_cb(CURLM *multi, long timeout_msec, void *userdata) -{ - NMConnectivityCheckHandle *cb_data = userdata; - - nm_clear_g_source(&cb_data->concheck.curl_timer); - if (timeout_msec != -1) - cb_data->concheck.curl_timer = g_timeout_add(timeout_msec, _con_curl_timeout_cb, cb_data); - return 0; -} - -typedef struct { - NMConnectivityCheckHandle *cb_data; - - GSource *source; - - /* this is a very simplistic weak-pointer. If ConCurlSockData gets - * destroyed, it will set *destroy_notify to TRUE. - * - * _con_curl_socketevent_cb() uses this to detect whether it can - * safely access @fdp after _con_curl_check_connectivity(). */ - gboolean *destroy_notify; - -} ConCurlSockData; - -static gboolean -_con_curl_socketevent_cb(int fd, GIOCondition condition, gpointer user_data) -{ - ConCurlSockData * fdp = user_data; - NMConnectivityCheckHandle *cb_data = fdp->cb_data; - int action = 0; - gboolean fdp_destroyed = FALSE; - gboolean success; - - if (condition & G_IO_IN) - action |= CURL_CSELECT_IN; - if (condition & G_IO_OUT) - action |= CURL_CSELECT_OUT; - if (condition & G_IO_ERR) - action |= CURL_CSELECT_ERR; - - nm_assert(!fdp->destroy_notify); - fdp->destroy_notify = &fdp_destroyed; - - success = _con_curl_check_connectivity(cb_data->concheck.curl_mhandle, fd, action); - - if (fdp_destroyed) { - /* hups. fdp got invalidated during _con_curl_check_connectivity(). That's fine, - * just don't touch it. */ - } else { - nm_assert(fdp->destroy_notify == &fdp_destroyed); - fdp->destroy_notify = NULL; - if (!success) - nm_clear_g_source_inst(&fdp->source); - } - - _complete_queued(cb_data->self); - - return G_SOURCE_CONTINUE; -} - -static int -multi_socket_cb(CURL *e_handle, curl_socket_t fd, int what, void *userdata, void *socketp) -{ - NMConnectivityCheckHandle *cb_data = userdata; - ConCurlSockData * fdp = socketp; - - (void) _NM_ENSURE_TYPE(int, fd); - - if (what == CURL_POLL_REMOVE) { - if (fdp) { - if (fdp->destroy_notify) - *fdp->destroy_notify = TRUE; - nm_clear_g_source_inst(&fdp->source); - curl_multi_assign(cb_data->concheck.curl_mhandle, fd, NULL); - g_slice_free(ConCurlSockData, fdp); - } - } else { - GIOCondition condition; - - if (!fdp) { - fdp = g_slice_new(ConCurlSockData); - *fdp = (ConCurlSockData){ - .cb_data = cb_data, - }; - curl_multi_assign(cb_data->concheck.curl_mhandle, fd, fdp); - } else - nm_clear_g_source_inst(&fdp->source); - - if (what == CURL_POLL_IN) - condition = G_IO_IN; - else if (what == CURL_POLL_OUT) - condition = G_IO_OUT; - else if (what == CURL_POLL_INOUT) - condition = G_IO_IN | G_IO_OUT; - else - condition = 0; - - if (condition) { - fdp->source = nm_g_unix_fd_source_new(fd, - condition, - G_PRIORITY_DEFAULT, - _con_curl_socketevent_cb, - fdp, - NULL); - g_source_attach(fdp->source, NULL); - } - } - - return CURLM_OK; -} - -static size_t -easy_header_cb(char *buffer, size_t size, size_t nitems, void *userdata) -{ - NMConnectivityCheckHandle *cb_data = userdata; - size_t len = size * nitems; - - if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) { - /* already completed. */ - return 0; - } - - if (len >= sizeof(HEADER_STATUS_ONLINE) - 1 - && !g_ascii_strncasecmp(buffer, HEADER_STATUS_ONLINE, sizeof(HEADER_STATUS_ONLINE) - 1)) { - cb_data_queue_completed(cb_data, NM_CONNECTIVITY_FULL, "status header found", NULL); - return 0; - } - - return len; -} - -static size_t -easy_write_cb(void *buffer, size_t size, size_t nmemb, void *userdata) -{ - NMConnectivityCheckHandle *cb_data = userdata; - size_t len = size * nmemb; - size_t response_len; - size_t check_len; - const char * response; - - if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) { - /* already completed. */ - return 0; - } - - if (len == 0) { - /* no data. That can happen, it's fine. */ - return len; - } - - response = _con_config_get_response(cb_data->concheck.con_config); - - if (response[0] == '\0') { - /* no response expected. We are however graceful and accept any - * extra response that we might receive. We determine the empty - * response based on the status code 204. - * - * Continue receiving... */ - cb_data->concheck.response_good_cnt += len; - - if (cb_data->concheck.response_good_cnt > (gsize)(100 * 1024)) { - /* we expect an empty response. We accept either - * 1) status code 204 and any response - * 2) status code 200 and an empty response. - * - * Here, we want to continue receiving data, to see whether we have - * case 1). Arguably, the server shouldn't send us 204 with a non-empty - * response, but we accept that also with a non-empty response, so - * keep receiving. - * - * However, if we get an excessive amount of data, we put a stop on it - * and fail. */ - cb_data_queue_completed(cb_data, - NM_CONNECTIVITY_PORTAL, - "unexpected non-empty response", - NULL); - return 0; - } - - return len; - } - - nm_assert(cb_data->concheck.response_good_cnt < strlen(response)); - - response_len = strlen(response); - - check_len = NM_MIN(len, response_len - cb_data->concheck.response_good_cnt); - - if (strncmp(&response[cb_data->concheck.response_good_cnt], buffer, check_len) != 0) { - cb_data_queue_completed(cb_data, NM_CONNECTIVITY_PORTAL, "unexpected response", NULL); - return 0; - } - - cb_data->concheck.response_good_cnt += len; - - if (cb_data->concheck.response_good_cnt >= response_len) { - /* We already have enough data, and it matched. */ - cb_data_queue_completed(cb_data, NM_CONNECTIVITY_FULL, "expected response", NULL); - return 0; - } - - return len; -} - -static gboolean -_timeout_cb(gpointer user_data) -{ - NMConnectivityCheckHandle *cb_data = user_data; - - nm_assert(NM_IS_CONNECTIVITY(cb_data->self)); - nm_assert(c_list_contains(&NM_CONNECTIVITY_GET_PRIVATE(cb_data->self)->handles_lst_head, - &cb_data->handles_lst)); - - cb_data_complete(cb_data, NM_CONNECTIVITY_LIMITED, "timeout"); - return G_SOURCE_REMOVE; -} -#endif - -static gboolean -_idle_cb(gpointer user_data) -{ - NMConnectivityCheckHandle *cb_data = user_data; - - nm_assert(NM_IS_CONNECTIVITY(cb_data->self)); - nm_assert(c_list_contains(&NM_CONNECTIVITY_GET_PRIVATE(cb_data->self)->handles_lst_head, - &cb_data->handles_lst)); - nm_assert(cb_data->completed_reason); - - cb_data->timeout_id = 0; - cb_data_complete(cb_data, cb_data->completed_state, cb_data->completed_reason); - return G_SOURCE_REMOVE; -} - -#if WITH_CONCHECK -static void -do_curl_request(NMConnectivityCheckHandle *cb_data) -{ - CURLM *mhandle; - CURL * ehandle; - long resolve; - - mhandle = curl_multi_init(); - if (!mhandle) { - cb_data_complete(cb_data, NM_CONNECTIVITY_ERROR, "curl error"); - return; - } - - ehandle = curl_easy_init(); - if (!ehandle) { - curl_multi_cleanup(mhandle); - cb_data_complete(cb_data, NM_CONNECTIVITY_ERROR, "curl error"); - return; - } - - cb_data->concheck.curl_mhandle = mhandle; - 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_multi_setopt(mhandle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb); - curl_multi_setopt(mhandle, CURLMOPT_SOCKETDATA, cb_data); - curl_multi_setopt(mhandle, CURLMOPT_TIMERFUNCTION, multi_timer_cb); - curl_multi_setopt(mhandle, CURLMOPT_TIMERDATA, cb_data); - - switch (cb_data->addr_family) { - case AF_INET: - resolve = CURL_IPRESOLVE_V4; - break; - case AF_INET6: - resolve = CURL_IPRESOLVE_V6; - break; - case AF_UNSPEC: - resolve = CURL_IPRESOLVE_WHATEVER; - break; - default: - resolve = CURL_IPRESOLVE_WHATEVER; - g_warn_if_reached(); - } - - curl_easy_setopt(ehandle, CURLOPT_URL, cb_data->concheck.con_config->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_easy_setopt(ehandle, CURLOPT_IPRESOLVE, resolve); - - curl_multi_add_handle(mhandle, ehandle); -} - -static void -resolve_cb(GObject *object, GAsyncResult *res, gpointer user_data) -{ - NMConnectivityCheckHandle *cb_data; - gs_unref_variant GVariant *result = NULL; - gs_unref_variant GVariant *addresses = NULL; - gsize no_addresses; - int ifindex; - int addr_family; - gsize len = 0; - gsize i; - gs_free_error GError *error = NULL; - - result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(object), res, &error); - if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - 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. */ - _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); - - for (i = 0; i < no_addresses; i++) { - gs_unref_variant GVariant *address = NULL; - char str_addr[NM_UTILS_INET_ADDRSTRLEN]; - gs_free char * host_entry = NULL; - const guchar * address_buf; - - g_variant_get_child(addresses, i, "(ii@ay)", &ifindex, &addr_family, &address); - - if (cb_data->addr_family != AF_UNSPEC && cb_data->addr_family != addr_family) - continue; - - address_buf = g_variant_get_fixed_array(address, &len, 1); - if ((addr_family == AF_INET && len != sizeof(struct in_addr)) - || (addr_family == AF_INET6 && len != sizeof(struct in6_addr))) - continue; - - host_entry = g_strdup_printf("%s:%s:%s", - cb_data->concheck.con_config->host, - cb_data->concheck.con_config->port ?: "80", - nm_utils_inet_ntop(addr_family, address_buf, str_addr)); - cb_data->concheck.hosts = curl_slist_append(cb_data->concheck.hosts, host_entry); - _LOG2T("adding '%s' to curl resolve list", host_entry); - } - - do_curl_request(cb_data); -} -#endif - -#define SD_RESOLVED_DNS ((guint64)(1LL << 0)) - -static NMConnectivityState -check_platform_config(NMConnectivity *self, - NMPlatform * platform, - int ifindex, - int addr_family, - const char ** reason) -{ - const NMDedupMultiHeadEntry *addresses; - const NMDedupMultiHeadEntry *routes; - - if (!nm_platform_link_is_connected(platform, ifindex)) { - NM_SET_OUT(reason, "no carrier"); - return NM_CONNECTIVITY_NONE; - } - - addresses = nm_platform_lookup_object(platform, - addr_family == AF_INET ? NMP_OBJECT_TYPE_IP4_ADDRESS - : NMP_OBJECT_TYPE_IP6_ADDRESS, - ifindex); - if (!addresses || addresses->len == 0) { - NM_SET_OUT(reason, "no IP address configured"); - return NM_CONNECTIVITY_NONE; - } - - routes = nm_platform_lookup_object(platform, - addr_family == AF_INET ? NMP_OBJECT_TYPE_IP4_ROUTE - : NMP_OBJECT_TYPE_IP6_ROUTE, - ifindex); - if (!routes || routes->len == 0) { - NM_SET_OUT(reason, "no IP route configured"); - return NM_CONNECTIVITY_NONE; - } - - switch (addr_family) { - case AF_INET: - { - const NMPlatformIP4Route *route; - gboolean found_global = FALSE; - NMDedupMultiIter iter; - const NMPObject * plobj; - - /* For IPv4 also require a route with global scope. */ - nmp_cache_iter_for_each (&iter, routes, &plobj) { - route = NMP_OBJECT_CAST_IP4_ROUTE(plobj); - if (nm_platform_route_scope_inv(route->scope_inv) == RT_SCOPE_UNIVERSE) { - found_global = TRUE; - break; - } - } - - if (!found_global) { - NM_SET_OUT(reason, "no global route configured"); - return NM_CONNECTIVITY_LIMITED; - } - break; - } - case AF_INET6: - /* Route scopes aren't meaningful for IPv6 so any route is fine. */ - break; - default: - g_return_val_if_reached(FALSE); - } - - NM_SET_OUT(reason, NULL); - return NM_CONNECTIVITY_UNKNOWN; -} - -NMConnectivityCheckHandle * -nm_connectivity_check_start(NMConnectivity * self, - int addr_family, - NMPlatform * platform, - int ifindex, - const char * iface, - NMConnectivityCheckCallback callback, - gpointer user_data) -{ - NMConnectivityPrivate * priv; - NMConnectivityCheckHandle *cb_data; - static guint64 request_counter = 0; - - g_return_val_if_fail(NM_IS_CONNECTIVITY(self), NULL); - g_return_val_if_fail(callback, NULL); - nm_assert(!platform || NM_IS_PLATFORM(platform)); - - priv = NM_CONNECTIVITY_GET_PRIVATE(self); - - cb_data = g_slice_new0(NMConnectivityCheckHandle); - cb_data->self = self; - cb_data->request_counter = ++request_counter; - c_list_link_tail(&priv->handles_lst_head, &cb_data->handles_lst); - cb_data->callback = callback; - cb_data->user_data = user_data; - cb_data->completed_state = NM_CONNECTIVITY_UNKNOWN; - cb_data->addr_family = addr_family; - if (iface) - cb_data->ifspec = g_strdup_printf("if!%s", iface); - -#if WITH_CONCHECK - - cb_data->concheck.con_config = _con_config_ref(priv->con_config); - - if (iface && ifindex > 0 && priv->enabled && priv->uri_valid) { - gboolean has_systemd_resolved; - NMConnectivityState state; - const char * reason; - - cb_data->concheck.ch_ifindex = ifindex; - - if (platform) { - state = check_platform_config(self, platform, ifindex, addr_family, &reason); - nm_assert((state == NM_CONNECTIVITY_UNKNOWN) == !reason); - if (state != NM_CONNECTIVITY_UNKNOWN) { - _LOG2D("skip connectivity check due to %s", reason); - cb_data->completed_state = state; - cb_data->completed_reason = reason; - cb_data->timeout_id = g_idle_add(_idle_cb, cb_data); - return cb_data; - } - } - - /* 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. - * - * Yes, this makes NMConnectivity singleton dependent on NMDnsManager singleton. - * Well, not really: it makes connectivity-check-start dependent on NMDnsManager - * which merely means, not to start a connectivity check, late during shutdown. - * - * NMDnsSystemdResolved tries to D-Bus activate systemd-resolved only once, - * to not spam syslog with failures messages from dbus-daemon. - * Note that unless NMDnsSystemdResolved tried and failed to start systemd-resolved, - * it guesses that systemd-resolved is activatable and returns %TRUE here. That - * means, while NMDnsSystemdResolved would not try to D-Bus activate systemd-resolved - * more than once, NMConnectivity might -- until NMDnsSystemdResolved tried itself - * and noticed that systemd-resolved is not available. - * This is relatively cumbersome to avoid, because we would have to go through - * NMDnsSystemdResolved trying to asynchronously start the service, to ensure there - * is only one attempt to start the service. */ - has_systemd_resolved = nm_dns_manager_has_systemd_resolved(nm_dns_manager_get()); - - if (has_systemd_resolved) { - GDBusConnection *dbus_connection; - - dbus_connection = NM_MAIN_DBUS_CONNECTION_GET; - if (!dbus_connection) { - /* we have no D-Bus connection? That might happen in configure and quit mode. - * - * Anyway, something is very odd, just fail connectivity check. */ - _LOG2D("start fake request (fail due to no D-Bus connection)"); - cb_data->completed_state = NM_CONNECTIVITY_ERROR; - cb_data->completed_reason = "no D-Bus connection"; - cb_data->timeout_id = g_idle_add(_idle_cb, cb_data); - return cb_data; - } - - cb_data->concheck.resolve_cancellable = g_cancellable_new(); - - g_dbus_connection_call(dbus_connection, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname", - g_variant_new("(isit)", - (gint32) cb_data->concheck.ch_ifindex, - cb_data->concheck.con_config->host, - (gint32) cb_data->addr_family, - SD_RESOLVED_DNS), - G_VARIANT_TYPE("(a(iiay)st)"), - G_DBUS_CALL_FLAGS_NONE, - -1, - cb_data->concheck.resolve_cancellable, - 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; - } -#endif - - if (!cb_data->ifspec) { - cb_data->completed_state = NM_CONNECTIVITY_ERROR; - cb_data->completed_reason = "missing interface"; - } else { - cb_data->completed_state = NM_CONNECTIVITY_FAKE; - cb_data->completed_reason = "fake result"; - } - _LOG2D("start fake request (%s)", cb_data->completed_reason); - cb_data->timeout_id = g_idle_add(_idle_cb, cb_data); - - return cb_data; -} - -void -nm_connectivity_check_cancel(NMConnectivityCheckHandle *cb_data) -{ - g_return_if_fail(cb_data); - g_return_if_fail(NM_IS_CONNECTIVITY(cb_data->self)); - - nm_assert( - c_list_contains(&NM_CONNECTIVITY_GET_PRIVATE(cb_data->self)->handles_lst_head, - &cb_data->handles_lst) - || c_list_contains(&NM_CONNECTIVITY_GET_PRIVATE(cb_data->self)->completed_handles_lst_head, - &cb_data->handles_lst)); - - cb_data_complete(cb_data, NM_CONNECTIVITY_CANCELLED, "cancelled"); -} - -/*****************************************************************************/ - -gboolean -nm_connectivity_check_enabled(NMConnectivity *self) -{ - g_return_val_if_fail(NM_IS_CONNECTIVITY(self), FALSE); - - return NM_CONNECTIVITY_GET_PRIVATE(self)->enabled; -} - -/*****************************************************************************/ - -guint -nm_connectivity_get_interval(NMConnectivity *self) -{ - return nm_connectivity_check_enabled(self) ? NM_CONNECTIVITY_GET_PRIVATE(self)->interval : 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 = g_strndup(host_begin, host_len); - - /* port */ - if (*p++ == ':') { - port_begin = p; - while (*p && *p != '/') { - port_len++; - p++; - } - if (port_len) - *port = g_strndup(port_begin, port_len); - } - - return TRUE; -} - -static void -update_config(NMConnectivity *self, NMConfigData *config_data) -{ - NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE(self); - guint interval; - gboolean enabled; - gboolean changed = FALSE; - const char * cur_uri = priv->con_config ? priv->con_config->uri : NULL; - const char * cur_response = priv->con_config ? priv->con_config->response : NULL; - const char * new_response; - const char * new_uri; - gboolean new_uri_valid = priv->uri_valid; - gboolean new_host_port = FALSE; - gs_free char * new_host = NULL; - gs_free char * new_port = NULL; - - new_uri = nm_config_data_get_connectivity_uri(config_data); - if (!nm_streq0(new_uri, cur_uri)) { - new_uri_valid = (new_uri && *new_uri); - if (new_uri_valid) { - gs_free char *scheme = g_uri_parse_scheme(new_uri); - gboolean is_https = FALSE; - - if (!scheme) { - _LOGE("invalid URI '%s' for connectivity check.", new_uri); - new_uri_valid = FALSE; - } else if (g_ascii_strcasecmp(scheme, "https") == 0) { - _LOGW("use of HTTPS for connectivity checking is not reliable and is discouraged " - "(URI: %s)", - new_uri); - is_https = TRUE; - } else if (g_ascii_strcasecmp(scheme, "http") != 0) { - _LOGE("scheme of '%s' uri doesn't use a scheme that is allowed for connectivity " - "check.", - new_uri); - new_uri_valid = FALSE; - } - if (new_uri_valid) { - new_host_port = TRUE; - if (!host_and_port_from_uri(new_uri, &new_host, &new_port)) { - _LOGE("cannot parse host and port from '%s'", new_uri); - new_uri_valid = FALSE; - } else if (!new_port && is_https) - new_port = g_strdup("443"); - } - } - - if (new_uri_valid || priv->uri_valid != new_uri_valid) - changed = TRUE; - } - - new_response = nm_config_data_get_connectivity_response(config_data); - if (!nm_streq0(new_response, cur_response)) - changed = TRUE; - - if (!priv->con_config || !nm_streq0(new_uri, priv->con_config->uri) - || !nm_streq0(new_response, priv->con_config->response)) { - if (!new_host_port) { - new_host = priv->con_config ? g_strdup(priv->con_config->host) : NULL; - new_port = priv->con_config ? g_strdup(priv->con_config->port) : NULL; - } - _con_config_unref(priv->con_config); - priv->con_config = g_slice_new(ConConfig); - *priv->con_config = (ConConfig){ - .ref_count = 1, - .uri = g_strdup(new_uri), - .response = g_strdup(new_response), - .host = g_steal_pointer(&new_host), - .port = g_steal_pointer(&new_port), - }; - } - priv->uri_valid = new_uri_valid; - - interval = nm_config_data_get_connectivity_interval(config_data); - interval = MIN(interval, (7 * 24 * 3600)); - if (priv->interval != interval) { - priv->interval = interval; - changed = TRUE; - } - - enabled = FALSE; -#if WITH_CONCHECK - if (priv->uri_valid && priv->interval) - enabled = nm_config_data_get_connectivity_enabled(config_data); -#endif - - if (priv->enabled != enabled) { - priv->enabled = enabled; - changed = TRUE; - } - - if (changed) - g_signal_emit(self, signals[CONFIG_CHANGED], 0); -} - -static void -config_changed_cb(NMConfig * config, - NMConfigData * config_data, - NMConfigChangeFlags changes, - NMConfigData * old_data, - NMConnectivity * self) -{ - update_config(self, config_data); -} - -/*****************************************************************************/ - -static void -nm_connectivity_init(NMConnectivity *self) -{ - NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE(self); -#if WITH_CONCHECK - CURLcode ret; -#endif - - c_list_init(&priv->handles_lst_head); - c_list_init(&priv->completed_handles_lst_head); - - priv->config = g_object_ref(nm_config_get()); - g_signal_connect(G_OBJECT(priv->config), - NM_CONFIG_SIGNAL_CONFIG_CHANGED, - G_CALLBACK(config_changed_cb), - self); - -#if WITH_CONCHECK - ret = curl_global_init(CURL_GLOBAL_ALL); - if (ret != CURLE_OK) { - _LOGE("unable to init cURL, connectivity check will not work: (%d) %s", - ret, - curl_easy_strerror(ret)); - } -#endif - - update_config(self, nm_config_get_data(priv->config)); -} - -static void -dispose(GObject *object) -{ - NMConnectivity * self = NM_CONNECTIVITY(object); - NMConnectivityPrivate * priv = NM_CONNECTIVITY_GET_PRIVATE(self); - NMConnectivityCheckHandle *cb_data; - - nm_assert(c_list_is_empty(&priv->completed_handles_lst_head)); - - while ( - (cb_data = - c_list_first_entry(&priv->handles_lst_head, NMConnectivityCheckHandle, handles_lst))) - cb_data_complete(cb_data, NM_CONNECTIVITY_DISPOSING, "shutting down"); - - nm_clear_pointer(&priv->con_config, _con_config_unref); - -#if WITH_CONCHECK - curl_global_cleanup(); -#endif - - if (priv->config) { - g_signal_handlers_disconnect_by_func(priv->config, config_changed_cb, self); - g_clear_object(&priv->config); - } - - G_OBJECT_CLASS(nm_connectivity_parent_class)->dispose(object); -} - -static void -nm_connectivity_class_init(NMConnectivityClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS(klass); - - signals[CONFIG_CHANGED] = g_signal_new(NM_CONNECTIVITY_CONFIG_CHANGED, - G_OBJECT_CLASS_TYPE(object_class), - G_SIGNAL_RUN_FIRST, - 0, - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0); - - object_class->dispose = dispose; -} |