diff options
-rw-r--r-- | src/devices/nm-device.c | 210 | ||||
-rw-r--r-- | src/nm-ip4-config.c | 18 | ||||
-rw-r--r-- | src/nm-ip4-config.h | 2 |
3 files changed, 226 insertions, 4 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index dab951c7bf..5ffb8ea5f0 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -168,6 +168,13 @@ typedef struct { } SlaveInfo; typedef struct { + guint log_domain; + guint timeout; + guint watch; + GPid pid; +} PingInfo; + +typedef struct { gboolean disposed; gboolean initialized; gboolean in_state_changed; @@ -228,6 +235,8 @@ typedef struct { gulong dhcp4_timeout_sigid; NMDHCP4Config * dhcp4_config; + PingInfo gw_ping; + /* dnsmasq stuff for shared connections */ NMDnsMasqManager *dnsmasq_manager; gulong dnsmasq_state_id; @@ -309,6 +318,8 @@ static void dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release); static const char *reason_to_string (NMDeviceStateReason reason); +static void ip_check_gw_ping_cleanup (NMDevice *self); + static void cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data); static void cp_connections_loaded (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data); static void cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data); @@ -3713,10 +3724,10 @@ nm_device_activate_ip4_config_commit (gpointer user_data) } } - /* Enter the SECONDARIES state if this is the first method to complete */ + /* Enter the IP_CHECK state if this is the first method to complete */ priv->ip4_state = IP_DONE; if (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG) - nm_device_state_changed (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE); + nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE); out: nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 5 of 5 (IPv4 Commit) complete.", @@ -3806,10 +3817,10 @@ nm_device_activate_ip6_config_commit (gpointer user_data) NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit (self, config); if (ip6_config_merge_and_apply (self, config, &reason)) { - /* Enter the SECONDARIES state if this is the first method to complete */ + /* Enter the IP_CHECK state if this is the first method to complete */ priv->ip6_state = IP_DONE; if (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG) - nm_device_state_changed (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE); + nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE); } else { nm_log_info (LOGD_DEVICE | LOGD_IP6, "Activation (%s) Stage 5 of 5 (IPv6 Commit) failed", @@ -4051,6 +4062,8 @@ nm_device_deactivate (NMDevice *self, NMDeviceStateReason reason) nm_setting_connection_get_zone (s_con)); } + ip_check_gw_ping_cleanup (self); + /* Break the activation chain */ activation_source_clear (self, TRUE, AF_INET); activation_source_clear (self, TRUE, AF_INET6); @@ -4426,6 +4439,189 @@ nm_device_get_ip6_config (NMDevice *self) return NM_DEVICE_GET_PRIVATE (self)->ip6_config; } +/****************************************************************/ + +static void +ip_check_gw_ping_cleanup (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->gw_ping.watch) { + g_source_remove (priv->gw_ping.watch); + priv->gw_ping.watch = 0; + } + if (priv->gw_ping.timeout) { + g_source_remove (priv->gw_ping.timeout); + priv->gw_ping.timeout = 0; + } + + if (priv->gw_ping.pid) { + guint count = 20; + int status; + + kill (priv->gw_ping.pid, SIGKILL); + do { + if (waitpid (priv->gw_ping.pid, &status, WNOHANG) != 0) + break; + g_usleep (G_USEC_PER_SEC / 20); + } while (count--); + + priv->gw_ping.pid = 0; + } +} + +static void +ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *iface; + guint log_domain = priv->gw_ping.log_domain; + + if (!priv->gw_ping.watch) + return; + priv->gw_ping.watch = 0; + priv->gw_ping.pid = 0; + + iface = nm_device_get_iface (self); + + if (WIFEXITED (status)) { + if (WEXITSTATUS (status) == 0) + nm_log_dbg (log_domain, "(%s): gateway ping succeeded", iface); + else { + nm_log_warn (log_domain, "(%s): gateway ping failed with error code %d", + iface, WEXITSTATUS (status)); + } + } else + nm_log_warn (log_domain, "(%s): ping stopped unexpectedly with status %d", iface, status); + + /* We've got connectivity, proceed to secondaries */ + ip_check_gw_ping_cleanup (self); + nm_device_state_changed (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE); +} + +static gboolean +ip_check_ping_timeout_cb (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->gw_ping.timeout = 0; + + nm_log_warn (priv->gw_ping.log_domain, "(%s): gateway ping timed out", + nm_device_get_iface (self)); + + ip_check_gw_ping_cleanup (self); + nm_device_state_changed (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE); + return FALSE; +} + +static gboolean +spawn_ping (NMDevice *self, + guint log_domain, + const char *binary, + const char *address, + guint timeout) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *args[] = { binary, "-I", nm_device_get_ip_iface (self), "-c", "1", "-w", NULL, address, NULL }; + GError *error = NULL; + char *str_timeout, *cmd; + gboolean success; + + g_return_val_if_fail (priv->gw_ping.watch == 0, FALSE); + g_return_val_if_fail (priv->gw_ping.timeout == 0, FALSE); + + args[6] = str_timeout = g_strdup_printf ("%u", timeout); + + if (nm_logging_level_enabled (LOGL_DEBUG)) { + cmd = g_strjoinv (" ", (gchar **) args); + nm_log_dbg (log_domain, "(%s): running '%s'", + nm_device_get_iface (self), + cmd); + g_free (cmd); + } + + success = g_spawn_async ("/", + (gchar **) args, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + nm_unblock_posix_signals, + NULL, + &priv->gw_ping.pid, + &error); + if (success) { + priv->gw_ping.log_domain = log_domain; + priv->gw_ping.watch = g_child_watch_add (priv->gw_ping.pid, ip_check_ping_watch_cb, self); + priv->gw_ping.timeout = g_timeout_add_seconds (timeout + 1, ip_check_ping_timeout_cb, self); + } else { + nm_log_warn (log_domain, "could not spawn %s: %s", binary, error->message); + g_clear_error (&error); + } + + g_free (str_timeout); + return success; +} + +static void +nm_device_start_ip_check (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMConnection *connection; + NMSettingConnection *s_con; + guint timeout = 0; + const char *ping_binary = NULL; + char buf[INET6_ADDRSTRLEN] = { 0 }; + guint log_domain = LOGD_IP4; + + /* Shouldn't be any active ping here, since IP_CHECK happens after the + * first IP method completes. Any subsequently completing IP method doesn't + * get checked. + */ + g_assert (!priv->gw_ping.watch); + g_assert (!priv->gw_ping.timeout); + g_assert (!priv->gw_ping.pid); + g_assert (priv->ip4_state == IP_DONE || priv->ip6_state == IP_DONE); + + connection = nm_device_get_connection (self); + g_assert (connection); + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + timeout = nm_setting_connection_get_gateway_ping_timeout (s_con); + + if (timeout) { + if (priv->ip4_config && priv->ip4_state == IP_DONE) { + guint gw = 0; + + ping_binary = "/usr/bin/ping"; + log_domain = LOGD_IP4; + + gw = nm_ip4_config_get_gateway (priv->ip4_config); + if (gw && !inet_ntop (AF_INET, &gw, buf, sizeof (buf))) + buf[0] = '\0'; + } else if (priv->ip6_config && priv->ip6_state == IP_DONE) { + const struct in6_addr *gw = NULL; + + ping_binary = "/usr/bin/ping6"; + log_domain = LOGD_IP6; + + gw = nm_ip6_config_get_gateway (priv->ip6_config); + if (gw && !inet_ntop (AF_INET6, gw, buf, sizeof (buf))) + buf[0] = '\0'; + } + } + + if (buf[0]) + spawn_ping (self, log_domain, ping_binary, buf, timeout); + + /* If no ping was started, just advance to SECONDARIES */ + if (!priv->gw_ping.pid) + nm_device_state_changed (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE); +} + +/****************************************************************/ + gboolean nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware) { @@ -4549,6 +4745,8 @@ dispose (GObject *object) } } + ip_check_gw_ping_cleanup (self); + /* Clear any queued transitions */ nm_device_queued_state_clear (self); nm_device_queued_ip_config_change_clear (self); @@ -5526,7 +5724,11 @@ nm_device_state_changed (NMDevice *device, */ nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); break; + case NM_DEVICE_STATE_IP_CHECK: + nm_device_start_ip_check (device); + break; case NM_DEVICE_STATE_SECONDARIES: + ip_check_gw_ping_cleanup (device); nm_log_dbg (LOGD_DEVICE, "(%s): device entered SECONDARIES state", nm_device_get_iface (device)); break; diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c index 0044d913de..d10a7adcf8 100644 --- a/src/nm-ip4-config.c +++ b/src/nm-ip4-config.c @@ -233,6 +233,24 @@ guint32 nm_ip4_config_get_num_addresses (NMIP4Config *config) return g_slist_length (NM_IP4_CONFIG_GET_PRIVATE (config)->addresses); } +/* Return the first gateway we find */ +guint32 +nm_ip4_config_get_gateway (NMIP4Config *config) +{ + GSList *iter; + guint32 gw; + + for (iter = NM_IP4_CONFIG_GET_PRIVATE (config)->addresses; iter; iter = iter->next) { + NMIP4Address *addr = iter->data; + + gw = nm_ip4_address_get_gateway (addr); + if (gw) + return gw; + } + + return 0; +} + guint32 nm_ip4_config_get_ptp_address (NMIP4Config *config) { g_return_val_if_fail (NM_IS_IP4_CONFIG (config), 0); diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h index 309f9edd36..1e1f95d078 100644 --- a/src/nm-ip4-config.h +++ b/src/nm-ip4-config.h @@ -62,6 +62,8 @@ void nm_ip4_config_replace_address (NMIP4Config *config, guint32 i, NMIP4Address *nm_ip4_config_get_address (NMIP4Config *config, guint32 i); guint32 nm_ip4_config_get_num_addresses (NMIP4Config *config); +guint32 nm_ip4_config_get_gateway (NMIP4Config *config); + guint32 nm_ip4_config_get_ptp_address (NMIP4Config *config); void nm_ip4_config_set_ptp_address (NMIP4Config *config, guint32 ptp_addr); |