libreswan/libreswan-5.1-pexpect-negotiating-ike-sa.patch
Daiki Ueno 1394b8e496 Avoid expectiation failure with crossing streams
Resolves: RHEL-73236
Signed-off-by: Daiki Ueno <dueno@redhat.com>
2025-01-24 16:16:01 +09:00

738 lines
27 KiB
Diff

From d421aeb263d81bacb92e392c65910054f71f9843 Mon Sep 17 00:00:00 2001
From: Andrew Cagney <cagney@gnu.org>
Date: Thu, 16 Jan 2025 19:28:00 -0500
Subject: [PATCH 1/4] revival: move state specific pexpects to
scheduled_{ike,child}_revival()
---
programs/pluto/revival.c | 101 +++++++++++++++++++--------------------
programs/pluto/revival.h | 4 +-
programs/pluto/routing.c | 21 ++++++--
3 files changed, 66 insertions(+), 60 deletions(-)
diff --git a/programs/pluto/revival.c b/programs/pluto/revival.c
index 0da0d7e4d2..8a2c779c18 100644
--- a/programs/pluto/revival.c
+++ b/programs/pluto/revival.c
@@ -191,58 +191,9 @@ static void schedule_revival_event(struct connection *c, struct logger *logger,
(impair.revival ? "revival" : NULL), logger);
}
-bool scheduled_revival(struct connection *c, struct state *st/*can be NULL*/,
- const char *subplot, struct logger *logger)
+static bool scheduled_revival(struct connection *c, struct state *st/*can be NULL*/,
+ const char *subplot, struct logger *logger)
{
- if (st != NULL) {
- /*
- * pexpect() ST is the owner. Routing should never
- * call when it isn't.
- */
- if (IS_CHILD_SA(st)) {
- if (c->negotiating_child_sa != SOS_NOBODY &&
- c->negotiating_child_sa != st->st_serialno) {
- /*
- * There's a newer SA playing with the routing.
- * Presumably this is an old Child SA that is in the
- * process of being rekeyed or replaced.
- */
- llog_pexpect(st->logger, HERE,
- "revival: skipping, .negotiating_child_sa "PRI_SO" is not us",
- pri_so(c->negotiating_child_sa));
- return false;
- }
-
- if (c->established_child_sa != SOS_NOBODY &&
- c->established_child_sa != st->st_serialno) {
- /* should be covered by above */
- llog_pexpect(st->logger, HERE,
- "revival: skipping, .established_child_sa "PRI_SO" is not us",
- pri_so(c->established_child_sa));
- return false;
- }
- }
-
- if (IS_IKE_SA(st)) {
- if (c->negotiating_ike_sa != SOS_NOBODY &&
- c->negotiating_ike_sa != st->st_serialno) {
- /* should be covered by above */
- llog_pexpect(st->logger, HERE,
- "revival: skipping, .negotiating_ike_sa "PRI_SO" is is not us",
- pri_so(c->negotiating_ike_sa));
- return false;
- }
- if (c->established_ike_sa != SOS_NOBODY &&
- c->established_ike_sa != st->st_serialno) {
- /* should be covered by above */
- llog_pexpect(st->logger, HERE,
- "revival: skipping, .established_ike_sa "PRI_SO" is is not us",
- pri_so(c->established_ike_sa));
- return false;
- }
- }
- }
-
if (!revival_plausable(c, logger)) {
return false;
}
@@ -275,14 +226,58 @@ bool scheduled_revival(struct connection *c, struct state *st/*can be NULL*/,
}
+bool scheduled_connection_revival(struct connection *c, const char *subplot)
+{
+ return scheduled_revival(c, NULL, subplot, c->logger);
+}
+
bool scheduled_child_revival(struct child_sa *child, const char *subplot)
{
- return scheduled_revival(child->sa.st_connection, &child->sa, subplot, child->sa.logger);
+ struct connection *c = child->sa.st_connection;
+ if (c->negotiating_child_sa != SOS_NOBODY &&
+ c->negotiating_child_sa != child->sa.st_serialno) {
+ /*
+ * There's a newer SA playing with the routing.
+ * Presumably this is an old Child SA that is in the
+ * process of being rekeyed or replaced.
+ */
+ llog_pexpect(child->sa.logger, HERE,
+ "revival: skipping, .negotiating_child_sa "PRI_SO" is not us",
+ pri_so(c->negotiating_child_sa));
+ return false;
+ }
+
+ if (c->established_child_sa != SOS_NOBODY &&
+ c->established_child_sa != child->sa.st_serialno) {
+ /* should be covered by above */
+ llog_pexpect(child->sa.logger, HERE,
+ "revival: skipping, .established_child_sa "PRI_SO" is not us",
+ pri_so(c->established_child_sa));
+ return false;
+ }
+ return scheduled_revival(c, &child->sa, subplot, child->sa.logger);
}
bool scheduled_ike_revival(struct ike_sa *ike, const char *subplot)
{
- return scheduled_revival(ike->sa.st_connection, &ike->sa, subplot, ike->sa.logger);
+ struct connection *c = ike->sa.st_connection;
+ if (c->negotiating_ike_sa != SOS_NOBODY &&
+ c->negotiating_ike_sa != ike->sa.st_serialno) {
+ /* should be covered by above */
+ llog_pexpect(ike->sa.logger, HERE,
+ "revival: skipping, .negotiating_ike_sa "PRI_SO" is is not us",
+ pri_so(c->negotiating_ike_sa));
+ return false;
+ }
+ if (c->established_ike_sa != SOS_NOBODY &&
+ c->established_ike_sa != ike->sa.st_serialno) {
+ /* should be covered by above */
+ llog_pexpect(ike->sa.logger, HERE,
+ "revival: skipping, .established_ike_sa "PRI_SO" is is not us",
+ pri_so(c->established_ike_sa));
+ return false;
+ }
+ return scheduled_revival(c, &ike->sa, subplot, ike->sa.logger);
}
void revive_connection(struct connection *c, const char *subplot,
diff --git a/programs/pluto/revival.h b/programs/pluto/revival.h
index dad6705301..13b00980dd 100644
--- a/programs/pluto/revival.h
+++ b/programs/pluto/revival.h
@@ -25,14 +25,12 @@ struct timer_event;
void revive_connection(struct connection *c, const char *subplot,
const threadtime_t *inception);
-bool scheduled_revival(struct connection *c, struct state *st, /*could-be-NULL*/
- const char *subplot, struct logger *logger);
-
/*
* As in the SA's connection should be kept up so the call scheduled a
* revival. Caller should adjust routing accordingly.
*/
+bool scheduled_connection_revival(struct connection *c, const char *subplot);
bool scheduled_child_revival(struct child_sa *child, const char *subplot);
bool scheduled_ike_revival(struct ike_sa *ike, const char *subplot);
diff --git a/programs/pluto/routing.c b/programs/pluto/routing.c
index 91115ab081..dd9b39721c 100644
--- a/programs/pluto/routing.c
+++ b/programs/pluto/routing.c
@@ -104,14 +104,27 @@ static bool connection_cannot_die(enum routing_event event,
struct logger *logger,
const struct routing_annex *e)
{
- struct state *st = (e->child != NULL && (*e->child) != NULL ? &(*e->child)->sa :
- e->ike != NULL && (*e->ike) != NULL ? &(*e->ike)->sa :
- NULL);
const char *subplot = (event == CONNECTION_TEARDOWN_IKE ? e->story :
event == CONNECTION_TEARDOWN_CHILD ? e->story :
event == CONNECTION_RESCHEDULE ? e->story :
"???");
- return scheduled_revival(c, st, subplot, logger);
+ if (e->child != NULL) {
+ struct child_sa *child = (*e->child);
+ if (child != NULL) {
+ PEXPECT(logger, child->sa.st_connection == c);
+ return scheduled_child_revival(child, subplot);
+ }
+ }
+
+ if (e->ike != NULL) {
+ struct ike_sa *ike = (*e->ike);
+ if (ike != NULL) {
+ PEXPECT(logger, ike->sa.st_connection == c);
+ return scheduled_ike_revival(ike, subplot);
+ }
+ }
+
+ return scheduled_connection_revival(c, subplot);
}
static void jam_sa(struct jambuf *buf, struct state *st, const char **sep)
--
2.48.1
From e474d36c43798898fa89d28ba302868399329f95 Mon Sep 17 00:00:00 2001
From: Andrew Cagney <cagney@gnu.org>
Date: Thu, 16 Jan 2025 20:20:24 -0500
Subject: [PATCH 2/4] revival: don't expect the IKE SA have
.{established,negotiating}_ike_sa
A crossing stream where this end initiated the connection's
IKE SA but is then crossed by the peer establishing the
connection's IKE SA only to connswitch to another
connection for the Child SA - leaves .negotiating_sa
but nothing else.
see #1989 EXPECTATION FAILED: revival: skipping, .negotiating_ike_sa is not us
---
programs/pluto/revival.c | 25 +++++++++++++++----------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/programs/pluto/revival.c b/programs/pluto/revival.c
index 8a2c779c18..fe76d1039e 100644
--- a/programs/pluto/revival.c
+++ b/programs/pluto/revival.c
@@ -261,21 +261,26 @@ bool scheduled_child_revival(struct child_sa *child, const char *subplot)
bool scheduled_ike_revival(struct ike_sa *ike, const char *subplot)
{
struct connection *c = ike->sa.st_connection;
- if (c->negotiating_ike_sa != SOS_NOBODY &&
- c->negotiating_ike_sa != ike->sa.st_serialno) {
- /* should be covered by above */
+ if (c->routing_sa != ike->sa.st_serialno) {
llog_pexpect(ike->sa.logger, HERE,
- "revival: skipping, .negotiating_ike_sa "PRI_SO" is is not us",
- pri_so(c->negotiating_ike_sa));
+ "revival: skipping, .routing_sa "PRI_SO" is is not us",
+ pri_so(c->routing_sa));
return false;
}
+ if (c->negotiating_ike_sa != SOS_NOBODY &&
+ c->negotiating_ike_sa != ike->sa.st_serialno) {
+ /*
+ * For instance, crossing stream establishes IKE SA,
+ * but some other Child SA, leaving .routing_sa
+ * hanging.
+ */
+ ldbg(ike->sa.logger, "revival: .negotiating_ike_sa "PRI_SO" is is not us",
+ pri_so(c->negotiating_ike_sa));
+ }
if (c->established_ike_sa != SOS_NOBODY &&
c->established_ike_sa != ike->sa.st_serialno) {
- /* should be covered by above */
- llog_pexpect(ike->sa.logger, HERE,
- "revival: skipping, .established_ike_sa "PRI_SO" is is not us",
- pri_so(c->established_ike_sa));
- return false;
+ ldbg(ike->sa.logger, "revival: .established_ike_sa "PRI_SO" is is not us",
+ pri_so(c->established_ike_sa));
}
return scheduled_revival(c, &ike->sa, subplot, ike->sa.logger);
}
--
2.48.1
From c70b4bf00dee27d35a438bdac9ed78ea76127384 Mon Sep 17 00:00:00 2001
From: Andrew Cagney <cagney@gnu.org>
Date: Thu, 16 Jan 2025 17:22:09 -0500
Subject: [PATCH 3/4] testing: add WIP
crossing-streams-23-conswitch-ike-sa-init-ikev2, see #1989
---
testing/pluto/TESTLIST | 1 +
.../01-east-init.sh | 11 ++
.../02-west-init.sh | 9 ++
.../03-west-ike-sa-init.sh | 13 +++
.../04-east-establish.sh | 6 ++
.../05-west-crossing-stream.sh | 9 ++
.../description.txt | 34 ++++++
.../east.console.txt | 74 +++++++++++++
.../final.sh | 6 ++
.../ipsec.conf | 32 ++++++
.../ipsec.secrets | 1 +
.../west.console.txt | 101 ++++++++++++++++++
12 files changed, 297 insertions(+)
create mode 100755 testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/01-east-init.sh
create mode 100755 testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/02-west-init.sh
create mode 100644 testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/03-west-ike-sa-init.sh
create mode 100644 testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/04-east-establish.sh
create mode 100644 testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/05-west-crossing-stream.sh
create mode 100644 testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/description.txt
create mode 100644 testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/east.console.txt
create mode 100755 testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/final.sh
create mode 100644 testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/ipsec.conf
create mode 100644 testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/ipsec.secrets
create mode 100644 testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/west.console.txt
diff --git a/testing/pluto/TESTLIST b/testing/pluto/TESTLIST
index 49d65fb534..24697f3786 100644
--- a/testing/pluto/TESTLIST
+++ b/testing/pluto/TESTLIST
@@ -1562,6 +1562,7 @@ kvmplutotest crossing-streams-20-peer-restarts-ondemand-ike-auth good github/163
kvmplutotest crossing-streams-21-second-child-crypto-ikev2 good
kvmplutotest crossing-streams-22-ikev2-ipsec-interface wip github:557
+kvmplutotest crossing-streams-23-conswitch-ike-sa-init-ikev2 wip github/1989
# uses the kernel-pfkeyv2.c code
diff --git a/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/01-east-init.sh b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/01-east-init.sh
new file mode 100755
index 0000000000..5eac31179d
--- /dev/null
+++ b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/01-east-init.sh
@@ -0,0 +1,11 @@
+/testing/guestbin/swan-prep --nokey
+
+../../guestbin/ifconfig.sh eth0 add 192.0.20.254/24
+
+ipsec start
+../../guestbin/wait-until-pluto-started
+
+ipsec whack --impair suppress_retransmits
+
+ipsec add a
+ipsec add b
diff --git a/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/02-west-init.sh b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/02-west-init.sh
new file mode 100755
index 0000000000..3690498351
--- /dev/null
+++ b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/02-west-init.sh
@@ -0,0 +1,9 @@
+/testing/guestbin/swan-prep --nokey
+ipsec start
+../../guestbin/wait-until-pluto-started
+
+ipsec whack --impair suppress_retransmits
+
+# note order
+ipsec add a
+ipsec add b
diff --git a/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/03-west-ike-sa-init.sh b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/03-west-ike-sa-init.sh
new file mode 100644
index 0000000000..65837088f5
--- /dev/null
+++ b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/03-west-ike-sa-init.sh
@@ -0,0 +1,13 @@
+# Initiate "a" with all packets blocked.
+#
+# This will create the negotiating IKE SA #1, and then hang.
+
+ipsec whack --impair block_outbound:yes
+ipsec up a --asynchronous
+../../guestbin/wait-for-pluto.sh --match '"a" #1: IMPAIR: blocking outbound message 1'
+../../guestbin/wait-for-pluto.sh --match '"a" #1: sent IKE_SA_INIT request'
+
+# With connection "a"'s IKE SA #1 stuck, unblock so that the peer's
+# IKE SA #2, which will cross "a", can establish
+
+ipsec whack --impair block_outbound:no
diff --git a/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/04-east-establish.sh b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/04-east-establish.sh
new file mode 100644
index 0000000000..668613881b
--- /dev/null
+++ b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/04-east-establish.sh
@@ -0,0 +1,6 @@
+# Initiate "b" from EAST.
+#
+# On WEST this is will match and establish connection "a" with IKE SA
+# #2, and then switch to connection "b" for Child SA #3.
+
+ipsec up b
diff --git a/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/05-west-crossing-stream.sh b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/05-west-crossing-stream.sh
new file mode 100644
index 0000000000..dc8387a82f
--- /dev/null
+++ b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/05-west-crossing-stream.sh
@@ -0,0 +1,9 @@
+# confirm that the peer's IKE SA established "a", and then the peer's
+# Child SA needed to switch to "b" before establishing.
+
+../../guestbin/wait-for-pluto.sh --match '"a" #2: responder established IKE SA'
+../../guestbin/wait-for-pluto.sh --match '"a" #3: switched to "b"'
+../../guestbin/wait-for-pluto.sh --match '"b" #3: responder established Child SA using #2'
+
+# this is where pluto realises that the stream crossed
+../../guestbin/wait-for-pluto.sh --match '#1: dropping negotiation'
diff --git a/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/description.txt b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/description.txt
new file mode 100644
index 0000000000..52806b90d5
--- /dev/null
+++ b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/description.txt
@@ -0,0 +1,34 @@
+IKE exchange crosses, initiator conswitches peer's child leaving local ike hanging
+
+each end has two connections that can share their IKE SA
+
+- WEST sends "a":IKE_SA_INIT to EAST
+ - creates IKE SA #1
+ - initiator sets "a".{routing,negotiating}_ike_sa to #1
+
+- EAST sends "b":IKE_SA_INIT to WEST
+
+- WEST responds to EAST'S "b":IKE_SA_INIT
+ - creates IKE_SA #2 using connection "a"
+ - responder leaves "a".{routing,negotiating}_ike_sa et.al. untouched
+
+- EAST sends "b":IKE_AUTH to WEST
+
+- WEST responds to EAST's "b":IKE_AUTH
+ IKE
+ - establishes IKE SA #2
+ - sets "a".{established,negotiating}_ike_sa #2
+ - leaves "a".routing_sa set to #1
+ Child:
+ - creates Child SA #3
+ - connswitches Child SA #3 to "b"
+ - establishes Child SA #3
+ - sets "b".{routing,negotiating,established}_child_sa to #3
+
+- WEST retransmits "a":IKE_SA_INIT #1 to EAST
+ - sees .established_ike_sa(?) is #2 stops negotiation
+ "tun-out-1" #1: suppressing retransmit because IKE SA was superseded #2; drop this negotiation
+ - tries to revive:
+ EXPECTATION FAILED: "a" #1: revival: skipping, .negotiating_ike_sa #2 is is not us
+
+ref github #1989
diff --git a/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/east.console.txt b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/east.console.txt
new file mode 100644
index 0000000000..d386cabf02
--- /dev/null
+++ b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/east.console.txt
@@ -0,0 +1,74 @@
+/testing/guestbin/swan-prep --nokey
+Creating empty NSS database
+east #
+ ../../guestbin/ifconfig.sh eth0 add 192.0.20.254/24
+ inet 192.0.20.254/24 scope global eth0
+east #
+ ipsec start
+Redirecting to: [initsystem]
+east #
+ ../../guestbin/wait-until-pluto-started
+east #
+ ipsec whack --impair suppress_retransmits
+east #
+ ipsec add a
+"a": added IKEv2 connection
+east #
+ ipsec add b
+"b": added IKEv2 connection
+east #
+ # Initiate "b" from EAST.
+east #
+ #
+east #
+ # On WEST this is will match and establish connection "a" with IKE SA
+east #
+ # #2, and then switch to connection "b" for Child SA #3.
+east #
+ ipsec up b
+"b" #1: initiating IKEv2 connection to 192.1.2.45 using UDP
+"b" #1: sent IKE_SA_INIT request to 192.1.2.45:UDP/500
+"b" #1: processed IKE_SA_INIT response from 192.1.2.45:UDP/500 {cipher=AES_GCM_16_256 integ=n/a prf=HMAC_SHA2_512 group=DH19}, initiating IKE_AUTH
+"b" #1: sent IKE_AUTH request to 192.1.2.45:UDP/500
+"b" #1: initiator established IKE SA; authenticated peer using authby=secret and ID_FQDN '@west'
+"b" #2: initiator established Child SA using #1; IPsec tunnel [192.0.20.0/24===192.0.3.0/24] {ESP/ESN=>0xESPESP <0xESPESP xfrm=AES_GCM_16_256-NONE DPD=passive}
+east #
+ # non-zero counts confirm encrypted traffic flowing
+east #
+ ipsec trafficstatus
+#2: "b", type=ESP, add_time=1234567890, inBytes=0, outBytes=0, maxBytes=2^63B, id='@west'
+east #
+ # do things line up?
+east #
+ ../../guestbin/ipsec-kernel-state.sh
+src 192.1.2.23 dst 192.1.2.45
+ proto esp spi 0xSPISPI reqid REQID mode tunnel
+ replay-window 0 flag af-unspec esn
+ aead rfc4106(gcm(aes)) 0xENCAUTHKEY 128
+ anti-replay esn context:
+ seq-hi 0x0, seq 0xXX, oseq-hi 0x0, oseq 0xXX
+ replay_window 0, bitmap-length 0
+src 192.1.2.45 dst 192.1.2.23
+ proto esp spi 0xSPISPI reqid REQID mode tunnel
+ replay-window 0 flag af-unspec esn
+ aead rfc4106(gcm(aes)) 0xENCAUTHKEY 128
+ anti-replay esn context:
+ seq-hi 0x0, seq 0xXX, oseq-hi 0x0, oseq 0xXX
+ replay_window 128, bitmap-length 4
+ 00000000 00000000 00000000 XXXXXXXX
+east #
+ ../../guestbin/ipsec-kernel-policy.sh
+src 192.0.3.0/24 dst 192.0.20.0/24
+ dir fwd priority PRIORITY ptype main
+ tmpl src 192.1.2.45 dst 192.1.2.23
+ proto esp reqid REQID mode tunnel
+src 192.0.3.0/24 dst 192.0.20.0/24
+ dir in priority PRIORITY ptype main
+ tmpl src 192.1.2.45 dst 192.1.2.23
+ proto esp reqid REQID mode tunnel
+src 192.0.20.0/24 dst 192.0.3.0/24
+ dir out priority PRIORITY ptype main
+ tmpl src 192.1.2.23 dst 192.1.2.45
+ proto esp reqid REQID mode tunnel
+east #
+
diff --git a/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/final.sh b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/final.sh
new file mode 100755
index 0000000000..c84a482b6a
--- /dev/null
+++ b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/final.sh
@@ -0,0 +1,6 @@
+# non-zero counts confirm encrypted traffic flowing
+ipsec trafficstatus
+
+# do things line up?
+../../guestbin/ipsec-kernel-state.sh
+../../guestbin/ipsec-kernel-policy.sh
diff --git a/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/ipsec.conf b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/ipsec.conf
new file mode 100644
index 0000000000..cb3d584bc2
--- /dev/null
+++ b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/ipsec.conf
@@ -0,0 +1,32 @@
+config setup
+ logfile=/tmp/pluto.log
+ logtime=no
+ logappend=no
+ dumpdir=/var/tmp
+ plutodebug=all
+ nhelpers=0
+
+conn base
+ keyexchange=ikev2
+ auto=ignore
+ # host
+ left=192.1.2.45
+ right=192.1.2.23
+ # auth
+ leftid=@west
+ rightid=@east
+ authby=secret
+ leftsubnet=192.0.3.0/24
+ retransmit-timeout=10s
+
+conn a
+ also=base
+ # client
+ leftsourceip=192.0.3.253
+ rightsubnet=192.0.2.0/24
+
+conn b
+ also=base
+ # client
+ leftsourceip=192.0.3.254
+ rightsubnet=192.0.20.0/24
diff --git a/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/ipsec.secrets b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/ipsec.secrets
new file mode 100644
index 0000000000..d3ed5698d0
--- /dev/null
+++ b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/ipsec.secrets
@@ -0,0 +1 @@
+@west @east : PSK "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
diff --git a/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/west.console.txt b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/west.console.txt
new file mode 100644
index 0000000000..00c98494e5
--- /dev/null
+++ b/testing/pluto/crossing-streams-23-conswitch-ike-sa-init-ikev2/west.console.txt
@@ -0,0 +1,101 @@
+/testing/guestbin/swan-prep --nokey
+Creating empty NSS database
+west #
+ ipsec start
+Redirecting to: [initsystem]
+west #
+ ../../guestbin/wait-until-pluto-started
+west #
+ ipsec whack --impair suppress_retransmits
+west #
+ # note order
+west #
+ ipsec add a
+"a": added IKEv2 connection
+west #
+ ipsec add b
+"b": added IKEv2 connection
+west #
+ # Initiate "a" with all packets blocked.
+west #
+ #
+west #
+ # This will create the negotiating IKE SA #1, and then hang.
+west #
+ ipsec whack --impair block_outbound:yes
+IMPAIR: recording all outbound messages
+IMPAIR: block all outbound messages: no -> yes
+west #
+ ipsec up a --asynchronous
+"a" #1: initiating IKEv2 connection to 192.1.2.23 using UDP
+west #
+ ../../guestbin/wait-for-pluto.sh --match '"a" #1: IMPAIR: blocking outbound message 1'
+"a" #1: IMPAIR: blocking outbound message 1
+west #
+ ../../guestbin/wait-for-pluto.sh --match '"a" #1: sent IKE_SA_INIT request'
+"a" #1: sent IKE_SA_INIT request to 192.1.2.23:UDP/500
+west #
+ # With connection "a"'s IKE SA #1 stuck, unblock so that the peer's
+west #
+ # IKE SA #2, which will cross "a", can establish
+west #
+ ipsec whack --impair block_outbound:no
+IMPAIR: block all outbound messages: yes -> no
+west #
+ # confirm that the peer's IKE SA established "a", and then the peer's
+west #
+ # Child SA needed to switch to "b" before establishing.
+west #
+ ../../guestbin/wait-for-pluto.sh --match '"a" #2: responder established IKE SA'
+"a" #2: responder established IKE SA; authenticated peer using authby=secret and ID_FQDN '@east'
+west #
+ ../../guestbin/wait-for-pluto.sh --match '"a" #3: switched to "b"'
+"a" #3: switched to "b"
+west #
+ ../../guestbin/wait-for-pluto.sh --match '"b" #3: responder established Child SA using #2'
+"b" #3: responder established Child SA using #2; IPsec tunnel [192.0.3.0/24===192.0.20.0/24] {ESP/ESN=>0xESPESP <0xESPESP xfrm=AES_GCM_16_256-NONE DPD=passive}
+west #
+ # this is where pluto realises that the stream crossed
+west #
+ ../../guestbin/wait-for-pluto.sh --match '#1: dropping negotiation'
+"a" #1: dropping negotiation as superseeded by established IKE SA #2
+west #
+ # non-zero counts confirm encrypted traffic flowing
+west #
+ ipsec trafficstatus
+#3: "b", type=ESP, add_time=1234567890, inBytes=0, outBytes=0, maxBytes=2^63B, id='@east'
+west #
+ # do things line up?
+west #
+ ../../guestbin/ipsec-kernel-state.sh
+src 192.1.2.45 dst 192.1.2.23
+ proto esp spi 0xSPISPI reqid REQID mode tunnel
+ replay-window 0 flag af-unspec esn
+ aead rfc4106(gcm(aes)) 0xENCAUTHKEY 128
+ anti-replay esn context:
+ seq-hi 0x0, seq 0xXX, oseq-hi 0x0, oseq 0xXX
+ replay_window 0, bitmap-length 0
+src 192.1.2.23 dst 192.1.2.45
+ proto esp spi 0xSPISPI reqid REQID mode tunnel
+ replay-window 0 flag af-unspec esn
+ aead rfc4106(gcm(aes)) 0xENCAUTHKEY 128
+ anti-replay esn context:
+ seq-hi 0x0, seq 0xXX, oseq-hi 0x0, oseq 0xXX
+ replay_window 128, bitmap-length 4
+ 00000000 00000000 00000000 XXXXXXXX
+west #
+ ../../guestbin/ipsec-kernel-policy.sh
+src 192.0.3.0/24 dst 192.0.20.0/24
+ dir out priority PRIORITY ptype main
+ tmpl src 192.1.2.45 dst 192.1.2.23
+ proto esp reqid REQID mode tunnel
+src 192.0.20.0/24 dst 192.0.3.0/24
+ dir fwd priority PRIORITY ptype main
+ tmpl src 192.1.2.23 dst 192.1.2.45
+ proto esp reqid REQID mode tunnel
+src 192.0.20.0/24 dst 192.0.3.0/24
+ dir in priority PRIORITY ptype main
+ tmpl src 192.1.2.23 dst 192.1.2.45
+ proto esp reqid REQID mode tunnel
+west #
+
--
2.48.1
From ee7a62dc7f400f4b830e64e0385a6d16fda234b3 Mon Sep 17 00:00:00 2001
From: Andrew Cagney <cagney@gnu.org>
Date: Mon, 6 Jan 2025 15:38:03 -0500
Subject: [PATCH 4/4] ikev2: in event_v2_retransmit() check .established_ike_sa
!= SOS_NOBODY
That is:
!IS_IKE_SA_ESTABLISHED(&ike->sa) && c->established_ike_sa != SOS_NOBODY
when the streams cross, the etablished IKE SA may be older.
---
programs/pluto/ikev2_retransmit.c | 32 ++++++++++++++++++++-----------
1 file changed, 21 insertions(+), 11 deletions(-)
diff --git a/programs/pluto/ikev2_retransmit.c b/programs/pluto/ikev2_retransmit.c
index df6ab99e63..7b0197445c 100644
--- a/programs/pluto/ikev2_retransmit.c
+++ b/programs/pluto/ikev2_retransmit.c
@@ -49,25 +49,35 @@ void event_v2_retransmit(struct state *ike_sa, monotime_t now UNUSED)
return;
}
- /* if this connection has a newer Child SA than this state
- * this negotiation is not relevant any more. would this
- * cover if there are multiple CREATE_CHILD_SA pending on this
- * IKE negotiation ???
- *
+ /*
* XXX: Suspect this is to handle a race where the other end
* brings up the connection first? For that case, shouldn't
* this state have been deleted?
*
- * NOTE: a larger serialno does not mean superseded. crossed
+ * NOTE: a larger serialno does not mean superseded. Crossed
* streams could mean the lower serial established later and
- * is the "newest". Should > be replaced with != ?
+ * is the "newest". Hence the equality check (and not >).
*/
struct connection *c = ike->sa.st_connection;
- if (!IS_IKE_SA_ESTABLISHED(&ike->sa) && c->established_ike_sa > ike->sa.st_serialno) {
- llog_sa(RC_LOG, ike,
- "suppressing retransmit because IKE SA was superseded #%lu; drop this negotiation",
- c->established_ike_sa);
+ if (!IS_IKE_SA_ESTABLISHED(&ike->sa) && c->established_ike_sa != SOS_NOBODY) {
+ /*
+ * The connection is established, yet this IKE SA is
+ * not. Presumably this means that the peer also
+ * initiated and established an IKE SA leaving this
+ * IKE SA in limbo.
+ *
+ * Note: since it isn't established it can't be the
+ * connection's established IKE SA.
+ *
+ * Note: this may also leave the Child SA for the
+ * connection in limbo. Hopefully revival code will
+ * pick that up.
+ */
+ PEXPECT(ike->sa.logger, c->established_ike_sa != ike->sa.st_serialno);
+ llog(RC_LOG, ike->sa.logger,
+ "suppressing retransmit because IKE SA was superseded by #%lu; drop this negotiation",
+ c->established_ike_sa);
pstat_sa_failed(&ike->sa, REASON_SUPERSEDED_BY_NEW_SA);
connection_delete_ike_family(&ike, HERE);
return;
--
2.48.1