Commit a0ac1c6d65 for strongswan.org

commit a0ac1c6d65e8ddbcd93bacc01c11e09c6b88f8d6
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Thu Sep 17 10:26:27 2026 +0200

    host: Add helper functions to check if an IP is loopback or link-local

diff --git a/src/libstrongswan/networking/host.c b/src/libstrongswan/networking/host.c
index a63673a76e..dc2bf5b8c3 100644
--- a/src/libstrongswan/networking/host.c
+++ b/src/libstrongswan/networking/host.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2014 Tobias Brunner
+ * Copyright (C) 2006-2026 Tobias Brunner
  * Copyright (C) 2006 Daniel Roethlisberger
  * Copyright (C) 2005-2006 Martin Willi
  * Copyright (C) 2005 Jan Hutter
@@ -701,3 +701,65 @@ host_t *host_create_any(int family)
 	free(this);
 	return NULL;
 }
+
+/*
+ * Described in header
+ */
+bool host_is_loopback(host_t *host)
+{
+	private_host_t *this = (private_host_t*)host;
+
+	if (!host)
+	{
+		return FALSE;
+	}
+
+	switch (this->address.sa_family)
+	{
+		case AF_INET:
+		{
+			/* for IPv4 it's the whole 127.0.0.0/8 subnet */
+			return ntohl(this->address4.sin_addr.s_addr) >> 24 == 0x7f;
+		}
+		case AF_INET6:
+		{
+			/* only a single address (::1) for IPv6 */
+			return IN6_IS_ADDR_LOOPBACK(&this->address6.sin6_addr);
+		}
+		default:
+		{
+			return FALSE;
+		}
+	}
+}
+
+/*
+ * Described in header
+ */
+bool host_is_linklocal(host_t *host)
+{
+	private_host_t *this = (private_host_t*)host;
+
+	if (!host)
+	{
+		return FALSE;
+	}
+
+	switch (this->address.sa_family)
+	{
+		case AF_INET:
+		{
+			/* 169.254.0.0/16 */
+			return ntohl(this->address4.sin_addr.s_addr) >> 16 == 0xa9fe;
+		}
+		case AF_INET6:
+		{
+			/* fe80::/10 */
+			return IN6_IS_ADDR_LINKLOCAL(&this->address6.sin6_addr);
+		}
+		default:
+		{
+			return FALSE;
+		}
+	}
+}
diff --git a/src/libstrongswan/networking/host.h b/src/libstrongswan/networking/host.h
index 6abd937439..96d5a8c21b 100644
--- a/src/libstrongswan/networking/host.h
+++ b/src/libstrongswan/networking/host.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2014 Tobias Brunner
+ * Copyright (C) 2006-2026 Tobias Brunner
  * Copyright (C) 2006 Daniel Roethlisberger
  * Copyright (C) 2005-2008 Martin Willi
  * Copyright (C) 2005 Jan Hutter
@@ -220,6 +220,22 @@ host_t *host_create_netmask(int family, int netbits);
  */
 host_t *host_create_any(int family);

+/**
+ * Check if the host's IP address is set to a loopback address.
+ *
+ * @param host			host to check
+ * @return				TRUE if host matches 127.0.0.0/8 or ::1/128
+ */
+bool host_is_loopback(host_t *host);
+
+/**
+ * Check if the host's IP address is set to a link-local address.
+ *
+ * @param host			host to check
+ * @return				TRUE if host matches 169.254.0.0/16 or fe80::/10
+ */
+bool host_is_linklocal(host_t *host);
+
 /**
  * printf hook function for host_t.
  *
diff --git a/src/libstrongswan/tests/suites/test_host.c b/src/libstrongswan/tests/suites/test_host.c
index 27cec85ddc..7ac6a0541c 100644
--- a/src/libstrongswan/tests/suites/test_host.c
+++ b/src/libstrongswan/tests/suites/test_host.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2013-2026 Tobias Brunner
  *
  * Copyright (C) secunet Security Networks AG
  *
@@ -642,6 +642,74 @@ START_TEST(test_equals_any)
 }
 END_TEST

+/*******************************************************************************
+ * host_is_loopback/host_is_linklocal
+ */
+
+START_TEST(test_host_is_loopback)
+{
+	host_t *a;
+
+	ck_assert(!host_is_loopback(NULL));
+
+	a = host_create_from_string("192.168.0.1", 0);
+	ck_assert(!host_is_loopback(a));
+	a->destroy(a);
+
+	a = host_create_from_string("127.0.0.1", 0);
+	ck_assert(host_is_loopback(a));
+	a->destroy(a);
+
+	a = host_create_from_string("127.255.255.255", 0);
+	ck_assert(host_is_loopback(a));
+	a->destroy(a);
+
+	a = host_create_from_string("fec1::1", 0);
+	ck_assert(!host_is_loopback(a));
+	a->destroy(a);
+
+	a = host_create_from_string("::1", 0);
+	ck_assert(host_is_loopback(a));
+	a->destroy(a);
+
+	a = host_create_from_string("::2", 0);
+	ck_assert(!host_is_loopback(a));
+	a->destroy(a);
+}
+END_TEST
+
+START_TEST(test_host_is_linklocal)
+{
+	host_t *a;
+
+	ck_assert(!host_is_linklocal(NULL));
+
+	a = host_create_from_string("192.168.0.1", 0);
+	ck_assert(!host_is_linklocal(a));
+	a->destroy(a);
+
+	a = host_create_from_string("169.254.0.1", 0);
+	ck_assert(host_is_linklocal(a));
+	a->destroy(a);
+
+	a = host_create_from_string("169.254.255.255", 0);
+	ck_assert(host_is_linklocal(a));
+	a->destroy(a);
+
+	a = host_create_from_string("fec1::1", 0);
+	ck_assert(!host_is_linklocal(a));
+	a->destroy(a);
+
+	a = host_create_from_string("fe80::1", 0);
+	ck_assert(host_is_linklocal(a));
+	a->destroy(a);
+
+	a = host_create_from_string("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 0);
+	ck_assert(host_is_linklocal(a));
+	a->destroy(a);
+}
+END_TEST
+
 /*******************************************************************************
  * clone
  */
@@ -790,6 +858,11 @@ Suite *host_suite_create()
 	tcase_add_test(tc, test_equals_any);
 	suite_add_tcase(s, tc);

+	tc = tcase_create("host_is_loopback/host_is_linklocal");
+	tcase_add_test(tc, test_host_is_loopback);
+	tcase_add_test(tc, test_host_is_linklocal);
+	suite_add_tcase(s, tc);
+
 	tc = tcase_create("clone");
 	tcase_add_test(tc, test_clone);
 	suite_add_tcase(s, tc);