[Swan-dev] pluto: Add RFC7383 fragmentation support

Herbert Xu herbert at gondor.apana.org.au
Thu Apr 30 15:34:57 EEST 2015


This patch adds RFC7383 IKEv2 fragmentation support to pluto.

Signed-off-by: Herbert Xu <herbert at gondor.apana.org.au>

diff --git a/include/ietf_constants.h b/include/ietf_constants.h
index e56683d..95fb27a 100644
--- a/include/ietf_constants.h
+++ b/include/ietf_constants.h
@@ -529,6 +529,11 @@ enum next_payload_types_ikev2 {
 	ISAKMP_NEXT_v2SK = 46,	/* Encrypted payload */
 	ISAKMP_NEXT_v2CP = 47,	/* Configuration Payload (MODECFG) */
 	ISAKMP_NEXT_v2EAP = 48,	/* Extensible Authentication Payload */
+	ISAKMP_NEXT_v2GSPM = 49,
+	ISAKMP_NEXT_v2IDG = 50,
+	ISAKMP_NEXT_v2GSA = 51,
+	ISAKMP_NEXT_v2KD = 52,
+	ISAKMP_NEXT_v2SKF = 53,	/* Encrypted fragment */
 	/* 128 - 255 Private Use */
 	/* Cisco/Microsoft proprietary IKE fragmentation - private use for libreswan */
 	ISAKMP_NEXT_v2IKE_FRAGMENTATION = 132,
@@ -1415,8 +1420,14 @@ typedef enum {
 	v2N_IKEV2_MESSAGE_ID_SYNC = 16422, /* RFC-6311 */
 	v2N_IPSEC_REPLAY_COUNTER_SYNC = 16423, /* RFC-6311 */
 	v2N_SECURE_PASSWORD_METHODS = 16424, /* RFC-6467 */
-
-	/* 16425 - 40969 Unassigned */
+	v2N_PSK_PERSIST = 16425,
+	v2N_PSK_CONFIRM = 16426,
+	v2N_ERX_SUPPORTED = 16427,
+	v2N_IFOM_CAPABILITY = 16428,
+	v2N_SENDER_REQUEST_ID = 16429,
+	v2N_FRAGMENTATION_SUPPORTED = 16430, /* RFC-7383 */
+
+	/* 16431 - 40969 Unassigned */
 	/* 40960 - 65535 Private Use */
 } v2_notification_t;
 
diff --git a/lib/libswan/constants.c b/lib/libswan/constants.c
index 33d2143..d8d4c2a 100644
--- a/lib/libswan/constants.c
+++ b/lib/libswan/constants.c
@@ -286,6 +286,11 @@ const char *const payload_name_ikev2_main[] = {
 	"ISAKMP_NEXT_v2SK",
 	"ISAKMP_NEXT_v2CP",
 	"ISAKMP_NEXT_v2EAP",
+	"ISAKMP_NEXT_v2GSPM",
+	"ISAKMP_NEXT_v2IDG",
+	"ISAKMP_NEXT_v2GSA",
+	"ISAKMP_NEXT_v2KD",
+	"ISAKMP_NEXT_v2SKF",
 	NULL	/* termination for bitnamesof() */
 };
 
@@ -306,7 +311,7 @@ static enum_names payload_names_ikev2_private_use = {
 
 static enum_names payload_names_ikev2_main = {
 	ISAKMP_NEXT_v2SA,
-	ISAKMP_NEXT_v2EAP,
+	ISAKMP_NEXT_v2SKF,
 	payload_name_ikev2_main,
 	&payload_names_ikev2_private_use
 };
@@ -321,7 +326,7 @@ enum_names ikev2_payload_names = {
 /* either V1 or V2 payload kind */
 static enum_names payload_names_ikev2copy_main = {
 	ISAKMP_NEXT_v2SA,
-	ISAKMP_NEXT_v2EAP,
+	ISAKMP_NEXT_v2SKF,
 	payload_name_ikev2_main,
 	&payload_names_ikev1_private_use
 };
@@ -1603,12 +1608,18 @@ static const char *const ikev2_notify_name_16384[] = {
 	"v2N_IPSEC_REPLAY_COUNTER_SYNC_SUPPORTED",
 	"v2N_IKEV2_MESSAGE_ID_SYNC",
 	"v2N_IPSEC_REPLAY_COUNTER_SYNC",
-	"v2N_SECURE_PASSWORD_METHODS",    /* 16423 */
+	"v2N_SECURE_PASSWORD_METHODS",
+	"v2N_PSK_PERSIST",
+	"v2N_PSK_CONFIRM",
+	"v2N_ERX_SUPPORTED",
+	"v2N_IFOM_CAPABILITY",
+	"v2N_SENDER_REQUEST_ID",
+	"v2N_FRAGMENTATION_SUPPORTED",    /* 16430 */
 };
 
 static enum_names ikev2_notify_names_16384 = {
 	v2N_INITIAL_CONTACT,
-	v2N_SECURE_PASSWORD_METHODS,
+	v2N_FRAGMENTATION_SUPPORTED,
 	ikev2_notify_name_16384,
 	NULL
 };
diff --git a/programs/pluto/ikev2.c b/programs/pluto/ikev2.c
index fcd094f..d1f4c68 100644
--- a/programs/pluto/ikev2.c
+++ b/programs/pluto/ikev2.c
@@ -566,6 +566,7 @@ struct ikev2_payloads_summary ikev2_decode_payloads(struct msg_digest *md,
 
 		switch (np) {
 		case ISAKMP_NEXT_v2SK:
+		case ISAKMP_NEXT_v2SKF:
 			/* RFC 5996 2.14 "Encrypted Payload":
 			 *
 			 * Next Payload - The payload type of the
@@ -635,6 +636,108 @@ void ikev2_log_payload_errors(struct ikev2_payload_errors errors)
 	}
 }
 
+static bool ikev2_check_fragment(struct msg_digest *md)
+{
+	struct state *st = md->st;
+	struct ikev2_skf *skf = &md->chain[ISAKMP_NEXT_v2SKF]->payload.v2skf;
+	struct ikev2_frag *i;
+
+	if (!(st->st_connection->policy & POLICY_IKE_FRAG_ALLOW)) {
+		libreswan_log("discarding IKE encrypted fragment - fragmentation not allowed by local policy (ike_frag=no)");
+		return TRUE;
+	}
+
+	DBG(DBG_CONTROL,
+	    DBG_log("received IKE encrypted fragment number '%u', total number '%u', next payload '%u'",
+		    skf->isaskf_number, skf->isaskf_total, skf->isaskf_np));
+
+	if (!skf->isaskf_number || !skf->isaskf_total ||
+	    skf->isaskf_number > skf->isaskf_total ||
+	    (skf->isaskf_number == 1 ? !skf->isaskf_np : skf->isaskf_np)) {
+		libreswan_log("Ignoring invalid IKE encrypted fragment");
+		return TRUE;
+	}
+
+	for (i = st->ikev2_frags; i; i = i->next) {
+		if (i->index != skf->isaskf_number)
+			continue;
+
+		if (i->total == skf->isaskf_total) {
+			libreswan_log("Ignoring duplicate IKE encrypted fragment");
+			return TRUE;
+		}
+
+		if (i->total > skf->isaskf_total) {
+			libreswan_log("Ignoring odd IKE encrypted fragment");
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+static bool ikev2_collect_fragment(struct msg_digest *md)
+{
+	struct state *st = md->st;
+	struct ikev2_skf *skf = &md->chain[ISAKMP_NEXT_v2SKF]->payload.v2skf;
+	pb_stream *e_pbs = &md->chain[ISAKMP_NEXT_v2SKF]->pbs;
+	struct ikev2_frag *frag, **i;
+	int num_frags;
+
+	if (ikev2_check_fragment(md))
+		return TRUE;
+
+	frag = alloc_thing(struct ikev2_frag, "ikev2_frag");
+	frag->np = skf->isaskf_np;
+	frag->index = skf->isaskf_number;
+	frag->total = skf->isaskf_total;
+	frag->iv = e_pbs->cur - md->packet_pbs.start;
+	clonetochunk(frag->cipher, md->packet_pbs.start,
+		     e_pbs->roof - md->packet_pbs.start,
+		     "IKEv2 encrypted fragment");
+
+	/* Add the fragment to the state */
+	i = &st->ikev2_frags;
+	if (*i && (*i)->total < skf->isaskf_total)
+		do {
+			struct ikev2_frag *old = *i;
+
+			(*i) = old->next;
+			freeanychunk(old->cipher);
+			pfree(old);
+		} while (*i);
+
+	num_frags = 0;
+	for (;;) {
+		if (frag) {
+			/* Still looking for a place to insert frag */
+			if (!*i || (*i)->index > frag->index) {
+				frag->next = *i;
+				*i = frag;
+				frag = NULL;
+			}
+		}
+
+		if (!*i)
+			break;
+
+		num_frags++;
+
+		i = &(*i)->next;
+	}
+
+	if (num_frags < skf->isaskf_total)
+		return TRUE;
+
+	/* optimize: if receiving fragments, immediately respond with fragments too */
+	st->st_seen_fragments = TRUE;
+
+	DBG(DBG_CONTROL,
+	    DBG_log(" updated IKE fragment state to respond using fragments without waiting for re-transmits"));
+
+	return FALSE;
+}
+
 /*
  * process an input packet, possibly generating a reply.
  *
@@ -822,8 +925,6 @@ void process_v2_packet(struct msg_digest **mdp)
 	struct ikev2_payloads_summary clear_payload_summary = { .status = STF_IGNORE };
 	struct ikev2_payload_errors clear_payload_status = { .status = STF_OK };
 
-	struct ikev2_payloads_summary enc_payload_summary =  { .status = STF_IGNORE };
-	struct ikev2_payload_errors enc_payload_status = { .status = STF_OK };
 
 	for (svm = v2_state_microcode_table; svm->state != STATE_IKEv2_ROOF;
 	     svm++) {
@@ -858,6 +959,17 @@ void process_v2_packet(struct msg_digest **mdp)
 				complete_v2_state_transition(mdp, clear_payload_summary.status);
 				return;
 			}
+
+			if (clear_payload_summary.seen &
+			    LELEM(PINDEX(ISAKMP_NEXT_v2SKF)))
+				clear_payload_summary.seen ^=
+					LELEM(PINDEX(ISAKMP_NEXT_v2SK)) |
+					LELEM(PINDEX(ISAKMP_NEXT_v2SKF));
+			if (clear_payload_summary.repeated &
+			    LELEM(PINDEX(ISAKMP_NEXT_v2SKF)))
+				clear_payload_summary.repeated ^=
+					LELEM(PINDEX(ISAKMP_NEXT_v2SK)) |
+					LELEM(PINDEX(ISAKMP_NEXT_v2SKF));
 		}
 		struct ikev2_payload_errors clear_payload_errors
 			= ikev2_verify_payloads(clear_payload_summary, svm, FALSE);
@@ -866,40 +978,6 @@ void process_v2_packet(struct msg_digest **mdp)
 			clear_payload_status = clear_payload_errors;
 			continue;
 		}
-		/*
-		 * Continue checking by unpacking the SK payload but
-		 * only its ok and there's one to unpack.  Otherwise
-		 * assume its a correct match.
-		 */
-		if (!(svm->flags & SMF2_UNPACK_SK)) {
-			break;
-		}
-		if (!(svm->req_clear_payloads & LELEM(PINDEX(ISAKMP_NEXT_v2SK)))) {
-			break;
-		}
-		if (enc_payload_summary.status != STF_OK) {
-			DBG(DBG_CONTROL,
-			    DBG_log("Unpacking encrypted payload for svm: %s",
-				    svm->story));
-			stf_status status = ikev2_verify_and_decrypt_sk_payload(md);
-			if (status != STF_OK) {
-				complete_v2_state_transition(mdp, status);
-				return;
-			}
-			unsigned np = md->chain[ISAKMP_NEXT_v2SK]->payload.generic.isag_np;
-			enc_payload_summary = ikev2_decode_payloads(md, &md->clr_pbs, np);
-			if (enc_payload_summary.status != STF_OK) {
-				complete_v2_state_transition(mdp, enc_payload_summary.status);
-				return;
-			}
-		}
-		struct ikev2_payload_errors enc_payload_errors
-			= ikev2_verify_payloads(enc_payload_summary, svm, TRUE);
-		if (enc_payload_errors.status != STF_OK) {
-			/* Save this failure for later logging. */
-			enc_payload_status = enc_payload_errors;
-			continue;
-		}
 		/* must be the right state machine entry */
 		break;
 	}
@@ -917,9 +995,6 @@ void process_v2_packet(struct msg_digest **mdp)
 		if (clear_payload_status.status != STF_OK) {
 			ikev2_log_payload_errors(clear_payload_status);
 			complete_v2_state_transition(mdp, clear_payload_status.status);
-		} else if (enc_payload_status.status != STF_OK) {
-			ikev2_log_payload_errors(enc_payload_status);
-			complete_v2_state_transition(mdp, enc_payload_status.status);
 		} else if (!(md->hdr.isa_flags & ISAKMP_FLAGS_v2_MSG_R)) {
 			/*
 			 * We are the responder to this message so
@@ -936,6 +1011,9 @@ void process_v2_packet(struct msg_digest **mdp)
 	if (state_busy(st))
 		return;
 
+	if (md->chain[ISAKMP_NEXT_v2SKF] && ikev2_collect_fragment(md))
+		return;
+
 	DBG(DBG_PARSING, {
 		    if (pbs_left(&md->message_pbs) != 0)
 			    DBG_log("removing %d bytes of padding",
@@ -1368,9 +1446,6 @@ static void success_v2_state_transition(struct msg_digest *md)
 
 	/* if requested, send the new reply packet */
 	if (svm->flags & SMF2_REPLY) {
-		/* free previously transmitted packet */
-		freeanychunk(st->st_tpacket);
-
 		if (nat_traversal_enabled && from_state != STATE_PARENT_I1) {
 			/* adjust our destination port if necessary */
 			nat_traversal_change_port_lookup(md, st);
@@ -1384,11 +1459,6 @@ static void success_v2_state_transition(struct msg_digest *md)
 				    st->st_interface->port);
 		    });
 
-		close_output_pbs(&reply_stream); /* good form, but actually a no-op */
-
-		clonetochunk(st->st_tpacket, reply_stream.start,
-			     pbs_offset(&reply_stream), "reply packet");
-
 		/* actually send the packet
 		 * Note: this is a great place to implement "impairments"
 		 * for testing purposes.  Suppress or duplicate the
diff --git a/programs/pluto/ikev2.h b/programs/pluto/ikev2.h
index 0b9957e..79eff7a 100644
--- a/programs/pluto/ikev2.h
+++ b/programs/pluto/ikev2.h
@@ -198,8 +198,6 @@ void send_v2_notification_invalid_ke_from_state(struct state *st);
 bool modp_in_propset(oakley_group_t received, struct alg_info_ike *ai_list);
 oakley_group_t first_modp_from_propset(struct alg_info_ike *ai_list);
 
-stf_status ikev2_verify_and_decrypt_sk_payload(struct msg_digest *md);
-
 struct ikev2_payloads_summary {
 	stf_status status;
 	lset_t seen;
diff --git a/programs/pluto/ikev2_parent.c b/programs/pluto/ikev2_parent.c
index 95803a3..a5c6e7c 100644
--- a/programs/pluto/ikev2_parent.c
+++ b/programs/pluto/ikev2_parent.c
@@ -495,6 +495,17 @@ static stf_status ikev2_parent_outI1_common(struct msg_digest *md,
 		close_output_pbs(&pb);
 	}
 
+	/* Send fragmentation support notification */
+	if (c->policy & POLICY_IKE_FRAG_ALLOW) {
+		int np = ISAKMP_NEXT_v2N;
+
+		if (!ship_v2N(np, ISAKMP_PAYLOAD_NONCRITICAL,
+			      PROTO_v2_RESERVED, &empty_chunk,
+			      v2N_FRAGMENTATION_SUPPORTED, &empty_chunk,
+			      &md->rbody))
+			return STF_INTERNAL_ERROR;
+	}
+
 	/* Send NAT-T Notify payloads */
 	{
 		int np = c->send_vendorid ? ISAKMP_NEXT_v2V : ISAKMP_NEXT_v2NONE;
@@ -525,6 +536,7 @@ static stf_status ikev2_parent_outI1_common(struct msg_digest *md,
 	clonetochunk(st->st_tpacket, reply_stream.start,
 		     pbs_offset(&reply_stream),
 		     "reply packet for ikev2_parent_outI1_tail");
+	release_v2fragments(&st->st_tfrags);
 
 	/* save packet for later signing */
 	freeanychunk(st->st_firstpacket_me);
@@ -538,6 +550,26 @@ static stf_status ikev2_parent_outI1_common(struct msg_digest *md,
 	return STF_OK;
 }
 
+static void ikev2_check_frag_support(struct msg_digest *md)
+{
+	struct payload_digest *p;
+	struct state *st = md->st;
+
+	passert(st);
+
+	for (p = md->chain[ISAKMP_NEXT_v2N]; p != NULL; p = p->next) {
+		switch (p->payload.v2n.isan_type) {
+		case v2N_FRAGMENTATION_SUPPORTED:
+			st->st_seen_fragvid = TRUE;
+			break;
+		default:
+			continue;
+		}
+
+		break;
+	}
+}
+
 /*
  *
  ***************************************************************
@@ -788,8 +820,10 @@ stf_status ikev2parent_inI1outR1(struct msg_digest *md)
 	 * check v2N_NAT_DETECTION_DESTINATION_IP or/and
 	 * v2N_NAT_DETECTION_SOURCE_IP
 	 */
-	if (md->chain[ISAKMP_NEXT_v2N] != NULL)
+	if (md->chain[ISAKMP_NEXT_v2N] != NULL) {
 		ikev2_natd_lookup(md, zero_cookie);
+		ikev2_check_frag_support(md);
+	}
 
 	/* calculate the nonce and the KE */
 	{
@@ -1008,6 +1042,17 @@ static stf_status ikev2_parent_inI1outR1_tail(
 			!has_preloaded_public_key(st);
 	}
 
+	/* Send fragmentation support notification */
+	if (c->policy & POLICY_IKE_FRAG_ALLOW) {
+		int np = ISAKMP_NEXT_v2N;
+
+		if (!ship_v2N(np, ISAKMP_PAYLOAD_NONCRITICAL,
+			      PROTO_v2_RESERVED, &empty_chunk,
+			      v2N_FRAGMENTATION_SUPPORTED, &empty_chunk,
+			      &md->rbody))
+			return STF_INTERNAL_ERROR;
+	}
+
 	/* Send NAT-T Notify payloads */
 	{
 		struct ikev2_generic in;
@@ -1046,6 +1091,7 @@ static stf_status ikev2_parent_inI1outR1_tail(
 	clonetochunk(st->st_tpacket, reply_stream.start,
 		     pbs_offset(&reply_stream),
 		     "reply packet for ikev2_parent_inI1outR1_tail");
+	release_v2fragments(&st->st_tfrags);
 
 	/* save packet for later signing */
 	freeanychunk(st->st_firstpacket_me);
@@ -1208,6 +1254,7 @@ stf_status ikev2parent_inR1outI2(struct msg_digest *md)
 		case v2N_USE_TRANSPORT_MODE:
 		case v2N_NAT_DETECTION_SOURCE_IP:
 		case v2N_NAT_DETECTION_DESTINATION_IP:
+		case v2N_FRAGMENTATION_SUPPORTED:
 			/* we do handle these further down */
 			break;
 		default:
@@ -1255,8 +1302,10 @@ stf_status ikev2parent_inR1outI2(struct msg_digest *md)
 	/* check v2N_NAT_DETECTION_DESTINATION_IP or/and
 	 * v2N_NAT_DETECTION_SOURCE_IP
 	 */
-	if (md->chain[ISAKMP_NEXT_v2N] != NULL)
+	if (md->chain[ISAKMP_NEXT_v2N] != NULL) {
 		ikev2_natd_lookup(md, st->st_rcookie);
+		ikev2_check_frag_support(md);
+	}
 
 	/* initiate calculation of g^xy */
 	return start_dh_v2(md, "ikev2_inR1outI2 KE", ORIGINAL_INITIATOR,
@@ -1540,15 +1589,15 @@ static stf_status ikev2_encrypt_msg(struct state *st,
  * the actual starting-variable (a.k.a. IV).
  */
 
-stf_status ikev2_verify_and_decrypt_sk_payload(struct msg_digest *md)
+static stf_status ikev2_verify_and_decrypt_sk_payload(struct msg_digest *md,
+						      struct chunk *chunk,
+						      unsigned int iv)
 {
 	/* caller should be passing in the original (parent) state. */
 	struct state *st = md->st;
 	struct state *pst = IS_CHILD_SA(st) ?
 		state_with_serialno(st->st_clonedfrom) : st;
 
-	pb_stream *e_pbs = &md->chain[ISAKMP_NEXT_v2SK]->pbs;
-
 	if (st != NULL && !st->hidden_variables.st_skeyid_calculated)
 	{
 		DBG(DBG_CRYPT | DBG_CONTROL, {
@@ -1563,7 +1612,7 @@ stf_status ikev2_verify_and_decrypt_sk_payload(struct msg_digest *md)
 		return STF_FAIL;
 	}
 
-	u_char *wire_iv_start = e_pbs->cur;
+	u_char *wire_iv_start = chunk->ptr + iv;
 	size_t wire_iv_size = pst->st_oakley.encrypter->wire_iv_size;
 	size_t integ_size = (ike_alg_enc_requires_integ(pst->st_oakley.encrypter)
 			     ? pst->st_oakley.integ_hasher->hash_integ_len
@@ -1576,14 +1625,14 @@ stf_status ikev2_verify_and_decrypt_sk_payload(struct msg_digest *md)
 	 * - at least one padding-length byte
 	 * - truncated integrity digest / tag
 	 */
-	u_char *payload_end = e_pbs->roof;
+	u_char *payload_end = chunk->ptr + chunk->len;
 	if (payload_end < (wire_iv_start + wire_iv_size + 1 + integ_size)) {
 		libreswan_log("encrypted payload impossibly short (%zu)",
 			      payload_end - wire_iv_start);
 		return STF_FAIL;
 	}
 
-	u_char *auth_start = md->packet_pbs.start;
+	u_char *auth_start = chunk->ptr;
 	u_char *enc_start = wire_iv_start + wire_iv_size;
 	u_char *integ_start = payload_end - integ_size;
 	size_t enc_size = integ_start - enc_start;
@@ -1705,7 +1754,57 @@ stf_status ikev2_verify_and_decrypt_sk_payload(struct msg_digest *md)
 	/* don't bother to check any other pad octets */
 	DBG(DBG_CRYPT, DBG_log("striping %u bytes as pad", padlen));
 
-	init_pbs(&md->clr_pbs, enc_start, enc_size - padlen, "cleartext");
+	setchunk(*chunk, enc_start, enc_size - padlen);
+	return STF_OK;
+}
+
+static stf_status ikev2_reassemble_fragments(struct msg_digest *md,
+					     struct chunk *chunk)
+{
+	struct state *st = md->st;
+	struct ikev2_frag *frag;
+	stf_status status;
+	unsigned int size;
+	unsigned int offset;
+
+	size = 0;
+	for (frag = st->ikev2_frags; frag; frag = frag->next) {
+		setchunk(frag->plain, frag->cipher.ptr, frag->cipher.len);
+
+		status = ikev2_verify_and_decrypt_sk_payload(
+			md, &frag->plain, frag->iv);
+		if (status != STF_OK) {
+			release_v2fragments(&st->ikev2_frags);
+			return status;
+		}
+
+		size += frag->plain.len;
+	}
+
+	/* We have all the fragments */
+	md->raw_packet.ptr = alloc_bytes(size, "IKE fragments buffer");
+
+	/* Reassemble fragments in buffer */
+	frag = st->ikev2_frags;
+	md->chain[ISAKMP_NEXT_v2SKF]->payload.v2skf.isaskf_np = frag->np;
+	offset = 0;
+	do {
+		struct ikev2_frag *old = frag;
+
+		passert(offset + frag->plain.len <= size);
+		memcpy(md->raw_packet.ptr + offset, frag->plain.ptr,
+		       frag->plain.len);
+		offset += frag->plain.len;
+		frag = frag->next;
+
+		freeanychunk(old->cipher);
+		pfree(old);
+	} while (frag);
+
+	st->ikev2_frags = NULL;
+
+	setchunk(*chunk, md->raw_packet.ptr, size);
+
 	return STF_OK;
 }
 
@@ -1713,11 +1812,29 @@ static
 stf_status ikev2_decrypt_msg(struct msg_digest *md)
 {
 	stf_status status;
-	status = ikev2_verify_and_decrypt_sk_payload(md);
+	struct chunk chunk;
+
+	if (md->chain[ISAKMP_NEXT_v2SKF]) {
+		status = ikev2_reassemble_fragments(md, &chunk);
+	} else {
+		pb_stream *e_pbs = &md->chain[ISAKMP_NEXT_v2SK]->pbs;
+
+		setchunk(chunk, md->packet_pbs.start,
+			 e_pbs->roof - md->packet_pbs.start);
+
+		status = ikev2_verify_and_decrypt_sk_payload(
+			md, &chunk, e_pbs->cur - md->packet_pbs.start);
+	}
+
 	if (status != STF_OK) {
 		return status;
 	}
-	unsigned np = md->chain[ISAKMP_NEXT_v2SK]->payload.generic.isag_np;
+
+	init_pbs(&md->clr_pbs, chunk.ptr, chunk.len, "cleartext");
+
+	unsigned np = md->chain[ISAKMP_NEXT_v2SK] ?
+		      md->chain[ISAKMP_NEXT_v2SK]->payload.generic.isag_np :
+		      md->chain[ISAKMP_NEXT_v2SKF]->payload.v2skf.isaskf_np;
 	struct ikev2_payloads_summary summary = ikev2_decode_payloads(md, &md->clr_pbs, np);
 	if (summary.status != STF_OK) {
 		return status;
@@ -1849,6 +1966,158 @@ static stf_status ikev2_send_auth(struct connection *c,
 	return STF_OK;
 }
 
+static stf_status ikev2_send_fragment(struct msg_digest *md,
+				      struct isakmp_hdr *hdr,
+				      struct ikev2_generic *oe,
+				      struct ikev2_frag **fragp,
+				      struct chunk *payload,
+				      unsigned int count, unsigned int total,
+				      const char *desc)
+{
+	struct state *st = md->st;
+	struct ikev2_skf e;
+	unsigned char *encstart;
+	pb_stream e_pbs, e_pbs_cipher;
+	unsigned char *iv;
+	unsigned char *authstart;
+
+	/* make sure HDR is at start of a clean buffer */
+	zero(&reply_buffer);
+	init_pbs(&reply_stream, reply_buffer, sizeof(reply_buffer),
+		 "reply packet");
+
+	/* beginning of data going out */
+	authstart = reply_stream.cur;
+
+	/* HDR out */
+	{
+		hdr->isa_np = ISAKMP_NEXT_v2SKF;
+
+		if (!out_struct(hdr, &isakmp_hdr_desc, &reply_stream,
+				&md->rbody))
+			return STF_INTERNAL_ERROR;
+	}
+
+	/* insert an Encryption payload header */
+	e.isaskf_np = count == 1 ? oe->isag_np : 0;
+	e.isaskf_critical = oe->isag_critical;
+	e.isaskf_number = count;
+	e.isaskf_total = total;
+
+	if (!out_struct(&e, &ikev2_skf_desc, &md->rbody, &e_pbs))
+		return STF_INTERNAL_ERROR;
+
+	/* insert IV */
+	iv = e_pbs.cur;
+	if (!emit_wire_iv(st, &e_pbs))
+		return STF_INTERNAL_ERROR;
+
+	/* note where cleartext starts */
+	init_pbs(&e_pbs_cipher, e_pbs.cur, e_pbs.roof - e_pbs.cur,
+		 "cleartext");
+	e_pbs_cipher.container = &e_pbs;
+	e_pbs_cipher.desc = NULL;
+	e_pbs_cipher.cur = e_pbs.cur;
+	encstart = e_pbs_cipher.cur;
+
+	if (!out_raw(payload->ptr, payload->len, &e_pbs_cipher,
+		     "cleartext fragment"))
+		return STF_INTERNAL_ERROR;
+
+	/*
+	 * need to extend the packet so that we will know how big it is
+	 * since the length is under the integrity check
+	 */
+	if (!ikev2_padup_pre_encrypt(st, &e_pbs_cipher))
+		return STF_INTERNAL_ERROR;
+
+	close_output_pbs(&e_pbs_cipher);
+
+	{
+		unsigned char *authloc = ikev2_authloc(st, &e_pbs);
+		int ret;
+
+		if (authloc == NULL)
+			return STF_INTERNAL_ERROR;
+
+		close_output_pbs(&e_pbs);
+		close_output_pbs(&md->rbody);
+		close_output_pbs(&reply_stream);
+
+		ret = ikev2_encrypt_msg(st, authstart,
+					iv, encstart, authloc,
+					&e_pbs, &e_pbs_cipher);
+		if (ret != STF_OK)
+			return ret;
+	}
+
+	*fragp = alloc_thing(struct ikev2_frag, "ikev2_frag");
+	clonetochunk((*fragp)->cipher, reply_stream.start,
+		     pbs_offset(&reply_stream), desc);
+
+	return STF_OK;
+}
+
+static stf_status ikev2_send_fragments(struct msg_digest *md,
+				       struct isakmp_hdr *hdr,
+				       struct ikev2_generic *e,
+				       struct chunk *payload,
+				       const char *desc)
+{
+	struct state *st = md->st;
+	unsigned int len;
+	unsigned int offset;
+	unsigned int total;
+	unsigned int count;
+	struct ikev2_frag **fragp;
+	int ret;
+
+	len = (st->st_connection->addr_family == AF_INET) ?
+	      ISAKMP_FRAG_MAXLEN_IPv4 : ISAKMP_FRAG_MAXLEN_IPv6;
+
+	if (st->st_interface && st->st_interface->ike_float)
+		len -= NON_ESP_MARKER_SIZE;
+
+	len -= NSIZEOF_isakmp_hdr + NSIZEOF_isakmp_ikefrag;
+	len -= ike_alg_enc_requires_integ(st->st_oakley.encrypter) ?
+	       st->st_oakley.integ_hasher->hash_integ_len :
+	       st->st_oakley.encrypter->aead_tag_size;
+
+	if (st->st_oakley.encrypter->pad_to_blocksize)
+		len &= ~(st->st_oakley.encrypter->enc_blocksize - 1);
+
+	len -= 2;
+
+	total = (payload->len - 1) / len + 1;
+	if (total >= 128) {
+		loglog(RC_LOG_SERIOUS, "Too many frags %u %u",
+		       (unsigned int)payload->len, len);
+		return STF_INTERNAL_ERROR;
+	}
+
+	count = 1;
+	offset = 0;
+	fragp = &st->st_tfrags;
+
+	for (;;) {
+		struct chunk cipher;
+
+		if (offset + len > payload->len)
+			len = payload->len - offset;
+		setchunk(cipher, payload->ptr + offset, len);
+		offset += len;
+		ret = ikev2_send_fragment(md, hdr, e, fragp, &cipher,
+					  count++, total, desc);
+
+		if (ret != STF_OK || offset >= payload->len)
+			break;
+
+		fragp = &(*fragp)->next;
+	}
+
+	return ret;
+}
+
 static stf_status ikev2_parent_inR1outI2_tail(
 	struct pluto_crypto_req_cont *dh,
 	struct pluto_crypto_req *r)
@@ -1865,6 +2134,9 @@ static stf_status ikev2_parent_inR1outI2_tail(
 	unsigned char *authstart;
 	struct state *pst = st;
 	bool send_cert = FALSE;
+	struct isakmp_hdr hdr = md->hdr;
+	unsigned char *authloc;
+	unsigned int len;
 
 	finish_dh_v2(st, r);
 
@@ -1906,8 +2178,6 @@ static stf_status ikev2_parent_inR1outI2_tail(
 
 	/* HDR out */
 	{
-		struct isakmp_hdr hdr = md->hdr;
-
 		/* clear all flags, set original initiator */
 		hdr.isa_flags = ISAKMP_FLAGS_v2_IKE_I;
 		hdr.isa_version = build_ikev2_version();
@@ -2073,6 +2343,8 @@ static stf_status ikev2_parent_inR1outI2_tail(
 		}
 	}
 
+	len = pbs_offset(&e_pbs_cipher);
+
 	/*
 	 * need to extend the packet so that we will know how big it is
 	 * since the length is under the integrity check
@@ -2082,15 +2354,29 @@ static stf_status ikev2_parent_inR1outI2_tail(
 
 	close_output_pbs(&e_pbs_cipher);
 
-	{
-		unsigned char *authloc = ikev2_authloc(st, &e_pbs);
+	authloc = ikev2_authloc(st, &e_pbs);
 
-		if (authloc == NULL)
-			return STF_INTERNAL_ERROR;
+	if (authloc == NULL)
+		return STF_INTERNAL_ERROR;
 
-		close_output_pbs(&e_pbs);
-		close_output_pbs(&md->rbody);
-		close_output_pbs(&reply_stream);
+	close_output_pbs(&e_pbs);
+	close_output_pbs(&md->rbody);
+	close_output_pbs(&reply_stream);
+
+	if (should_fragment_ike_msg(st, pbs_offset(&reply_stream), TRUE)) {
+		struct chunk payload;
+
+		clonetochunk(payload, e_pbs_cipher.start, len,
+			     "cleartext for ikev2_parent_outR1_I2");
+
+		ret = ikev2_send_fragments(md, &hdr, &e, &payload,
+					   "reply fragment for ikev2_parent_outR1_I2");
+
+		freeanychunk(payload);
+		return ret;
+	}
+
+	{
 
 		ret = ikev2_encrypt_msg(st, authstart,
 					iv, encstart, authloc,
@@ -2106,6 +2392,7 @@ static stf_status ikev2_parent_inR1outI2_tail(
 	clonetochunk(pst->st_tpacket, reply_stream.start,
 		     pbs_offset(&reply_stream),
 		     "reply packet for ikev2_parent_outI1");
+	release_v2fragments(&pst->st_tfrags);
 
 	return STF_OK;
 }
@@ -2588,10 +2875,13 @@ static stf_status ikev2_parent_inI2outR2_auth_tail( struct msg_digest *md,
 	{
 		unsigned char *encstart;
 		unsigned char *iv;
+		unsigned char *authloc;
 		struct ikev2_generic e;
 		pb_stream e_pbs, e_pbs_cipher;
 		stf_status ret;
 		bool send_cert = FALSE;
+		unsigned int len;
+		struct isakmp_hdr hdr;
 
 		/* make sure HDR is at start of a clean buffer */
 		zero(&reply_buffer);
@@ -2600,7 +2890,7 @@ static stf_status ikev2_parent_inI2outR2_auth_tail( struct msg_digest *md,
 
 		/* HDR out */
 		{
-			struct isakmp_hdr hdr = md->hdr; /* grab cookies */
+			hdr = md->hdr; /* grab cookies */
 
 			/* set msg responder flag - clear others */
 			hdr.isa_flags = ISAKMP_FLAGS_v2_MSG_R;
@@ -2749,22 +3039,37 @@ static stf_status ikev2_parent_inI2outR2_auth_tail( struct msg_digest *md,
 			}
 		}
 
+		len = pbs_offset(&e_pbs_cipher);
+
 		if (!ikev2_padup_pre_encrypt(st, &e_pbs_cipher))
 			return STF_INTERNAL_ERROR;
 
 		close_output_pbs(&e_pbs_cipher);
 
-		{
-			unsigned char *authloc = ikev2_authloc(st, &e_pbs);
+		authloc = ikev2_authloc(st, &e_pbs);
 
-			if (authloc == NULL)
-				return STF_INTERNAL_ERROR;
+		if (authloc == NULL)
+			return STF_INTERNAL_ERROR;
 
-			close_output_pbs(&e_pbs);
+		close_output_pbs(&e_pbs);
+		close_output_pbs(&md->rbody);
+		close_output_pbs(&reply_stream);
 
-			close_output_pbs(&md->rbody);
-			close_output_pbs(&reply_stream);
+		if (should_fragment_ike_msg(st, pbs_offset(&reply_stream),
+						TRUE)) {
+			struct chunk payload;
+
+			clonetochunk(payload, e_pbs_cipher.start, len,
+				     "cleartext for ikev2_parent_inI2outR2_tail");
 
+			ret = ikev2_send_fragments(md, &hdr, &e, &payload,
+						   "reply fragment for ikev2_parent_inI2outR2_tail");
+
+			freeanychunk(payload);
+			return ret;
+		}
+
+		{
 			ret = ikev2_encrypt_msg(st, authstart,
 						iv, encstart, authloc,
 						&e_pbs, &e_pbs_cipher);
@@ -2778,6 +3083,7 @@ static stf_status ikev2_parent_inI2outR2_auth_tail( struct msg_digest *md,
 	clonetochunk(st->st_tpacket, reply_stream.start,
 		     pbs_offset(&reply_stream),
 		     "reply packet for ikev2_parent_inI2outR2_tail");
+	release_v2fragments(&st->st_tfrags);
 
 	/* note: retransmission is driven by initiator */
 
@@ -3311,6 +3617,7 @@ void send_v2_notification(struct state *p1st,
 
 	clonetochunk(p1st->st_tpacket, reply_stream.start, pbs_offset(&reply_stream),
 		     "notification packet");
+	release_v2fragments(&p1st->st_tfrags);
 
 	send_ike_msg(p1st, __FUNCTION__);
 }
@@ -3599,6 +3906,7 @@ static stf_status ikev2_child_inIoutR_tail(struct pluto_crypto_req_cont *qke,
 
 	freeanychunk(pst->st_tpacket);
 	clonetochunk(pst->st_tpacket, reply_stream.start, pbs_offset(&reply_stream), "reply packet for CREATE_CHILD_SA exchange");
+	release_v2fragments(&pst->st_tfrags);
 
 	send_ike_msg(pst, __FUNCTION__);
 
@@ -3942,6 +4250,7 @@ stf_status process_encrypted_informational_ikev2(struct msg_digest *md)
 		clonetochunk(st->st_tpacket, reply_stream.start,
 			     pbs_offset(&reply_stream),
 			     "reply packet for informational exchange");
+		release_v2fragments(&st->st_tfrags);
 
 		send_ike_msg(st, __FUNCTION__);
 	}
@@ -4189,6 +4498,7 @@ stf_status ikev2_send_informational(struct state *st)
 		clonetochunk(pst->st_tpacket, reply_stream.start,
 			     pbs_offset(&reply_stream),
 			     "reply packet for informational exchange");
+		release_v2fragments(&st->st_tfrags);
 		pst->st_pend_liveness = TRUE; /* we should only do this when dpd/liveness is active? */
 		send_ike_msg(pst, __FUNCTION__);
 	}
@@ -4345,6 +4655,7 @@ static bool ikev2_delete_out_guts(struct state *const st, struct state *const ps
 	clonetochunk(pst->st_tpacket, reply_stream.start,
 		     pbs_offset(&reply_stream),
 		     "request packet for informational exchange");
+	release_v2fragments(&pst->st_tfrags);
 
 	send_ike_msg(pst, __FUNCTION__);
 
diff --git a/programs/pluto/packet.c b/programs/pluto/packet.c
index 896f14f..b6d4a2d 100644
--- a/programs/pluto/packet.c
+++ b/programs/pluto/packet.c
@@ -1194,6 +1194,41 @@ struct_desc ikev2_sk_desc = { "IKEv2 Encryption Payload",
 			     ikev2generic_fields,
 			     sizeof(struct ikev2_generic) };
 
+/*
+ * 2.5.  Fragmenting Message
+ *
+ *                         1                   2                   3
+ *     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *    | Next Payload  |C|  RESERVED   |         Payload Length        |
+ *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *    |        Fragment Number        |        Total Fragments        |
+ *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *    |                     Initialization Vector                     |
+ *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *    ~                      Encrypted content                        ~
+ *    +               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *    |               |             Padding (0-255 octets)            |
+ *    +-+-+-+-+-+-+-+-+                               +-+-+-+-+-+-+-+-+
+ *    |                                               |  Pad Length   |
+ *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *    ~                    Integrity Checksum Data                    ~
+ *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *                         Encrypted Fragment Payload
+ */
+static field_desc ikev2skf_fields[] = {
+	{ ft_enum, 8 / BITS_PER_BYTE, "next payload type", &ikev2_payload_names },
+	{ ft_set, 8 / BITS_PER_BYTE, "flags", critical_names },
+	{ ft_len, 16 / BITS_PER_BYTE, "length", NULL },
+	{ ft_nat, 16 / BITS_PER_BYTE, "fragment number", NULL },
+	{ ft_nat, 16 / BITS_PER_BYTE, "total fragments", NULL },
+	{ ft_end,  0, NULL, NULL }
+};
+
+struct_desc ikev2_skf_desc = { "IKEv2 Encrypted Fragment",
+			      ikev2skf_fields, sizeof(struct ikev2_skf) };
+
 /* descriptor for each V1 payload type
  *
  * There is a slight problem in that some payloads differ, depending
@@ -1242,7 +1277,13 @@ static struct_desc *const v2_payload_descs[] = {
 	&ikev2_ts_desc,			/* 44 ISAKMP_NEXT_v2TSi */
 	&ikev2_ts_desc,			/* 45 ISAKMP_NEXT_v2TSr */
 	&ikev2_sk_desc,                 /* 46 ISAKMP_NEXT_v2SK */
-	&ikev2_cp_desc,			/* 57 ISAKMP_NEXT_v2CP */
+	&ikev2_cp_desc,			/* 47 ISAKMP_NEXT_v2CP */
+	NULL,				/* 48 */
+	NULL,				/* 49 */
+	NULL,				/* 50 */
+	NULL,				/* 51 */
+	NULL,				/* 52 */
+	&ikev2_skf_desc,                /* 53 ISAKMP_NEXT_v2SKF */
 };
 
 static field_desc suggested_group_fields[] = {
diff --git a/programs/pluto/packet.h b/programs/pluto/packet.h
index 15af444..1e0e6ce 100644
--- a/programs/pluto/packet.h
+++ b/programs/pluto/packet.h
@@ -902,6 +902,19 @@ struct ikev2_cp_attribute {
 
 extern struct_desc ikev2_cp_attribute_desc;
 
+/*
+ * Fragment Message. RFC 7383 section 2.5
+ */
+struct ikev2_skf {
+	u_int8_t isaskf_np;
+	u_int8_t isaskf_critical;
+	u_int16_t isaskf_length;
+	u_int16_t isaskf_number;
+	u_int16_t isaskf_total;
+};
+
+extern struct_desc ikev2_skf_desc;
+
 /* union of all payloads */
 
 union payload {
@@ -931,6 +944,7 @@ union payload {
 	struct ikev2_delete v2delete;
 	struct ikev2_cp v2cp;
 	struct ikev2_cp_attribute v2cp_attribute;
+	struct ikev2_skf v2skf;
 };
 
 struct suggested_group {
diff --git a/programs/pluto/server.c b/programs/pluto/server.c
index ed287c2..7e9693c 100644
--- a/programs/pluto/server.c
+++ b/programs/pluto/server.c
@@ -1215,6 +1215,32 @@ static bool send_frags(struct state *st, const char *where)
 	return TRUE;
 }
 
+bool should_fragment_ike_msg(struct state *st, size_t len, bool resending)
+{
+	if (st->st_interface && st->st_interface->ike_float)
+		len += NON_ESP_MARKER_SIZE;
+
+	return len >= (st->st_connection->addr_family == AF_INET ?
+		       ISAKMP_FRAG_MAXLEN_IPv4 : ISAKMP_FRAG_MAXLEN_IPv6) &&
+	       (resending ?
+		(st->st_connection->policy & POLICY_IKE_FRAG_ALLOW) &&
+		st->st_seen_fragvid :
+		(st->st_connection->policy & POLICY_IKE_FRAG_FORCE) ||
+		st->st_seen_fragments);
+}
+
+static bool send_ikev2_frags(struct state *st, const char *where)
+{
+	struct ikev2_frag *frag;
+
+	for (frag = st->st_tfrags; frag; frag = frag->next)
+		if (!send_packet(st, where, FALSE,
+				 frag->cipher.ptr, frag->cipher.len, NULL, 0))
+			return FALSE;
+
+	return TRUE;
+}
+
 static bool send_or_resend_ike_msg(struct state *st, const char *where,
 				   bool resending)
 {
@@ -1234,15 +1260,10 @@ static bool send_or_resend_ike_msg(struct state *st, const char *where,
 	/* decide of whether we're to fragment  - IKEv1 only, draft-smyslov-ipsecme-ikev2-fragmentation not implemented yet */
 	if (!st->st_ikev2 &&
 	    st->st_state != STATE_MAIN_I1 &&
-	    len + natt_bonus >=
-		(st->st_connection->addr_family == AF_INET ?
-		 ISAKMP_FRAG_MAXLEN_IPv4 : ISAKMP_FRAG_MAXLEN_IPv6) &&
-	    ((resending &&
-	      (st->st_connection->policy & POLICY_IKE_FRAG_ALLOW) &&
-	      st->st_seen_fragvid) ||
-	     ((st->st_connection->policy & POLICY_IKE_FRAG_FORCE) ||
-	      st->st_seen_fragments))) {
+	    should_fragment_ike_msg(st, len + natt_bonus, resending)) {
 		return send_frags(st, where);
+	} else if (st->st_ikev2 && st->st_tfrags) {
+		return send_ikev2_frags(st, where);
 	} else {
 		return send_packet(st, where, FALSE, st->st_tpacket.ptr,
 				   st->st_tpacket.len, NULL, 0);
diff --git a/programs/pluto/server.h b/programs/pluto/server.h
index ddec566..09ac047 100644
--- a/programs/pluto/server.h
+++ b/programs/pluto/server.h
@@ -85,5 +85,7 @@ extern struct event *pluto_event_new(evutil_socket_t ft, short events,
 bool ev_before(struct pluto_event *pev, deltatime_t delay);
 extern void set_pluto_busy(bool busy);
 extern void set_whack_pluto_ddos(enum ddos_mode mode);
+extern bool should_fragment_ike_msg(struct state *st, size_t len,
+				    bool resending);
 
 #endif /* _SERVER_H */
diff --git a/programs/pluto/state.c b/programs/pluto/state.c
index a720845..d77907c 100644
--- a/programs/pluto/state.c
+++ b/programs/pluto/state.c
@@ -454,10 +454,31 @@ void release_whack(struct state *st)
 	close_any(st->st_whack_sock);
 }
 
+void release_v2fragments(struct ikev2_frag **fragp)
+{
+	struct ikev2_frag *frag = *fragp;
+
+	while (frag != NULL) {
+		struct ikev2_frag *this = frag;
+
+		frag = this->next;
+		freeanychunk(this->cipher);
+
+		pfree(this);
+	}
+
+	*fragp = NULL;
+}
+
 void release_fragments(struct state *st)
 {
 	struct ike_frag *frag = st->ike_frags;
 
+	if (st->st_ikev2) {
+		release_v2fragments(&st->ikev2_frags);
+		return;
+	}
+
 	while (frag != NULL) {
 		struct ike_frag *this = frag;
 
@@ -664,6 +685,7 @@ void delete_state(struct state *st)
 	freeanychunk(st->st_firstpacket_me);
 	freeanychunk(st->st_firstpacket_him);
 	freeanychunk(st->st_tpacket);
+	release_v2fragments(&st->st_tfrags);
 	freeanychunk(st->st_rpacket);
 	freeanychunk(st->st_p1isa);
 	freeanychunk(st->st_gi);
diff --git a/programs/pluto/state.h b/programs/pluto/state.h
index 64a7955..9cb3c97 100644
--- a/programs/pluto/state.h
+++ b/programs/pluto/state.h
@@ -149,6 +149,16 @@ struct ike_frag {
 	size_t size;
 };
 
+struct ikev2_frag {
+	struct ikev2_frag *next;
+	int np;
+	int index;
+	int total;
+	unsigned int iv;
+	struct chunk cipher;
+	struct chunk plain;
+};
+
 /*
  * internal state that
  * should get copied by god... to the child SA state.
@@ -230,7 +240,11 @@ struct state {
 	const char        *st_suspended_md_func;
 	int st_suspended_md_line;
 
-	struct ike_frag *ike_frags;		/* collected ike fragments */
+	/* collected ike fragments */
+	union {
+		struct ike_frag *ike_frags;
+		struct ikev2_frag *ikev2_frags;
+	};
 
 	struct trans_attrs st_oakley;
 
@@ -315,6 +329,7 @@ struct state {
 
 	/* my stuff */
 	chunk_t st_tpacket;                     /* Transmitted packet */
+	struct ikev2_frag *st_tfrags;		/* Transmitted fragments */
 
 #ifdef HAVE_LABELED_IPSEC
 	struct xfrm_user_sec_ctx_ike *sec_ctx;
@@ -545,6 +560,7 @@ extern void fmt_state(struct state *st, const monotime_t n,
 extern void delete_states_by_peer(const ip_address *peer);
 extern void replace_states_by_peer(const ip_address *peer);
 extern void release_fragments(struct state *st);
+extern void release_v2fragments(struct ikev2_frag **fragp);
 extern void v1_delete_state_by_xauth_name(struct state *st, void *name);
 extern void delete_state_by_id_name(struct state *st, void *name);
 
-- 
Email: Herbert Xu <herbert at gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt


More information about the Swan-dev mailing list