Commit b8e275ca62 for strongswan.org

commit b8e275ca6230d42fc24edb2d9881e8eb4a1b0c8f
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Wed Oct 21 18:08:56 2026 +0200

    kernel-libipsec: Add basic support for bypass policies

    This basically just sets the `pass` parameter when adding/removing
    routes and avoids installing an exclude route should there be an overlap.
    It does no advanced tracking of policies/routes (e.g. ordering them
    should there be a bypass and a regular policy for the same traffic).

    This only works on Linux and with a separate routing table.  The other
    backends don't implement the `pass` parameter.

    Closes strongswan/strongswan#2428

diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c
index c7dee9a480..bac7edf126 100644
--- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c
+++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c
@@ -117,6 +117,8 @@ struct route_entry_t {
 	uint8_t prefixlen;
 	/** Reference to exclude route, if any */
 	exclude_route_t *exclude;
+	/** Whether the route was installed for a bypass policy */
+	bool pass;
 };

 /**
@@ -144,7 +146,8 @@ static bool route_entry_equals(route_entry_t *a, route_entry_t *b)
 		{
 			return a->if_name && b->if_name && streq(a->if_name, b->if_name) &&
 				   chunk_equals(a->dst_net, b->dst_net) &&
-				   a->prefixlen == b->prefixlen;
+				   a->prefixlen == b->prefixlen &&
+				   a->pass == b->pass;
 		}
 	}
 	return FALSE;
@@ -158,6 +161,8 @@ typedef struct policy_entry_t policy_entry_t;
 struct policy_entry_t {
 	/** Direction of this policy: in, out, forward */
 	uint8_t direction;
+	/** Whether this is a bypass policy */
+	bool pass;
 	/** Parameters of installed policy */
 	struct {
 		/** Subnet and port */
@@ -178,11 +183,13 @@ struct policy_entry_t {
  */
 static policy_entry_t *create_policy_entry(traffic_selector_t *src_ts,
 										   traffic_selector_t *dst_ts,
-										   policy_dir_t dir)
+										   policy_dir_t dir,
+										   policy_type_t type)
 {
 	policy_entry_t *this;
 	INIT(this,
 		.direction = dir,
+		.pass = type == POLICY_PASS,
 	);

 	src_ts->to_subnet(src_ts, &this->src.net, &this->src.mask);
@@ -217,6 +224,7 @@ CALLBACK(policy_entry_equals, bool,

 	VA_ARGS_VGET(args, b);
 	return a->direction == b->direction &&
+		   a->pass == b->pass &&
 		   a->src.proto == b->src.proto &&
 		   a->dst.proto == b->dst.proto &&
 		   a->src.mask == b->src.mask &&
@@ -460,6 +468,7 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this,
 		.src_ip = src_ip,
 		.dst_net = chunk_clone(policy->dst.net->get_address(policy->dst.net)),
 		.prefixlen = policy->dst.mask,
+		.pass = policy->pass,
 	);
 #ifndef __linux__
 	/* on Linux we can't install a gateway */
@@ -480,7 +489,7 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this,
 		/* uninstall previously installed route */
 		if (charon->kernel->del_route(charon->kernel, old->dst_net,
 									  old->prefixlen, old->gateway, old->src_ip,
-									  old->if_name, FALSE) != SUCCESS)
+									  old->if_name, old->pass) != SUCCESS)
 		{
 			DBG1(DBG_KNL, "error uninstalling route installed with policy "
 				 "%R === %R %N", src_ts, dst_ts, policy_dir_names,
@@ -491,7 +500,7 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this,
 		policy->route = NULL;
 	}

-	if (!this->allow_peer_ts && dst_ts->is_host(dst_ts, dst))
+	if (!route->pass && !this->allow_peer_ts && dst_ts->is_host(dst_ts, dst))
 	{
 		DBG1(DBG_KNL, "can't install route for %R === %R %N, conflicts with "
 			 "IKE traffic", src_ts, dst_ts, policy_dir_names,
@@ -501,7 +510,7 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this,
 		return FALSE;
 	}
 	/* if remote traffic selector covers the IKE peer, add an exclude route */
-	if (!this->allow_peer_ts && dst_ts->includes(dst_ts, dst))
+	if (!route->pass && !this->allow_peer_ts && dst_ts->includes(dst_ts, dst))
 	{
 		/* add exclude route for peer */
 		add_exclude_route(this, route, src, dst);
@@ -512,7 +521,8 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this,

 	switch (charon->kernel->add_route(charon->kernel, route->dst_net,
 									  route->prefixlen, route->gateway,
-									  route->src_ip, route->if_name, FALSE))
+									  route->src_ip, route->if_name,
+									  route->pass))
 	{
 		case ALREADY_DONE:
 			/* route exists, do not uninstall */
@@ -542,6 +552,24 @@ METHOD(kernel_ipsec_t, add_policy, status_t,
 	policy_entry_t *policy, *found = NULL;
 	status_t status;

+	/* on Linux, bypass policies only work if a separate routing table is used,
+	 * they don't work on other platforms at all */
+	if (data->type == POLICY_PASS)
+	{
+#ifdef __linux__
+		if (!lib->settings->get_int(lib->settings, "%s.routing_table",
+									IPSEC_ROUTING_TABLE, lib->ns))
+#endif
+		{
+			if (id->dir == POLICY_OUT)
+			{
+				DBG1(DBG_KNL, "bypass policies not supported by this kernel "
+					 "backend/configuration");
+			}
+			return NOT_SUPPORTED;
+		}
+	}
+
 	status = ipsec->policies->add_policy(ipsec->policies, data->src, data->dst,
 										 id->src_ts, id->dst_ts, id->dir,
 										 data->type, data->sa, id->mark,
@@ -551,7 +579,7 @@ METHOD(kernel_ipsec_t, add_policy, status_t,
 		return status;
 	}
 	/* we track policies in order to install routes */
-	policy = create_policy_entry(id->src_ts, id->dst_ts, id->dir);
+	policy = create_policy_entry(id->src_ts, id->dst_ts, id->dir, data->type);

 	this->mutex->lock(this->mutex);
 	if (this->policies->find_first(this->policies, policy_entry_equals,
@@ -593,7 +621,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t,
 										 data->type, data->sa, id->mark,
 										 data->prio);

-	policy = create_policy_entry(id->src_ts, id->dst_ts, id->dir);
+	policy = create_policy_entry(id->src_ts, id->dst_ts, id->dir, data->type);

 	this->mutex->lock(this->mutex);
 	if (!this->policies->find_first(this->policies, policy_entry_equals,
@@ -618,7 +646,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t,

 		if (charon->kernel->del_route(charon->kernel, route->dst_net,
 								route->prefixlen, route->gateway, route->src_ip,
-								route->if_name, FALSE) != SUCCESS)
+								route->if_name, route->pass) != SUCCESS)
 		{
 			DBG1(DBG_KNL, "error uninstalling route installed with "
 				 "policy %R === %R %N", id->src_ts, id->dst_ts,
@@ -649,7 +677,8 @@ METHOD(kernel_ipsec_t, flush_policies, status_t,

 			charon->kernel->del_route(charon->kernel, route->dst_net,
 									  route->prefixlen, route->gateway,
-									  route->src_ip, route->if_name, FALSE);
+									  route->src_ip, route->if_name,
+									  route->pass);
 			remove_exclude_route(this, route);
 		}
 		policy_entry_destroy(pol);