diff options
Diffstat (limited to 'src/devices/nm-device.c')
-rw-r--r-- | src/devices/nm-device.c | 344 |
1 files changed, 293 insertions, 51 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 6142908a57..cc3df54ae8 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -164,6 +164,7 @@ struct _NMDeviceConnectivityHandle { gpointer user_data; NMConnectivityCheckHandle *c_handle; guint64 seq; + bool is_periodic:1; }; /*****************************************************************************/ @@ -543,7 +544,19 @@ typedef struct _NMDevicePrivate { NMConnectivity *concheck_mgr; - gulong concheck_periodic_id; + /* if periodic checks are enabled, this is the source id for the next check. */ + guint concheck_p_cur_id; + + /* the currently configured max periodic interval. */ + guint concheck_p_max_interval; + + /* the current interval. If we are probing, the interval might be lower + * then the configured max interval. */ + guint concheck_p_cur_interval; + + /* the timestamp, when we last scheduled the timer concheck_p_cur_id with current interval + * concheck_p_cur_interval. */ + gint64 concheck_p_cur_basetime_ns; NMConnectivityState connectivity_state; @@ -2185,17 +2198,225 @@ nm_device_get_physical_port_id (NMDevice *self) /*****************************************************************************/ +typedef enum { + CONCHECK_SCHEDULE_UPDATE_INTERVAL, + CONCHECK_SCHEDULE_CHECK_EXTERNAL, + CONCHECK_SCHEDULE_CHECK_PERIODIC, + CONCHECK_SCHEDULE_RETURNED_MIN, + CONCHECK_SCHEDULE_RETURNED_BUMP, + CONCHECK_SCHEDULE_RETURNED_MAX, +} ConcheckScheduleMode; + +static NMDeviceConnectivityHandle *concheck_start (NMDevice *self, + NMDeviceConnectivityCallback callback, + gpointer user_data, + gboolean is_periodic); + +static void concheck_periodic_schedule_set (NMDevice *self, + ConcheckScheduleMode mode); + +static gboolean +concheck_periodic_timeout_cb (gpointer user_data) +{ + NMDevice *self = user_data; + + concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_CHECK_PERIODIC); + concheck_start (self, NULL, NULL, TRUE); + return G_SOURCE_CONTINUE; +} + +static gboolean +concheck_is_possible (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if ( !nm_device_is_real (self) + || NM_FLAGS_HAS (priv->unmanaged_flags, NM_UNMANAGED_LOOPBACK)) + return FALSE; + + /* we enable periodic checks for every device state (except UNKNOWN). Especially with + * unmanaged devices, it is interesting to know whether we have connectivity on that device. */ + if (priv->state == NM_DEVICE_STATE_UNKNOWN) + return FALSE; + + return TRUE; +} + +static gboolean +concheck_periodic_schedule_do (NMDevice *self, gint64 interval_ns) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + gboolean periodic_check_disabled = FALSE; + + /* we always cancel whatever was pending. */ + if (nm_clear_g_source (&priv->concheck_p_cur_id)) + periodic_check_disabled = TRUE; + + if (priv->concheck_p_max_interval == 0) { + /* periodic checks are disabled */ + goto out; + } + + nm_assert (interval_ns >= 0); + + if (!concheck_is_possible (self)) + goto out; + + _LOGT (LOGD_CONCHECK, "connectivity: periodic-check: %sscheduled in %u milliseconds (%u seconds interval)", + periodic_check_disabled ? "re-" : "", + (guint) (interval_ns / NM_UTILS_NS_PER_MSEC), + priv->concheck_p_cur_interval); + + nm_assert (priv->concheck_p_cur_interval > 0); + priv->concheck_p_cur_id = g_timeout_add (interval_ns / NM_UTILS_NS_PER_MSEC, + concheck_periodic_timeout_cb, + self); + return TRUE; +out: + if (periodic_check_disabled) + _LOGT (LOGD_CONCHECK, "connectivity: periodic-check: unscheduled"); + return FALSE; +} + +#define CONCHECK_P_PROBE_INTERVAL 1 + static void -concheck_update_state (NMDevice *self, NMConnectivityState state) +concheck_periodic_schedule_set (NMDevice *self, + ConcheckScheduleMode mode) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + gint64 new_expiry, cur_expiry, tdiff; + gint64 now_ns = 0; - if (state == NM_CONNECTIVITY_ERROR) + if (priv->concheck_p_max_interval == 0) { + /* periodic check is disabled. Nothing to do. */ return; + } - /* If the connectivity check is disabled and we obtain a fake - * result, make an optimistic guess. */ - if (state == NM_CONNECTIVITY_FAKE) { + if (!priv->concheck_p_cur_id) { + /* we currently don't have a timeout scheduled. No need to reschedule + * another one... */ + if (mode == CONCHECK_SCHEDULE_UPDATE_INTERVAL) { + /* ... unless, we are initalizing. In this case, setup the current current + * interval and schedule a perform a check right away. */ + priv->concheck_p_cur_interval = NM_MIN (priv->concheck_p_max_interval, CONCHECK_P_PROBE_INTERVAL); + priv->concheck_p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns); + if (concheck_periodic_schedule_do (self, priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND)) + concheck_start (self, NULL, NULL, TRUE); + } + return; + } + + switch (mode) { + case CONCHECK_SCHEDULE_UPDATE_INTERVAL: + /* called with "UPDATE_INTERVAL" and already have a concheck_p_cur_id scheduled. */ + + if (priv->concheck_p_cur_interval <= priv->concheck_p_max_interval) { + /* we currently have a shorter interval set, then what we now have. Either, + * because we are probing, or because the previous max interval was shorter. + * + * Either way, the current timer is set just fine. Nothing to do. */ + return; + } + + cur_expiry = priv->concheck_p_cur_basetime_ns + (priv->concheck_p_max_interval * NM_UTILS_NS_PER_SECOND); + priv->concheck_p_cur_interval = priv->concheck_p_max_interval; + priv->concheck_p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns); + if (cur_expiry <= now_ns) { + /* the last timer was scheduled longer ago then the new desired interval. It means, + * we must schedule a timer right away */ + if (concheck_periodic_schedule_do (self, priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND)) { + concheck_start (self, NULL, NULL, TRUE); + } + } else { + /* we only need to reset the timer. */ + concheck_periodic_schedule_do (self, (cur_expiry - now_ns) / NM_UTILS_NS_PER_MSEC); + } + return; + + case CONCHECK_SCHEDULE_CHECK_EXTERNAL: + /* a external connectivity check delays our periodic check. We reset the counter. */ + priv->concheck_p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns); + concheck_periodic_schedule_do (self, priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND); + return; + + case CONCHECK_SCHEDULE_CHECK_PERIODIC: + /* we schedule a periodic connectivity check now. We just remember the time when + * we did it. There is nothing to reschedule, it's fine already. */ + priv->concheck_p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns); + return; + + /* we just got an event that we lost connectivity (that is, concheck returned). We reset + * the interval to min/max or increase the probe interval (bump). */ + case CONCHECK_SCHEDULE_RETURNED_MIN: + priv->concheck_p_cur_interval = NM_MIN (priv->concheck_p_max_interval, CONCHECK_P_PROBE_INTERVAL); + break; + case CONCHECK_SCHEDULE_RETURNED_MAX: + priv->concheck_p_cur_interval = priv->concheck_p_max_interval; + break; + case CONCHECK_SCHEDULE_RETURNED_BUMP: + priv->concheck_p_cur_interval = NM_MIN (priv->concheck_p_cur_interval * 2, priv->concheck_p_max_interval); + break; + } + + /* we are here, because we returned from a connectivity check and adjust the current interval. + * + * But note that we calculate the new timeout based on the time when we scheduled the + * last check, instead of counting from now. The reaons is, that we want that the times + * when we schedule checks be at precise intervals, without including the time it took for + * the connectivity check. */ + new_expiry = priv->concheck_p_cur_basetime_ns + (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND); + tdiff = NM_MAX (new_expiry - nm_utils_get_monotonic_timestamp_ns_cached (&now_ns), 0); + priv->concheck_p_cur_basetime_ns = now_ns - tdiff; + concheck_periodic_schedule_do (self, tdiff); +} + +void +nm_device_check_connectivity_update_interval (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + guint new_interval; + + new_interval = nm_connectivity_get_interval (concheck_get_mgr (self)); + + new_interval = NM_MIN (new_interval, 7 *24 * 3600); + + if (new_interval != priv->concheck_p_max_interval) { + _LOGT (LOGD_CONCHECK, "connectivity: periodic-check: set interval to %u seconds", new_interval); + priv->concheck_p_max_interval = new_interval; + } + + if (!new_interval) { + /* this will cancel any potentially pending timeout. */ + concheck_periodic_schedule_do (self, 0); + return; + } + + concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_UPDATE_INTERVAL); +} + +static void +concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean is_periodic) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + /* @state is a result of the connectivity check. We only expect a precise + * number of possible values. */ + nm_assert (NM_IN_SET (state, NM_CONNECTIVITY_LIMITED, + NM_CONNECTIVITY_PORTAL, + NM_CONNECTIVITY_FULL, + NM_CONNECTIVITY_FAKE, + NM_CONNECTIVITY_ERROR)); + + if (state == NM_CONNECTIVITY_ERROR) { + /* on error, we don't change the current connectivity state, + * except making UNKNOWN to NONE. */ + state = priv->connectivity_state; + if (state == NM_CONNECTIVITY_UNKNOWN) + state = NM_CONNECTIVITY_NONE; + } else if (state == NM_CONNECTIVITY_FAKE) { + /* If the connectivity check is disabled and we obtain a fake + * result, make an optimistic guess. */ if (priv->state == NM_DEVICE_STATE_ACTIVATED) { if (nm_device_get_best_default_route (self, AF_UNSPEC)) state = NM_CONNECTIVITY_FULL; @@ -2205,10 +2426,32 @@ concheck_update_state (NMDevice *self, NMConnectivityState state) state = NM_CONNECTIVITY_NONE; } - if (priv->connectivity_state == state) + if (priv->connectivity_state == state) { + /* we got a connectivty update, but the state didn't change. If we were probing, + * we bump the probe frequency. */ + if ( is_periodic + && priv->concheck_p_cur_id) + concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_BUMP); return; + } + /* we need to update the probe interval before emitting signals. Emitting + * a signal might call back into NMDevice and change the probe settings. + * So, do that first. */ + if (state == NM_CONNECTIVITY_FULL) { + /* we reached full connectivity state. Stop probing by setting the + * interval to the max. */ + concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_MAX); + } else if (priv->connectivity_state == NM_CONNECTIVITY_FULL) { + /* we are about to loose connectivity. (re)start probing by setting + * the timeout interval to the min. */ + concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_MIN); + } else { + if ( is_periodic + && priv->concheck_p_cur_id) + concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_BUMP); + } - _LOGD (LOGD_CONCHECK, "state changed from %s to %s", + _LOGD (LOGD_CONCHECK, "connectivity state changed from %s to %s", nm_connectivity_state_to_string (priv->connectivity_state), nm_connectivity_state_to_string (state)); priv->connectivity_state = state; @@ -2227,34 +2470,6 @@ concheck_update_state (NMDevice *self, NMConnectivityState state) } static void -concheck_periodic (NMConnectivity *connectivity, NMDevice *self) -{ - nm_device_check_connectivity (self, NULL, NULL); -} - -static void -concheck_periodic_update (NMDevice *self) -{ - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - - if ( priv->state == NM_DEVICE_STATE_ACTIVATED - && nm_device_get_best_default_route (self, AF_UNSPEC)) { - if (!priv->concheck_periodic_id) { - priv->concheck_periodic_id = g_signal_connect (concheck_get_mgr (self), - NM_CONNECTIVITY_PERIODIC_CHECK, - G_CALLBACK (concheck_periodic), self); - nm_device_check_connectivity (self, NULL, NULL); - } - } else { - if (priv->concheck_periodic_id) { - /* The default route has gone off, trigger a final connectivity check. */ - nm_clear_g_signal_handler (priv->concheck_mgr, &priv->concheck_periodic_id); - nm_device_check_connectivity (self, NULL, NULL); - } - } -} - -static void concheck_handle_complete (NMDeviceConnectivityHandle *handle, GError *error) { @@ -2264,7 +2479,7 @@ concheck_handle_complete (NMDeviceConnectivityHandle *handle, c_list_unlink (&handle->concheck_lst); if (handle->c_handle) - nm_connectivity_check_cancel (g_steal_pointer (&handle->c_handle)); + nm_connectivity_check_cancel (handle->c_handle); if (handle->callback) { handle->callback (handle->self, @@ -2291,6 +2506,18 @@ concheck_cb (NMConnectivity *connectivity, gboolean handle_is_alive; guint64 seq; + handle = user_data; + nm_assert (handle->c_handle == c_handle); + nm_assert (NM_IS_DEVICE (handle->self)); + + handle->c_handle = NULL; + self = g_object_ref (handle->self); + + _LOGT (LOGD_CONCHECK, "connectivity: complete check (seq:%llu, state:%s%s%s%s)", + (long long unsigned) handle->seq, + nm_connectivity_state_to_string (state), + NM_PRINT_FMT_QUOTED (error, ", error: ", error->message, "", "")); + if (nm_utils_error_is_cancelled (error, FALSE)) { /* the only place where we nm_connectivity_check_cancel(@c_handle), is * from inside concheck_handle_event(). This is a recursive call, @@ -2301,12 +2528,7 @@ concheck_cb (NMConnectivity *connectivity, /* we keep NMConnectivity instance alive. It cannot be disposing. */ nm_assert (!nm_utils_error_is_cancelled (error, TRUE)); - handle = user_data; - nm_assert (handle->c_handle == c_handle); - handle->c_handle = NULL; - /* keep @self alive, while we invoke callbacks. */ - self = g_object_ref (handle->self); priv = NM_DEVICE_GET_PRIVATE (self); nm_assert (!handle || c_list_contains (&priv->concheck_lst_head, &handle->concheck_lst)); @@ -2314,7 +2536,7 @@ concheck_cb (NMConnectivity *connectivity, seq = handle->seq; /* first update the new state, and emit signals. */ - concheck_update_state (self, state); + concheck_update_state (self, state, handle->is_periodic); handle_is_alive = FALSE; @@ -2361,10 +2583,11 @@ check_handles: concheck_handle_complete (handle, NULL); } -NMDeviceConnectivityHandle * -nm_device_check_connectivity (NMDevice *self, - NMDeviceConnectivityCallback callback, - gpointer user_data) +static NMDeviceConnectivityHandle * +concheck_start (NMDevice *self, + NMDeviceConnectivityCallback callback, + gpointer user_data, + gboolean is_periodic) { static guint64 seq_counter = 0; NMDevicePrivate *priv; @@ -2379,8 +2602,14 @@ nm_device_check_connectivity (NMDevice *self, handle->self = self; handle->callback = callback; handle->user_data = user_data; + handle->is_periodic = is_periodic; + c_list_link_tail (&priv->concheck_lst_head, &handle->concheck_lst); + _LOGT (LOGD_CONCHECK, "connectivity: start check (seq:%llu%s)", + (long long unsigned) handle->seq, + is_periodic ? ", periodic-check" : ""); + handle->c_handle = nm_connectivity_check_start (concheck_get_mgr (self), nm_device_get_ip_iface (self), concheck_cb, @@ -2388,6 +2617,21 @@ nm_device_check_connectivity (NMDevice *self, return handle; } +NMDeviceConnectivityHandle * +nm_device_check_connectivity (NMDevice *self, + NMDeviceConnectivityCallback callback, + gpointer user_data) +{ + NMDeviceConnectivityHandle *handle; + + if (!concheck_is_possible (self)) + return NULL; + + concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_CHECK_EXTERNAL); + handle = concheck_start (self, callback, user_data, FALSE); + return handle; +} + void nm_device_check_connectivity_cancel (NMDeviceConnectivityHandle *handle) { @@ -10688,8 +10932,6 @@ nm_device_set_ip_config (NMDevice *self, } if (IS_IPv4) { - concheck_periodic_update (self); - if (!nm_device_sys_iface_state_is_external_or_assume (self)) ip4_rp_filter_update (self); } @@ -13544,7 +13786,7 @@ _set_state_full (NMDevice *self, if (ip_config_valid (old_state) && !ip_config_valid (state)) notify_ip_properties (self); - concheck_periodic_update (self); + nm_device_check_connectivity_update_interval (self); /* Dispose of the cached activation request */ if (req) @@ -14711,7 +14953,7 @@ dispose (GObject *object) g_clear_object (&priv->lldp_listener); } - nm_clear_g_signal_handler (priv->concheck_mgr, &priv->concheck_periodic_id); + nm_clear_g_source (&priv->concheck_p_cur_id); G_OBJECT_CLASS (nm_device_parent_class)->dispose (object); |