diff --git a/src/service.c b/src/service.c
index 2017370d7ce8..15a79c0aa5c8 100644
--- a/src/service.c
+++ b/src/service.c
@@ -46,6 +46,10 @@
 #define VPN_AUTOCONNECT_TIMEOUT_ATTEMPTS_THRESHOLD 270
 
 typedef guint (*online_check_timeout_compute_t)(unsigned int interval);
+typedef bool (*is_counter_threshold_met_predicate_t)(
+	const struct connman_service *service,
+	const char *counter_description,
+	unsigned int counter_threshold);
 
 static DBusConnection *connection = NULL;
 
@@ -219,12 +223,14 @@ static struct connman_ipconfig *create_ip6config(struct connman_service *service
 static void dns_changed(struct connman_service *service);
 static void vpn_auto_connect(void);
 static void trigger_autoconnect(struct connman_service *service);
+static void service_list_sort(void);
 static void complete_online_check(struct connman_service *service,
 					enum connman_ipconfig_type type,
 					bool success,
 					int err);
 static bool service_downgrade_online_state(struct connman_service *service);
 static bool connman_service_is_default(const struct connman_service *service);
+static int start_online_check_if_connected(struct connman_service *service);
 static void set_error(struct connman_service *service,
 					enum connman_service_error error);
 static void clear_error(struct connman_service *service);
@@ -2162,6 +2168,235 @@ done:
 	return status;
 }
 
+static bool online_check_failures_threshold_was_met(
+			const struct connman_service *service)
+{
+	return service->online_check_failures_met_threshold;
+}
+
+static void online_check_failures_threshold_was_met_set_value(
+			struct connman_service *service, bool value)
+{
+	DBG("service %p (%s) failures met threshold %u",
+		service, connman_service_get_identifier(service),
+		value);
+
+	service->online_check_failures_met_threshold = value;
+}
+
+static void online_check_failures_threshold_was_met_set(
+			struct connman_service *service)
+{
+	online_check_failures_threshold_was_met_set_value(service, true);
+}
+
+static void online_check_failures_threshold_was_met_clear(
+			struct connman_service *service)
+{
+	online_check_failures_threshold_was_met_set_value(service, false);
+}
+
+static inline void online_check_counter_reset(
+			unsigned int *counter)
+{
+	if (!counter)
+		return;
+
+	*counter = 0;
+}
+
+static void online_check_failures_reset(struct connman_service *service)
+{
+	DBG("service %p (%s)",
+		service, connman_service_get_identifier(service));
+
+	online_check_counter_reset(&service->online_check_state_ipv4.failures);
+	online_check_counter_reset(&service->online_check_state_ipv6.failures);
+}
+
+static void online_check_successes_reset(struct connman_service *service)
+{
+	DBG("service %p (%s)",
+		service, connman_service_get_identifier(service));
+
+	online_check_counter_reset(&service->online_check_state_ipv4.successes);
+	online_check_counter_reset(&service->online_check_state_ipv6.successes);
+}
+
+static void online_check_state_reset(struct connman_service *service)
+{
+	online_check_failures_reset(service);
+
+	online_check_successes_reset(service);
+
+	online_check_failures_threshold_was_met_clear(service);
+
+	clear_error(service);
+}
+
+static void online_check_counters_log(
+			const struct connman_service *service,
+			const char *counter_description,
+			unsigned int ipv4_counter,
+			unsigned int ipv6_counter)
+{
+	DBG("service %p (%s) "
+		"ipv4 state %d (%s) %s(s/es) %u "
+		"ipv6 state %d (%s) %s(s/es) %u ",
+		service, connman_service_get_identifier(service),
+		service->state_ipv4, state2string(service->state_ipv4),
+		counter_description,
+		ipv4_counter,
+		service->state_ipv6, state2string(service->state_ipv6),
+		counter_description,
+		ipv6_counter);
+}
+
+static bool online_check_counter_threshold_is_met(
+			const struct connman_service *service,
+			const char *counter_threshold_key,
+			const char *counter_description,
+			is_counter_threshold_met_predicate_t predicate)
+{
+	unsigned int counter_threshold;
+	bool threshold_met = false;
+
+	if (!service ||
+		!counter_threshold_key ||
+		!counter_description ||
+		!predicate)
+		goto done;
+
+	counter_threshold = connman_setting_get_uint(counter_threshold_key);
+
+	threshold_met = predicate(service,
+						counter_description,
+						counter_threshold);
+
+	DBG("service %p (%s) %s threshold %u %s(s) met %u",
+		service, connman_service_get_identifier(service),
+		counter_description,
+		counter_threshold,
+		counter_description,
+		threshold_met);
+
+done:
+	return threshold_met;
+}
+
+static bool is_online_check_failure_threshold_met_predicate(
+			const struct connman_service *service,
+			const char *counter_description,
+			unsigned int counter_threshold)
+{
+	bool ipv4_is_connected;
+	bool ipv6_is_connected;
+	bool threshold_met = false;
+
+	online_check_counters_log(service,
+		counter_description,
+		service->online_check_state_ipv4.failures,
+		service->online_check_state_ipv6.failures);
+
+	ipv4_is_connected = is_connected(service->state_ipv4);
+	ipv6_is_connected = is_connected(service->state_ipv6);
+
+	/*
+	 * It is entirely possible that IPv4 reachability is fine and that
+	 * IPv6 reachablity is not due to the premises ISP, premises
+	 * Internet access equipment (that is, CPE), availability of the
+	 * reachability endpoint infrastructure, etc.
+	 *
+	 * Consequently, we want to see bilateral failures of BOTH IPv4
+	 * AND IPv6 in excess of the threshold, to the extent either is
+	 * connected (based on the #is_connected predicate).
+	 */
+	if ((!ipv6_is_connected &&
+		 ipv4_is_connected &&
+		 service->online_check_state_ipv4.failures >=
+		 counter_threshold) ||
+
+		(!ipv4_is_connected &&
+		ipv6_is_connected &&
+		service->online_check_state_ipv6.failures >=
+		counter_threshold) ||
+
+		(ipv4_is_connected &&
+		service->online_check_state_ipv4.failures >=
+		counter_threshold &&
+		ipv6_is_connected &&
+		service->online_check_state_ipv6.failures >=
+		counter_threshold)) {
+		threshold_met = true;
+	}
+
+	return threshold_met;
+}
+
+static bool online_check_failures_threshold_is_met(
+			const struct connman_service *service)
+{
+	const char * const counter_threshold_key =
+		"OnlineCheckFailuresThreshold";
+	const char * const counter_description =
+		"failure";
+
+	return online_check_counter_threshold_is_met(service,
+			counter_threshold_key,
+			counter_description,
+			is_online_check_failure_threshold_met_predicate);
+}
+
+static bool is_online_check_success_threshold_met_predicate(
+			const struct connman_service *service,
+			const char *counter_description,
+			unsigned int counter_threshold)
+{
+	bool threshold_met = false;
+
+	online_check_counters_log(service,
+		counter_description,
+		service->online_check_state_ipv4.successes,
+		service->online_check_state_ipv6.successes);
+
+	/*
+	 * It is entirely possible that IPv4 reachability is fine and that
+	 * IPv6 reachablity is not due to the premises ISP, premises
+	 * Internet access equipment (that is, CPE), availability of the
+	 * reachability endpoint infrastructure, etc.
+	 *
+	 * Consequently, we want to see bilateral successes of EITHER IPv4
+	 * OR IPv6 (as with #combine_state) in excess of the threshold, to
+	 * the extent either is connected (based on the #is_connected
+	 * predicate).
+	 */
+
+	if ((is_connected(service->state_ipv4) &&
+		service->online_check_state_ipv4.successes >=
+		counter_threshold) ||
+		(is_connected(service->state_ipv6) &&
+		service->online_check_state_ipv6.successes >=
+		counter_threshold)) {
+		threshold_met = true;
+	}
+
+	return threshold_met;
+}
+
+static bool online_check_successes_threshold_is_met(
+			const struct connman_service *service)
+{
+	const char * const counter_threshold_key =
+		"OnlineCheckSuccessesThreshold";
+	const char * const counter_description =
+		"success";
+
+	return online_check_counter_threshold_is_met(service,
+			counter_threshold_key,
+			counter_description,
+			is_online_check_success_threshold_met_predicate);
+}
+
 /**
  *  @brief
  *    Retry an "online" HTTP-based Internet reachability check.
@@ -2356,6 +2591,214 @@ static void reschedule_online_check(struct connman_service *service,
 		online_check_state->interval++;
 }
 
+static void online_check_counter_increment_and_log(
+			const struct connman_service *service,
+			enum connman_ipconfig_type type,
+			const char *counter_description,
+			unsigned int *counter)
+{
+	if (!service || !counter_description || !counter)
+		return;
+
+	(*counter)++;
+
+	DBG("service %p (%s) type %d (%s) %s %u",
+		service, connman_service_get_identifier(service),
+		type, __connman_ipconfig_type2string(type),
+		counter_description, *counter);
+}
+
+static void online_check_log_success(const struct connman_service *service,
+			enum connman_ipconfig_type type)
+{
+	g_autofree char *interface = NULL;
+
+	interface = connman_service_get_interface(service);
+
+	connman_info("Interface %s [ %s ] %s online check to %s succeeded",
+		interface,
+		__connman_service_type2string(service->type),
+		__connman_ipconfig_type2string(type),
+		type == CONNMAN_IPCONFIG_TYPE_IPV4 ?
+			connman_setting_get_string("OnlineCheckIPv4URL") :
+			connman_setting_get_string("OnlineCheckIPv6URL"));
+}
+
+static void continuous_online_check_log_counter_threshold_met(
+			const struct connman_service *service,
+			const char *counter_threshold_key,
+			const char *counter_description)
+{
+	g_autofree char *interface = NULL;
+
+	interface = connman_service_get_interface(service);
+
+	connman_warn("Interface %s [ %s ] online check had %u back-to-back "
+		"%s; %s threshold met",
+		interface,
+		__connman_service_type2string(service->type),
+		connman_setting_get_uint(counter_threshold_key),
+				 counter_description,
+				 counter_description);
+}
+
+static void continuous_online_check_log_successes_threshold_met(
+			const struct connman_service *service
+)
+{
+	static const char *const counter_threshold_key =
+		"OnlineCheckSuccessesThreshold";
+	static const char *const counter_description =
+		"success(es)";
+
+	continuous_online_check_log_counter_threshold_met(service,
+		counter_threshold_key,
+		counter_description);
+}
+
+static void continuous_online_check_log_failures_threshold_met(
+			const struct connman_service *service
+)
+{
+	static const char *const counter_threshold_key =
+		"OnlineCheckFailuresThreshold";
+	static const char *const counter_description =
+		"failure(s)";
+
+	continuous_online_check_log_counter_threshold_met(service,
+		counter_threshold_key,
+		counter_description);
+}
+
+static bool handle_oneshot_online_check_success(
+			struct connman_service *service,
+			enum connman_ipconfig_type type,
+			struct online_check_state *online_check_state)
+{
+	const bool reschedule = true;
+
+	/*
+	 * Simply log the success, mark the service IP configuration state
+	 * as ONLINE, and return.
+	 */
+	online_check_log_success(service, type);
+
+	__connman_service_ipconfig_indicate_state(service,
+		CONNMAN_SERVICE_STATE_ONLINE,
+		type);
+
+	return !reschedule;
+}
+
+static bool handle_continuous_online_check_success(
+			struct connman_service *service,
+			enum connman_ipconfig_type type,
+			struct online_check_state *online_check_state)
+{
+	bool failures_threshold_was_met;
+	bool successes_threshold_is_met;
+	const bool reschedule = true;
+
+	/* Unconditionally increment and log the success counter. */
+
+	online_check_counter_increment_and_log(service, type,
+		"successes", &online_check_state->successes);
+
+	/*
+	 * Ultimately, for failures, we are looking for a STRING of
+	 * SUSTAINED, BACK-TO-BACK failures to meet the failures
+	 * threshold. Consequently, any success should reset the
+	 * corresponding failure count back to zero (0).
+	 */
+	online_check_counter_reset(&online_check_state->failures);
+
+	failures_threshold_was_met =
+		online_check_failures_threshold_was_met(service);
+	successes_threshold_is_met =
+		online_check_successes_threshold_is_met(service);
+
+	DBG("failures threshold was met %u, "
+		"successes threshold is met %u, "
+		"default %u",
+		failures_threshold_was_met,
+		successes_threshold_is_met,
+		connman_service_is_default(service));
+
+	/*
+	 * If the service HAD previously-exceeded the failure threshold
+	 * AND if this is the first success, then reset the online check
+	 * interval to the initial, minimum value since we want to recover
+	 * as quickly as possible with a STRING of SUSTAINED, BACK-TO-BACK
+	 * successes, where the length of that string is dictated by the
+	 * "OnlineCheckSuccessesThreshold" settings value.
+	 *
+	 * Otherwise, if the service HAD NOT previously-exceeded the
+	 * failure threshold OR if it HAD previously-exceeded the failure
+	 * threshold AND the successes threshold was met, then reset the
+	 * online check interval to the maximum value.
+	 */
+	if (failures_threshold_was_met &&
+		online_check_state->successes == 1)
+		online_check_state->interval = online_check_initial_interval;
+	else if (!failures_threshold_was_met ||
+		(failures_threshold_was_met && successes_threshold_is_met))
+		online_check_state->interval = online_check_max_interval;
+
+	/*
+	 * If the service HAD NOT previously-exceeded the failure
+	 * threshold, then simply mark the service IP configuration state
+	 * as ONLINE.
+	 *
+	 * Otherwise, if the service HAD previously exceeded the failure
+	 * threshold AND successes meet or exceed the configured success
+	 * threshold, then re-sort the network services and update the
+	 * gateways accordingly.
+	 *
+	 * The succeeding service will be promoted until such time as it
+	 * has a configured number of failures, at which time, we will
+	 * resort again.
+	 *
+	 */
+	if (!failures_threshold_was_met) {
+		if (online_check_state->successes == 1)
+			online_check_log_success(service, type);
+
+		if (connman_service_is_default(service))
+			__connman_service_ipconfig_indicate_state(service,
+				CONNMAN_SERVICE_STATE_ONLINE,
+				type);
+	} else if (failures_threshold_was_met &&
+			   successes_threshold_is_met) {
+		online_check_log_success(service, type);
+
+		continuous_online_check_log_successes_threshold_met(service);
+
+		online_check_state_reset(service);
+
+		/*
+		 * The ordering here is considered and intentional. FIRST, now
+		 * that this service has cleared / reset the online check
+		 * state, re-sort the service list. This may promote this
+		 * service back to the default. SECOND, make the READY to
+		 * ONLINE promotion, since that promotion is qualified with
+		 * this service being the default (that is, has the default
+		 * route) service.
+		 */
+		service_list_sort();
+
+		if (connman_service_is_default(service)) {
+			__connman_service_ipconfig_indicate_state(
+				service,
+				CONNMAN_SERVICE_STATE_ONLINE,
+				type);
+		}
+
+		__connman_gateway_update();
+	}
+
+	return reschedule;
+}
+
 /**
  *  @brief
  *    Handle the successful completion of an "online" HTTP-based
@@ -2395,7 +2838,7 @@ static bool handle_online_check_success(struct connman_service *service,
 				struct online_check_state *online_check_state,
 				bool oneshot)
 {
-	const bool reschedule = !oneshot;
+	bool reschedule;
 
 	DBG("service %p (%s) type %d (%s) "
 		"one-shot %u\n",
@@ -2404,12 +2847,144 @@ static bool handle_online_check_success(struct connman_service *service,
 		type, __connman_ipconfig_type2string(type),
 		oneshot);
 
-	__connman_service_ipconfig_indicate_state(service,
-		CONNMAN_SERVICE_STATE_ONLINE,
-		type);
+	if (oneshot)
+		reschedule = handle_oneshot_online_check_success(service,
+						type,
+						online_check_state);
+	else
+		reschedule = handle_continuous_online_check_success(service,
+						type,
+						online_check_state);
 
-	if (!oneshot)
-		online_check_state->interval = online_check_max_interval;
+	return reschedule;
+}
+
+static void online_check_log_failure(const struct connman_service *service,
+			enum connman_ipconfig_type type,
+			int err)
+{
+	g_autofree char *interface = NULL;
+
+	interface = connman_service_get_interface(service);
+
+	connman_warn("Interface %s [ %s ] %s online check to %s failed: %d: %s",
+		interface,
+		__connman_service_type2string(service->type),
+		__connman_ipconfig_type2string(type),
+		type == CONNMAN_IPCONFIG_TYPE_IPV4 ?
+			connman_setting_get_string("OnlineCheckIPv4URL") :
+			connman_setting_get_string("OnlineCheckIPv6URL"),
+		err,
+		strerror(-err));
+}
+
+static bool handle_oneshot_online_check_failure(
+			struct connman_service *service,
+			enum connman_ipconfig_type type,
+			enum connman_service_state ipconfig_state,
+			struct online_check_state *online_check_state,
+			int err)
+{
+	const bool reschedule = true;
+
+	/* Simply indicate rescheduling another check is desired. */
+
+	DBG("online check mode is one-shot; requesting another check");
+
+	return reschedule;
+}
+
+static bool handle_continuous_online_check_failure(
+			struct connman_service *service,
+			enum connman_ipconfig_type type,
+			enum connman_service_state ipconfig_state,
+			struct online_check_state *online_check_state,
+			int err)
+{
+	bool reschedule = false;
+
+	/* Unconditionally increment and log the failure counter. */
+
+	online_check_counter_increment_and_log(service, type,
+		"failures", &online_check_state->failures);
+
+	/*
+	 * Ultimately, for successes, we are looking for a STRING of
+	 * SUSTAINED, BACK-TO-BACK successes to meet the successes
+	 * threshold. Consequently, any failure should reset the
+	 * corresponding success count back to zero (0).
+	 */
+	online_check_counter_reset(&online_check_state->successes);
+
+	/*
+	 * If this is the first failure, then reset the online check
+	 * interval to the initial, minimum value. Subsequent failures
+	 * will increment the interval on reschedule from here until the
+	 * maximum interval is hit.
+	 */
+	if (online_check_state->failures == 1)
+		online_check_state->interval = online_check_initial_interval;
+
+	DBG("failures threshold was met %u failures threshold is met %u "
+		"default %u",
+		online_check_failures_threshold_was_met(service),
+		online_check_failures_threshold_is_met(service),
+		connman_service_is_default(service));
+
+	/*
+	 * If the service HAD NOT previously-exceeded the failure
+	 * threshold AND failures meet or exceed the configured failure
+	 * threshold, then:
+	 *
+	 *	  1. Assert the failure threshold state.
+	 *	  2. Reset the success counters.
+	 *	  3. Attempt to downgrade the service IP configuration state
+	 *		 from ONLINE to READY.
+	 *	  4. Re-sort the network services.
+	 *	  5. Update the gateways accordingly.
+	 *
+	 * The failing service will be demoted until such time as it has a
+	 * configured number of successes, at which time, we will resort
+	 * again.
+	 *
+	 */
+	if (!online_check_failures_threshold_was_met(service) &&
+		online_check_failures_threshold_is_met(service)) {
+		online_check_failures_threshold_was_met_set(service);
+
+		continuous_online_check_log_failures_threshold_met(service);
+
+		online_check_successes_reset(service);
+
+		/*
+		 * Attempt to downgrade the service state from ONLINE to
+		 * READY.
+		 *
+		 * We attempt BOTH IPv4 and IPv6 IP configuration states since
+		 * the #online_check_failures_threshold_is_met predicate tells
+		 * us that both IP configurations have met the failures
+		 * threshold.
+		 */
+		service_downgrade_online_state(service);
+
+		set_error(service, CONNMAN_SERVICE_ERROR_ONLINE_CHECK_FAILED);
+
+		service_list_sort();
+
+		__connman_gateway_update();
+	}
+
+	DBG("failures threshold was met %u, default %u",
+		online_check_failures_threshold_was_met(service),
+		connman_service_is_default(service));
+
+	/*
+	 * We only want to reschedule future online checks for
+	 * the default service or those that are in failure.
+	 */
+	if (connman_service_is_default(service) ||
+		online_check_failures_threshold_was_met(service))
+		reschedule = true;
 
 	return reschedule;
 }
@@ -2473,10 +3048,10 @@ static bool handle_online_check_failure(struct connman_service *service,
 		oneshot, err, strerror(-err));
 
 	/*
-	 * If this completion closure was a failure with error status
-	 * -ECANCELED, then it was canceled by #__connman_wispr_cancel.
-	 * Simply ignore it and DO NOT reschedule; another check will be
-	 * along to replace the canceled one.
+	 * Regardless of online check mode, if this completion closure
+	 * was a failure with error status -ECANCELED, then it was canceled
+	 * by #__connman_wispr_cancel. Simply ignore it and DO NOT indicate
+	 * rescheduling another check is desired.
 	 */
 	if (err == -ECANCELED) {
 		DBG("online check was canceled; no action taken");
@@ -2484,15 +3059,26 @@ static bool handle_online_check_failure(struct connman_service *service,
 		goto done;
 	}
 
-	if (oneshot)
-		reschedule = true;
-	else {
-		if (service_downgrade_online_state(service))
-			online_check_state->interval =
-				online_check_initial_interval;
+	/* Unconditionally log the failure, regardless of online check mode. */
 
-		reschedule = connman_service_is_default(service);
-	}
+	online_check_log_failure(service, type, err);
+
+	/* Handle the failure according to the online check mode. */
+
+	if (oneshot)
+		reschedule = handle_oneshot_online_check_failure(
+						service,
+						type,
+						ipconfig_state,
+						online_check_state,
+						err);
+	else
+		reschedule = handle_continuous_online_check_failure(
+						service,
+						type,
+						ipconfig_state,
+						online_check_state,
+						err);
 
 done:
 	return reschedule;
@@ -3004,6 +3590,34 @@ static void default_changed(void)
 
 	__connman_service_timeserver_changed(current_default, NULL);
 
+	/*
+	 * If there is a current default service, then it may either have
+	 * been temporarily:
+	 *
+	 *	 1. promoted as a failover from another senior service that
+	 *		was temporarily demoted
+	 *	 2. demoted as a failover to another junior service that is
+	 *		being temporarily promoted
+	 *
+	 * due to a continuous mode online check failure.
+	 *
+	 * Regardless, only services in online check failure or the default
+	 * service should be running online checks and only the default
+	 * service should be online. Consequently, make the appropriate
+	 * calls on the current default to ensure that is the case BEFORE
+	 * assigning the proposed new default as the current default.
+	 */
+	if (current_default) {
+		if (!online_check_failures_threshold_was_met(current_default) &&
+			current_default->error !=
+				CONNMAN_SERVICE_ERROR_ONLINE_CHECK_FAILED) {
+			cancel_online_check(current_default,
+				CONNMAN_IPCONFIG_TYPE_ALL);
+
+			service_downgrade_online_state(current_default);
+		}
+	}
+
 	current_default = service;
 
 	if (service) {
@@ -6867,6 +7481,15 @@ static gint service_compare(gconstpointer a, gconstpointer b)
 
 	if (a_connected && b_connected) {
 		int rval;
+		if (!online_check_failures_threshold_was_met(service_a) &&
+			online_check_failures_threshold_was_met(service_b)) {
+			return -1;
+		}
+
+		if (online_check_failures_threshold_was_met(service_a) &&
+			!online_check_failures_threshold_was_met(service_b)) {
+			return 1;
+		}
 
 		/* Compare the VPN transport and the service */
 		if ((service_a->type == CONNMAN_SERVICE_TYPE_VPN ||
@@ -7814,6 +8437,8 @@ static int service_indicate_state(struct connman_service *service)
 
 		__connman_wispr_stop(service);
 
+		online_check_state_reset(service);
+
 		__connman_wpad_stop(service);
 
 		domain_changed(service);
