[PATCH 1/1] service: Demote IPv4 link-local services

Johannes Emerich <[email protected]> Wed, 15 Apr 2026 13:22:58 +0200
Newsgroups dev.linux.lists.connman
Message-ID <[email protected]>
In the current default configuration, ConnMan is less robust in
operating dual Ethernet than it could be, and may get stuck in what is
very obviously a dead-end, assigning the default route to a link-local
service. We observed that no default route is even installed when
ConnMan chooses an LLA service as default.

This patch teaches ConnMan to give IPv4 link-local services lower
priority than READY/ONLINE services using another ipconfig method (e.g.
DHCP). This seems like a useful heuristic to apply directly in ConnMan,
given that link-local services are never suitable for configuring the
default route.

This criterion is considered relatively late in the comparison hierarchy
so that

- online services still win over ready services,
- ready services still win over failed-online-check services,
- preferred technologies still win over less preferred (which may be
  questionable, but also in line with current treatment of preferred
  technologies).

As link-local configured services are in READY state and remain in
READY state when receiving an address via DHCP, updating the comparison
function alone is not sufficient, we also need to add a new trigger for
re-sorting the service list when the IP address changes.

The chosen trigger here is address_updated(), which is called both when
the ipconfig method is changed manually as well as when the ipconfig
changes bottom up when DHCP becomes active again after the IP device had
fallen back to link-local/AUTO.

Considerations for this choice of re-sorting trigger:

- Suitability: Triggering on address updates seems appropriate given
  that we are ultimately interested in connectedness, not the ipconfig
  method alone. We want to prefer READY DHCP services in particular, not
  just any service for which DHCP is currently used but has not yieled
  an IP.
- Frequency: relatively low, compared with trigger from
  __connman_service_update_from_network() which triggers on average
  every 3 seconds on my laptop

Why to introduce this sorting criterion in addition to online checks:

Online checks with one-shot mode do not handle this case (though we
initially thought it would). The check will fail for the LLA service and
it will stay in READY. But this does not cause the service to be demoted
in one-shot mode and so it may remain default service. The other, good
service never gets the chance to run an online check and "prove" itself
to be ONLINE. We ran practical tests to confirm that in the described
scenario the LLA service remains default service even with one-shot
online checking enabled.

We have confirmed that the continuous online check successfully
recovers from accidental use of the LLA service as default service, but
requires some compromises:

- Requires running additional public infrastructure to work behind
  firewalled networks (which may block ConnMan's probe hosts)
- Need to balance quick detection of LLA scenario with low chance of
  false positives on regular network services (tuning timing and failure
  threshold parameters)
---
 src/service.c | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/src/service.c b/src/service.c
index d5869b14..85193a9f 100644
--- a/src/service.c
+++ b/src/service.c
@@ -1161,6 +1161,18 @@ static bool is_idle(enum connman_service_state state)
 	return false;
 }
 
+static bool is_ipv4ll(const struct connman_service *service)
+{
+	enum connman_ipconfig_method method4;
+
+	if (!service || !service->ipconfig_ipv4)
+		return false;
+
+	method4 = __connman_ipconfig_get_method(service->ipconfig_ipv4);
+
+	return (method4 == CONNMAN_IPCONFIG_METHOD_AUTO);
+}
+
 static int nameservers_changed_cb(void *user_data)
 {
 	struct connman_service *service = user_data;
@@ -4108,6 +4120,9 @@ static void address_updated(struct connman_service *service,
 		connman_service_get_identifier(service),
 		type, __connman_ipconfig_type2string(type));
 
+	SERVICE_LIST_SORT();
+	__connman_gateway_update();
+
 	if (is_connected(service->state) &&
 			connman_service_is_default(service)) {
 		nameserver_remove_all(service, type);
@@ -8444,6 +8459,22 @@ static gint service_compare(gconstpointer a, gconstpointer b)
 			return 1;
 	}
 
+	/*
+	 * Prefer configured IPv4 address over auto/link-local IP.
+	 * "online" services would already have won against "ready".
+	 */
+	if (state_a == CONNMAN_SERVICE_STATE_READY &&
+			state_b == CONNMAN_SERVICE_STATE_READY) {
+		bool a_is_link_local = is_ipv4ll(service_a);
+		bool b_is_link_local = is_ipv4ll(service_b);
+
+		if (!a_is_link_local && b_is_link_local)
+			return -1;
+
+		if (a_is_link_local && !b_is_link_local)
+			return 1;
+	}
+
 	/*
 	 * If at this point the services are still comparing as
 	 * equivalent, then use favorite status, giving priority to @a a
-- 
2.51.2