Repository: zephyrproject-rtos/zephyr
Pull request #90010: Fix IPv6 address selection for deprecated addresses
Author: login: jukkar
Labels: name: area: Networking
Created: 2025-05-15T11:38:53Z | Merged: 2025-05-21T07:30:23Z

Always prefer preferred IPv6 address over deprecated one regardless of prefix length. This works now same way as in Linux.



Diff:
diff --git a/subsys/net/ip/net_if.c b/subsys/net/ip/net_if.c
index 198d44acd7b6c..b541787a0d981 100644
--- a/subsys/net/ip/net_if.c
+++ b/subsys/net/ip/net_if.c
@@ -2862,6 +2862,16 @@ struct net_if_ipv6_prefix *net_if_ipv6_prefix_get(struct net_if *iface,
 	}
 
 out:
+	if (prefix != NULL) {
+		NET_DBG("Found prefix %s/%d for %s",
+			net_sprint_ipv6_addr(&prefix->prefix),
+			prefix->len,
+			net_sprint_ipv6_addr(addr));
+	} else {
+		NET_DBG("No prefix found for %s",
+			net_sprint_ipv6_addr(addr));
+	}
+
 	net_if_unlock(iface);
 
 	return prefix;
@@ -3173,7 +3183,26 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 			len = prefix_len;
 		}
 
-		if (len >= *best_so_far) {
+		if (ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
+		    addr_state == NET_ADDR_PREFERRED) {
+			/* We have a preferred address and a deprecated
+			 * address. We prefer always the preferred address
+			 * over the deprecated address.
+			 * See RFC 6724 chapter 5.
+			 */
+			NET_DBG("skipping deprecated address %s",
+				net_sprint_ipv6_addr(&ipv6->unicast[i].address.in6_addr));
+			continue;
+		}
+
+		if (len >= *best_so_far ||
+		    (ipv6->unicast[i].addr_state == NET_ADDR_PREFERRED &&
+		     addr_state == NET_ADDR_DEPRECATED)) {
+			/* Currently we have best deprecated address, but
+			 * should now choose the preferred address regardless
+			 * of the length.
+			 */
+
 			/* Mesh local address can only be selected for the same
 			 * subnet.
 			 */
@@ -3182,17 +3211,6 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 				continue;
 			}
 
-			if (len == *best_so_far &&
-			    ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
-			    addr_state == NET_ADDR_PREFERRED) {
-				/* We have a preferred address and a deprecated
-				 * address. We prefer the preferred address if the
-				 * prefix lengths are the same.
-				 * See RFC 6724 chapter 5.
-				 */
-				continue;
-			}
-
 			addr_state = ipv6->unicast[i].addr_state;
 
 			NET_DBG("[%zd] Checking %s (%s) dst %s/%d", i,
@@ -5278,6 +5296,15 @@ static struct net_if_addr *get_ifaddr(struct net_if *iface,
 	return ifaddr;
 }
 
+/* This helper function is used only in tests. */
+#if defined(CONFIG_NET_TEST)
+struct net_if_addr *net_if_ipv6_get_ifaddr(struct net_if *iface,
+					   const void *addr)
+{
+	return get_ifaddr(iface, AF_INET6, addr, NULL);
+}
+#endif /* CONFIG_NET_TEST */
+
 static void remove_ipv6_ifaddr(struct net_if *iface,
 			       struct net_if_addr *ifaddr,
 			       unsigned int maddr_count)
diff --git a/tests/net/ip-addr/prj.conf b/tests/net/ip-addr/prj.conf
index 7c105491f5ec7..54e05b7653361 100644
--- a/tests/net/ip-addr/prj.conf
+++ b/tests/net/ip-addr/prj.conf
@@ -18,6 +18,7 @@ CONFIG_NET_IF_MAX_IPV6_COUNT=2
 CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3
 CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=2
 CONFIG_NET_IF_MCAST_IPV4_ADDR_COUNT=3
+CONFIG_NET_IF_IPV6_PREFIX_COUNT=4
 CONFIG_NET_L2_DUMMY=y
 CONFIG_NET_L2_ETHERNET=n
 CONFIG_ZTEST=y
diff --git a/tests/net/ip-addr/src/main.c b/tests/net/ip-addr/src/main.c
index aa0fb3ec6e47f..71df9d75b96d9 100644
--- a/tests/net/ip-addr/src/main.c
+++ b/tests/net/ip-addr/src/main.c
@@ -34,6 +34,9 @@ LOG_MODULE_REGISTER(net_test, CONFIG_NET_IPV6_LOG_LEVEL);
 #define DBG(fmt, ...)
 #endif
 
+extern struct net_if_addr *net_if_ipv6_get_ifaddr(struct net_if *iface,
+						  const void *addr);
+
 static struct net_if *default_iface;
 static struct net_if *second_iface;
 
@@ -210,6 +213,43 @@ ZTEST(ip_addr_fn, test_ip_addresses)
 	TEST_IPV4(127, 0, 0, 1, "127.0.0.1");
 }
 
+#if defined(CONFIG_NET_IPV6_LOG_LEVEL) || defined(CONFIG_NET_IPV4_LOG_LEVEL)
+static const char *addr_state_to_str(enum net_addr_state state)
+{
+	switch (state) {
+	case NET_ADDR_PREFERRED:
+		return "preferred";
+	case NET_ADDR_DEPRECATED:
+		return "deprecated";
+	case NET_ADDR_TENTATIVE:
+		return "tentative";
+	case NET_ADDR_ANY_STATE:
+		return "invalid";
+	default:
+		break;
+	}
+
+	return "unknown";
+}
+
+static const char *get_addr_state(struct net_if *iface,
+				  const struct in6_addr *addr)
+{
+	struct net_if_addr *ifaddr;
+
+	if (iface == NULL) {
+		return "<iface not set>";
+	}
+
+	ifaddr = net_if_ipv6_get_ifaddr(iface, addr);
+	if (ifaddr) {
+		return addr_state_to_str(ifaddr->addr_state);
+	}
+
+	return "<addr not found>";
+}
+#endif
+
 ZTEST(ip_addr_fn, test_ipv6_addresses)
 {
 	struct in6_addr loopback = IN6ADDR_LOOPBACK_INIT;
@@ -227,16 +267,16 @@ ZTEST(ip_addr_fn, test_ipv6_addresses)
 	struct in6_addr ula = { { { 0xfc, 0x00, 0xaa, 0xaa, 0, 0, 0, 0,
 				    0, 0, 0, 0, 0xd1, 0xd2, 0xd3, 0xd4 } } };
 	struct in6_addr ula2 = { { { 0xfc, 0x00, 0xaa, 0xaa, 0, 0, 0, 0,
-				     0, 0, 0, 0, 0x1, 0x2, 0x3, 0x4 } } };
+				0, 0, 0, 0, 0xd1, 0xd2, 0xd3, 2 } } };
 	struct in6_addr ula3 = { { { 0xfc, 0x00, 0xaa, 0xaa, 0, 0, 0, 0,
-				     0, 0, 0, 0, 0xf1, 0xf2, 0xf3, 0xf4 } } };
+				0, 0, 0, 0, 0xd1, 0xd2, 0xf3, 3 } } };
 	struct in6_addr ula4 = { { { 0xfc, 0x00, 0xaa, 0xaa, 0, 0, 0, 0,
-				     0, 0, 0, 0, 0xf1, 0xf2, 0xf3, 0xf5 } } };
+				0, 0, 0, 0, 0xd1, 0xd2, 0xf3, 4 } } };
 	struct in6_addr ula5 = { { { 0xfc, 0x00, 0xaa, 0xaa, 0, 0, 0, 0,
-				     0, 0, 0, 0, 0xf1, 0xf2, 0xf3, 0xf6 } } };
+				0, 0, 0, 0, 0xd1, 0xd2, 0xd3, 0xd5 } } };
 	struct in6_addr *tmp;
 	const struct in6_addr *out;
-	struct net_if_addr *ifaddr1, *ifaddr2;
+	struct net_if_addr *ifaddr1, *ifaddr2, *ifaddr_ula, *ifaddr_ula3, *ifaddr_ula4;
 	struct net_if_mcast_addr *ifmaddr1;
 	struct net_if_ipv6_prefix *prefix;
 	struct net_if *iface;
@@ -339,8 +379,10 @@ ZTEST(ip_addr_fn, test_ipv6_addresses)
 				 "IPv6 src addr selection failed, iface %p\n",
 				 iface);
 
-		DBG("Selected IPv6 address %s, iface %p\n",
-		       net_sprint_ipv6_addr(out), iface);
+		DBG("%d: Selected IPv6 address %s state %s, iface %p\n", __LINE__,
+		    net_sprint_ipv6_addr(out),
+		    get_addr_state(iface, out),
+		    iface);
 
 		zassert_false(memcmp(out->s6_addr, &addr6_pref2.s6_addr,
 				     sizeof(struct in6_addr)),
@@ -352,8 +394,9 @@ ZTEST(ip_addr_fn, test_ipv6_addresses)
 		zassert_not_null(out, "IPv6 src any addr selection failed, "
 				 "iface %p\n", iface);
 
-		DBG("Selected IPv6 address %s, iface %p\n",
-		       net_sprint_ipv6_addr(out), iface);
+		DBG("%d: Selected IPv6 address %s, iface %p\n", __LINE__,
+		    net_sprint_ipv6_addr(out),
+		    iface);
 
 		zassert_false(memcmp(out->s6_addr, &any.s6_addr,
 				     sizeof(struct in6_addr)),
@@ -367,8 +410,10 @@ ZTEST(ip_addr_fn, test_ipv6_addresses)
 		zassert_not_null(out,  "IPv6 src ll addr selection failed, "
 				 "iface %p\n", iface);
 
-		DBG("Selected IPv6 address %s, iface %p\n",
-		       net_sprint_ipv6_addr(out), iface);
+		DBG("%d: Selected IPv6 address %s state %s, iface %p\n", __LINE__,
+		    net_sprint_ipv6_addr(out),
+		    iface != NULL ? get_addr_state(iface, out) : "unknown",
+		    iface);
 
 		zassert_false(memcmp(out->s6_addr, &addr6.s6_addr,
 				     sizeof(struct in6_addr)),
@@ -384,18 +429,25 @@ ZTEST(ip_addr_fn, test_ipv6_addresses)
 	/**TESTPOINTS: Check what IPv6 address is selected when some
 	 * addresses are in preferred state and some in deprecated state.
 	 */
-	ifaddr2 = net_if_ipv6_addr_add(default_iface, &ula,
-				       NET_ADDR_AUTOCONF, 0);
-	zassert_not_null(ifaddr2, "IPv6 ula address add failed");
+	prefix = net_if_ipv6_prefix_add(default_iface, &ula, 96, 3600);
+	zassert_not_null(prefix, "IPv6 ula prefix add failed");
 
-	ifaddr2->addr_state = NET_ADDR_PREFERRED;
+	prefix = net_if_ipv6_prefix_add(default_iface, &ula2, 64, 3600);
+	zassert_not_null(prefix, "IPv6 ula prefix add failed");
+
+	ifaddr_ula = net_if_ipv6_addr_add(default_iface, &ula,
+					  NET_ADDR_AUTOCONF, 0);
+	zassert_not_null(ifaddr_ula, "IPv6 ula address add failed");
+
+	ifaddr_ula->addr_state = NET_ADDR_PREFERRED;
 
 	out = net_if_ipv6_select_src_addr(default_iface, &ula2);
 	zassert_not_null(out, "IPv6 src ula addr selection failed, "
 			 "iface %p\n", default_iface);
 
-	DBG("Selected IPv6 address %s, iface %p\n",
-	    net_sprint_ipv6_addr(out), iface);
+	DBG("%d: Selected IPv6 address %s state %s, iface %p\n", __LINE__,
+	    net_sprint_ipv6_addr(out), get_addr_state(default_iface, out),
+	    iface);
 
 	zassert_false(memcmp(out->s6_addr, &ula.s6_addr, sizeof(struct in6_addr)),
 		      "IPv6 wrong src ula address selected, iface %p\n", iface);
@@ -403,53 +455,89 @@ ZTEST(ip_addr_fn, test_ipv6_addresses)
 	/* Allow selection of deprecated address if no other address
 	 * is available.
 	 */
-	ifaddr2->addr_state = NET_ADDR_DEPRECATED;
+	ifaddr_ula->addr_state = NET_ADDR_DEPRECATED;
 
 	out = net_if_ipv6_select_src_addr(default_iface, &ula3);
 	zassert_not_null(out, "IPv6 src ula addr selection failed, "
 			 "iface %p\n", default_iface);
 
+	/* Back to preferred state so that later checks work correctly */
+	ifaddr_ula->addr_state = NET_ADDR_PREFERRED;
+
 	/* Then add another address with preferred state and check that we
-	 * still select the deprecated address as it is a better match.
+	 * still do not select the deprecated address even if it is a better match.
 	 */
-	ifaddr2 = net_if_ipv6_addr_add(default_iface, &ula3,
-				       NET_ADDR_AUTOCONF, 0);
-	zassert_not_null(ifaddr2, "IPv6 ula address add failed");
+	ifaddr_ula3 = net_if_ipv6_addr_add(default_iface, &ula3,
+					   NET_ADDR_AUTOCONF, 0);
+	zassert_not_null(ifaddr_ula3, "IPv6 ula address add failed");
 
-	ifaddr2->addr_state = NET_ADDR_PREFERRED;
+	ifaddr_ula3->addr_state = NET_ADDR_PREFERRED;
 
 	out = net_if_ipv6_select_src_addr(default_iface, &ula2);
 	zassert_not_null(out, "IPv6 src ula addr selection failed, "
 			 "iface %p\n", default_iface);
 
-	DBG("Selected IPv6 address %s, iface %p\n",
-	    net_sprint_ipv6_addr(out), iface);
+	DBG("%d: Selected IPv6 address %s state %s, iface %p\n", __LINE__,
+	    net_sprint_ipv6_addr(out), get_addr_state(default_iface, out),
+	    iface);
 
 	zassert_false(memcmp(out->s6_addr, &ula3.s6_addr, sizeof(struct in6_addr)),
 		      "IPv6 wrong src ula address selected, iface %p\n", iface);
 
-	zassert_true(net_if_ipv6_addr_rm(default_iface, &ula),
-		     "IPv6 removing address failed\n");
+	/* Then change the address to deprecated state and check that we
+	 * do select the deprecated address.
+	 */
+	ifaddr_ula3->addr_state = NET_ADDR_DEPRECATED;
 
-	prefix = net_if_ipv6_prefix_add(default_iface, &ula4, 96, 3600);
-	zassert_not_null(prefix, "IPv6 ula prefix add failed");
+	out = net_if_ipv6_select_src_addr(default_iface, &ula2);
+	zassert_not_null(out, "IPv6 src ula addr selection failed, "
+			 "iface %p\n", default_iface);
 
-	ifaddr1 = net_if_ipv6_addr_add(default_iface, &ula4,
-				       NET_ADDR_AUTOCONF, 0);
-	zassert_not_null(ifaddr1, "IPv6 ula address add failed");
+	DBG("%d: Selected IPv6 address %s state %s, iface %p\n", __LINE__,
+	    net_sprint_ipv6_addr(out), get_addr_state(default_iface, out),
+	    iface);
 
-	ifaddr2->addr_state = NET_ADDR_DEPRECATED;
+	zassert_false(memcmp(out->s6_addr, &ula.s6_addr, sizeof(struct in6_addr)),
+		      "IPv6 wrong src ula address selected, iface %p\n", iface);
+
+	/* Then have two deprecated addresses */
+	ifaddr_ula->addr_state = NET_ADDR_DEPRECATED;
+
+	out = net_if_ipv6_select_src_addr(default_iface, &ula2);
+	zassert_not_null(out, "IPv6 src ula addr selection failed, "
+			 "iface %p\n", default_iface);
+
+	DBG("%d: Selected IPv6 address %s state %s, iface %p\n", __LINE__,
+	    net_sprint_ipv6_addr(out), get_addr_state(default_iface, out),
+	    iface);
 
+	zassert_false(memcmp(out->s6_addr, &ula3.s6_addr, sizeof(struct in6_addr)),
+		      "IPv6 wrong src ula address selected, iface %p\n", iface);
+
+	ifaddr_ula4 = net_if_ipv6_addr_add(default_iface, &ula4,
+					   NET_ADDR_AUTOCONF, 0);
+	zassert_not_null(ifaddr_ula4, "IPv6 ula address add failed");
+
+	ifaddr_ula4->addr_state = NET_ADDR_DEPRECATED;
+	ifaddr_ula3->addr_state = NET_ADDR_PREFERRED;
+
+	/* There is now one preferred and two deprecated addresses.
+	 * The preferred address should be selected.
+	 */
 	out = net_if_ipv6_select_src_addr(default_iface, &ula5);
 	zassert_not_null(out, "IPv6 src ula addr selection failed, "
 			 "iface %p\n", default_iface);
 
-	DBG("Selected IPv6 address %s, iface %p\n",
-	    net_sprint_ipv6_addr(out), iface);
+	DBG("%d: Selected IPv6 address %s state %s, iface %p\n", __LINE__,
+	    net_sprint_ipv6_addr(out), get_addr_state(default_iface, out),
+	    iface);
 
-	zassert_false(memcmp(out->s6_addr, &ula4.s6_addr, sizeof(struct in6_addr)),
+	zassert_false(memcmp(out->s6_addr, &ula3.s6_addr, sizeof(struct in6_addr)),
 		      "IPv6 wrong src ula address selected, iface %p\n", iface);
 
+	zassert_true(net_if_ipv6_addr_rm(default_iface, &ula),
+		     "IPv6 removing address failed\n");
+
 	zassert_true(net_if_ipv6_addr_rm(default_iface, &ula3),
 		     "IPv6 removing address failed\n");
 
@@ -489,8 +577,8 @@ ZTEST(ip_addr_fn, test_ipv4_ll_address_select_default_first)
 	zassert_not_null(out, "IPv4 src addr selection failed, iface %p\n",
 			 iface);
 
-	DBG("Selected IPv4 address %s, iface %p\n", net_sprint_ipv4_addr(out),
-	    iface);
+	DBG("%d: Selected IPv4 address %s, iface %p\n", __LINE__,
+	    net_sprint_ipv4_addr(out), iface);
 
 	zassert_equal_ptr(iface, default_iface, "Wrong iface selected");
 	zassert_equal(out->s_addr, lladdr4_1.s_addr,
@@ -528,8 +616,8 @@ ZTEST(ip_addr_fn, test_ipv4_ll_address_select)
 	zassert_not_null(out, "IPv4 src addr selection failed, iface %p\n",
 			 iface);
 
-	DBG("Selected IPv4 address %s, iface %p\n", net_sprint_ipv4_addr(out),
-	    iface);
+	DBG("%d: Selected IPv4 address %s, iface %p\n", __LINE__,
+	    net_sprint_ipv4_addr(out), iface);
 
 	zassert_equal(out->s_addr, lladdr4_1.s_addr,
 		      "IPv4 wrong src address selected, iface %p\n", iface);
@@ -540,8 +628,8 @@ ZTEST(ip_addr_fn, test_ipv4_ll_address_select)
 	zassert_not_null(out, "IPv4 src addr selection failed, iface %p\n",
 			 iface);
 
-	DBG("Selected IPv4 address %s, iface %p\n", net_sprint_ipv4_addr(out),
-	    iface);
+	DBG("%d: Selected IPv4 address %s, iface %p\n", __LINE__,
+	    net_sprint_ipv4_addr(out), iface);
 
 	zassert_equal(out->s_addr, lladdr4_2.s_addr,
 		      "IPv4 wrong src address selected, iface %p\n", iface);
@@ -606,7 +694,7 @@ ZTEST(ip_addr_fn, test_ipv4_addresses)
 		zassert_not_null(out,  "IPv4 src addr selection failed, "
 				 "iface %p\n", iface);
 
-		DBG("Selected IPv4 address %s, iface %p\n",
+		DBG("%d: Selected IPv4 address %s, iface %p\n", __LINE__,
 		       net_sprint_ipv4_addr(out), iface);
 
 		zassert_equal(out->s_addr, addr4.s_addr,
@@ -618,7 +706,7 @@ ZTEST(ip_addr_fn, test_ipv4_addresses)
 		zassert_not_null(out, "IPv4 src ll addr selection failed, "
 				 "iface %p\n", iface);
 
-		DBG("Selected IPv4 address %s, iface %p\n",
+		DBG("%d: Selected IPv4 address %s, iface %p\n", __LINE__,
 		       net_sprint_ipv4_addr(out), iface);
 
 		zassert_equal(out->s_addr, lladdr4.s_addr,
@@ -630,7 +718,7 @@ ZTEST(ip_addr_fn, test_ipv4_addresses)
 		zassert_not_null(out, "IPv4 src any addr selection failed, "
 				 "iface %p\n", iface);
 
-		DBG("Selected IPv4 address %s, iface %p\n",
+		DBG("%d: Selected IPv4 address %s, iface %p\n", __LINE__,
 		       net_sprint_ipv4_addr(out), iface);
 
 		zassert_equal(out->s_addr, addr4.s_addr,
@@ -642,7 +730,7 @@ ZTEST(ip_addr_fn, test_ipv4_addresses)
 		zassert_not_null(out, "IPv4 src any addr selection failed, "
 				 "iface %p\n", iface);
 
-		DBG("Selected IPv4 address %s, iface %p\n",
+		DBG("%d: Selected IPv4 address %s, iface %p\n", __LINE__,
 		       net_sprint_ipv4_addr(out), iface);
 
 		zassert_equal(out->s_addr, addr4.s_addr,


Reviews:
user: login: Damian-Nordic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T12:12:01Z
user: login: jukkar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T12:19:29Z
user: login: Damian-Nordic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T12:38:24Z
user: login: rlubos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T12:40:32Z
user: login: jukkar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T13:03:53Z
user: login: Damian-Nordic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T07:24:54Z
user: login: jukkar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T09:33:58Z
user: login: Damian-Nordic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T09:45:41Z
user: login: jukkar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T12:16:02Z
user: login: jukkar
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T12:19:27Z
user: login: rlubos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T12:21:58Z
user: login: rlubos
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T10:43:24Z
user: login: Damian-Nordic
body: Thanks!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T10:44:46Z
user: login: pdgendt
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T06:25:55Z

Review comments:
diff_hunk: @@ -3182,14 +3182,15 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 				continue;
 			}
 
-			if (len == *best_so_far &&
-			    ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
+			if (ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
path: subsys/net/ip/net_if.c
user: login: Damian-Nordic
body: This addresses the case where deprecated address comes after preferred one, but there's another scenario in which the preferred address may fail the `len >= *best_so_far` condition and hence it may not be selected over the deprecated address, right?
created_at: 2025-05-15T12:12:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3182,14 +3182,15 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 				continue;
 			}
 
-			if (len == *best_so_far &&
-			    ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
+			if (ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
path: subsys/net/ip/net_if.c
user: login: jukkar
body: There is a check
```
	if (len >= *best_so_far) {
```
few lines above. It should select the best prefix, we then skip the deprecated address if the other one is preferred one. I tried to construct the tests so that they verify this. Please clarify more what you mean if I missed something.

created_at: 2025-05-15T12:19:28Z
author_association: MEMBER
diff_hunk: @@ -3182,14 +3182,15 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 				continue;
 			}
 
-			if (len == *best_so_far &&
-			    ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
+			if (ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
path: subsys/net/ip/net_if.c
user: login: Damian-Nordic
body: I think the order of processing addresses shouldn't matter.

In case the address array contains:
[0] Preferred address for which `get_diff_ipv6` returns 32
[1] Deprecated address for which `get_diff_ipv6` returns 64

the code will correctly choose [0] despite [1] being a better match prefix-wise.

However if the order of elements is opposite:
[0] Deprecated address for which `get_diff_ipv6` returns 64
[1] Preferred address for which `get_diff_ipv6` returns 32

the code will choose [0] because [1] will be discarded early due to `len >= *best_so_far` not being satisfied (`32 >= 64`). I think the code should define some kind of `is_address_better` predicate and this predicate should exit early whenever one address is preferred and another one is deprecated.

created_at: 2025-05-15T12:38:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3182,14 +3182,15 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 				continue;
 			}
 
-			if (len == *best_so_far &&
-			    ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
+			if (ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
path: subsys/net/ip/net_if.c
user: login: rlubos
body: I think @Damian-Nordic is right, this will fail if the deprecated address is checked first, for example:
1. We first check deprecated address, with 64-bit prefix match. `best_so_far` is set to 64
2. In the second iteration we check preferred address with 40-bit prefix match. The preferred address should take preference, but since the prefix match is worse than `best_so_far`, we won't even check if the current address is deprecated.

I think we need to check `addr_state` for deprecated address before we do the prefix-match check.
created_at: 2025-05-15T12:40:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3182,14 +3182,15 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 				continue;
 			}
 
-			if (len == *best_so_far &&
-			    ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
+			if (ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
path: subsys/net/ip/net_if.c
user: login: jukkar
body: Indeed, good point. I will rework the code.
created_at: 2025-05-15T13:03:53Z
author_association: MEMBER
diff_hunk: @@ -2864,6 +2864,16 @@ struct net_if_ipv6_prefix *net_if_ipv6_prefix_get(struct net_if *iface,
 out:
 	net_if_unlock(iface);
 
+	if (prefix != NULL) {
path: subsys/net/ip/net_if.c
user: login: Damian-Nordic
body: You may want to do this before `net_if_unlock` to make sure the prefix is not changed in the middle of printing it.
created_at: 2025-05-16T07:21:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3173,6 +3183,18 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 			len = prefix_len;
 		}
 
+		if (ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
path: subsys/net/ip/net_if.c
user: login: Damian-Nordic
body: Just moving it here is not enough. What you need is turning
```
if (len >= *best_so_far) {
```
into sth like
```
if (len >= *best_so_far || (ipv6->unicast[i].addr_state == NET_ADDR_PREFERRED && addr_state == NET_ADDR_DEPRECATED) {
```
right?
created_at: 2025-05-16T07:24:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3173,6 +3183,18 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 			len = prefix_len;
 		}
 
+		if (ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
path: subsys/net/ip/net_if.c
user: login: jukkar
body: If we always prefer preferred address instead of deprecated, then we do not really need to check the prefix length as it should not matter here. Note that the `addr_state` variable stores the state of the current best address.
created_at: 2025-05-16T09:33:58Z
author_association: MEMBER
diff_hunk: @@ -3173,6 +3183,18 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 			len = prefix_len;
 		}
 
+		if (ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
path: subsys/net/ip/net_if.c
user: login: Damian-Nordic
body: > If we always prefer preferred address instead of deprecated, then we do not really need to check the prefix length as it should not matter here

Yes, and this is what we need - in case A is preferred and B is deprecated we don't care whether CommonPrefixLength(A, destination) is longer than CommonPrefixLength(B, destination). The common prefix length should only matter when comparing deprecated to deprecated or preferred to preferred.
created_at: 2025-05-16T09:45:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3173,6 +3183,18 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 			len = prefix_len;
 		}
 
+		if (ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
path: subsys/net/ip/net_if.c
user: login: jukkar
body: > The common prefix length should only matter when comparing deprecated to deprecated or preferred to preferred.

That should happen with the current code in the PR, the tests should verify it.
created_at: 2025-05-16T12:16:02Z
author_association: MEMBER
diff_hunk: @@ -3173,6 +3183,18 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 			len = prefix_len;
 		}
 
+		if (ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
path: subsys/net/ip/net_if.c
user: login: jukkar
body: Output from running the tests
```
START - test_ipv6_addresses
...
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_prefix_add: (test_ipv6_addresses): [0] interface 0x806e9e4 prefix fc00:aaaa::d1d2:d3d4/96 added
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_prefix_add: (test_ipv6_addresses): [1] interface 0x806e9e4 prefix fc00:aaaa::d1d2:d302/64 added
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_addr_add: (test_ipv6_addresses): [0] interface 1 (0x806e9e4) address fc00:aaaa::d1d2:d3d4 type AUTO added
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_prefix_get: (test_ipv6_addresses): Found prefix fc00:aaaa::d1d2:d302/64 for fc00:aaaa::d1d2:d302
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): [0] Checking fc00:aaaa::d1d2:d3d4 (preferred) dst fc00:aaaa::d1d2:d302/64
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): Selected fc00:aaaa::d1d2:d3d4 (preferred) dst fc00:aaaa::d1d2:d302/64
448: Selected IPv6 address fc00:aaaa::d1d2:d3d4 state preferred, iface (nil)
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_prefix_get: (test_ipv6_addresses): Found prefix fc00:aaaa::d1d2:d302/64 for fc00:aaaa::d1d2:f303
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): [0] Checking fc00:aaaa::d1d2:d3d4 (deprecated) dst fc00:aaaa::d1d2:f303/64
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): Selected fc00:aaaa::d1d2:d3d4 (deprecated) dst fc00:aaaa::d1d2:f303/64
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_addr_add: (test_ipv6_addresses): [1] interface 1 (0x806e9e4) address fc00:aaaa::d1d2:f303 type AUTO added
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_prefix_get: (test_ipv6_addresses): Found prefix fc00:aaaa::d1d2:d302/64 for fc00:aaaa::d1d2:d302
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): [0] Checking fc00:aaaa::d1d2:d3d4 (preferred) dst fc00:aaaa::d1d2:d302/64
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): [1] Checking fc00:aaaa::d1d2:f303 (preferred) dst fc00:aaaa::d1d2:d302/64
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): Selected fc00:aaaa::d1d2:f303 (preferred) dst fc00:aaaa::d1d2:d302/64
480: Selected IPv6 address fc00:aaaa::d1d2:f303 state preferred, iface (nil)
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_prefix_get: (test_ipv6_addresses): Found prefix fc00:aaaa::d1d2:d302/64 for fc00:aaaa::d1d2:d302
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): [0] Checking fc00:aaaa::d1d2:d3d4 (preferred) dst fc00:aaaa::d1d2:d302/64
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): skipping deprecated address fc00:aaaa::d1d2:f303
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): Selected fc00:aaaa::d1d2:d3d4 (preferred) dst fc00:aaaa::d1d2:d302/64
496: Selected IPv6 address fc00:aaaa::d1d2:d3d4 state preferred, iface (nil)
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_prefix_get: (test_ipv6_addresses): Found prefix fc00:aaaa::d1d2:d302/64 for fc00:aaaa::d1d2:d302
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): [0] Checking fc00:aaaa::d1d2:d3d4 (deprecated) dst fc00:aaaa::d1d2:d302/64
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): [1] Checking fc00:aaaa::d1d2:f303 (deprecated) dst fc00:aaaa::d1d2:d302/64
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): Selected fc00:aaaa::d1d2:f303 (deprecated) dst fc00:aaaa::d1d2:d302/64
510: Selected IPv6 address fc00:aaaa::d1d2:f303 state deprecated, iface (nil)
```
Above were two deprecated addresses and we select the best from those two.
```
cont...
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_addr_add: (test_ipv6_addresses): [2] interface 1 (0x806e9e4) address fc00:aaaa::d1d2:f304 type AUTO added
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_prefix_get: (test_ipv6_addresses): Found prefix fc00:aaaa::d1d2:d302/64 for fc00:aaaa::d1d2:d3d5
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): [0] Checking fc00:aaaa::d1d2:d3d4 (deprecated) dst fc00:aaaa::d1d2:d3d5/64
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): [1] Checking fc00:aaaa::d1d2:f303 (preferred) dst fc00:aaaa::d1d2:d3d5/64
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): skipping deprecated address fc00:aaaa::d1d2:f304
[00:00:00.000,000] <dbg> net_if: net_if_ipv6_get_best_match: (test_ipv6_addresses): Selected fc00:aaaa::d1d2:f303 (preferred) dst fc00:aaaa::d1d2:d3d5/64
531: Selected IPv6 address fc00:aaaa::d1d2:f303 state preferred, iface (nil)
[00:00:00.000,000] <dbg> net_if: net_if_addr_unref_debug: (test_ipv6_addresses): [1] ifaddr fc00:aaaa::d1d2:d3d4 state 2 ref 0 (net_if_ipv6_addr_rm():2107)
[00:00:00.000,000] <dbg> net_if: net_if_addr_unref_debug: (test_ipv6_addresses): [1] ifaddr fc00:aaaa::d1d2:f303 state 1 ref 0 (net_if_ipv6_addr_rm():2107)
[00:00:00.000,000] <dbg> net_if: net_if_addr_unref_debug: (test_ipv6_addresses): [1] ifaddr fc00:aaaa::d1d2:f304 state 2 ref 0 (net_if_ipv6_addr_rm():2107)
 PASS - test_ipv6_addresses in 0.000 seconds
===================================================================
```

created_at: 2025-05-16T12:19:27Z
author_association: MEMBER
diff_hunk: @@ -3173,6 +3183,18 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface,
 			len = prefix_len;
 		}
 
+		if (ipv6->unicast[i].addr_state == NET_ADDR_DEPRECATED &&
path: subsys/net/ip/net_if.c
user: login: rlubos
body: I think we're missing `ipv6->unicast[i].addr_state == NET_ADDR_PREFERRED && addr_state == NET_ADDR_DEPRECATED` case (current best choice is deprecated address), in such case we should update the chosen address regardless of the prefix match.
created_at: 2025-05-16T12:21:58Z
author_association: CONTRIBUTOR

PR comments:
user: login: jukkar
created_at: 2025-05-19T10:24:23Z
body: * Updated according to comments
author_association: MEMBER
user: login: sonarqubecloud[bot]
created_at: 2025-05-19T10:43:40Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=zephyrproject-rtos_zephyr&pullRequest=90010) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=zephyrproject-rtos_zephyr&pullRequest=90010&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=zephyrproject-rtos_zephyr&pullRequest=90010&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=zephyrproject-rtos_zephyr&pullRequest=90010&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=zephyrproject-rtos_zephyr&pullRequest=90010&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=zephyrproject-rtos_zephyr&pullRequest=90010&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=zephyrproject-rtos_zephyr&pullRequest=90010)


author_association: NONE
user: login: jukkar
created_at: 2025-05-21T06:11:55Z
body: @pdgendt some help would be needed in review, please take a look if you have time, thanks!
author_association: MEMBER