diff --git a/libreswan-5.1-pexpect-negotiating-ike-sa.patch b/libreswan-5.1-pexpect-negotiating-ike-sa.patch new file mode 100644 index 0000000..c991e81 --- /dev/null +++ b/libreswan-5.1-pexpect-negotiating-ike-sa.patch @@ -0,0 +1,737 @@ +From d421aeb263d81bacb92e392c65910054f71f9843 Mon Sep 17 00:00:00 2001 +From: Andrew Cagney +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 +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 +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 +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 + diff --git a/libreswan.spec b/libreswan.spec index c3c3091..4816959 100644 --- a/libreswan.spec +++ b/libreswan.spec @@ -47,6 +47,7 @@ Source5: https://download.libreswan.org/cavs/ikev2.fax.bz2 Patch1: libreswan-4.15-ipsec_import.patch Patch2: libreswan-5.1-rereadsecrets.patch Patch3: libreswan-5.1-opt-protoport.patch +Patch4: libreswan-5.1-pexpect-negotiating-ike-sa.patch BuildRequires: audit-libs-devel BuildRequires: bison