RFC 9611

Internet Key Exchange Protocol Version 2 (IKEv2) Support for Per-Resource Child Security Associations (SAs)

πŸ“… published July 2024, from ietf

RFC 9611: Internet Key Exchange Protocol Version 2 (IKEv2) Support for Per-Resource Child Security Associations (SAs) ✨

Okay bestie, this is the RFC 9611 tea πŸ«–πŸ’–. It's from the IETF (Internet Engineering Task Force), it's a Standards Track doc, and it came out in July 2024. The ISSN is 2070-1721 πŸ”’. The authors are A. Antony (secunet), T. Brunner (codelabs), S. Klassert (secunet), and P. Wouters (Aiven) πŸ§‘β€πŸ’»πŸ‘©β€πŸ’». The header doesn't list any RFCs that it obsoletes or updates, so it's a fresh start 🌸.

Abstract πŸ“

okay so, to make IPsec traffic faster between peers 🏎️, this doc defines one Notify Message Status Types and one Notify Message Error Types payload for IKEv2. These let peers negotiate multiple Child Security Associations (SAs) that use the same Traffic Selectors but live on different resources, like CPUs 🧠✨.

The SA_RESOURCE_INFO notification says that the negotiated Child SA, plus any new Child SAs with the same Traffic Selectors, form a group. Most or all of those Child SAs are tied to one specific resource, like one particular CPU πŸŽ€. The TS_MAX_QUEUE notify means the peer doesn't want to make any more additional Child SAs for that exact Traffic Selector combo πŸ™…β€β™€οΈ.

Using multiple Child SAs with the same Traffic Selectors is seriously iconic πŸ‘‘. Each resource holding a Child SA gets its own Sequence Number Counter, so the CPUs don't have to sync their cryptographic state or turn off packet replay protection πŸ˜­πŸ’….

Status of This Memo πŸ“Œ

This is an Internet Standards Track document. 🌈

It's a product of the Internet Engineering Task Force (IETF) and represents the consensus of the IETF community. It got public review and was approved for publication by the Internet Engineering Steering Group (IESG) 🫢. For more on Internet Standards, check Section 2 of RFC 7841.

For the current status, any errata, and how to give feedback, go to https://www.rfc-editor.org/info/rfc9611 πŸ”—.

Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved πŸ’œ.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as they're in effect on the publication date. Read these carefully, because they spell out your rights and restrictions for this document πŸ“œ. Code Components pulled out of this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions, and they come without warranty, as the Revised BSD License describes πŸ“.

Table of Contents πŸ—‚οΈ

1.  Introduction
  1.1.  Requirements Language
  1.2.  Terminology
2.  Performance Bottlenecks
3.  Negotiation of Resource-Specific Child SAs
4.  Implementation Considerations
5.  Payload Format
  5.1.  SA_RESOURCE_INFO Notify Message Status Type Payload
  5.2.  TS_MAX_QUEUE Notify Message Error Type Payload
6.  Operational Considerations
7.  Security Considerations
8.  IANA Considerations
9.  References
  9.1.  Normative References
  9.2.  Informative References
Acknowledgements
Authors' Addresses

1. Introduction πŸ¦‹

Most IPsec implementations are stuck using one hardware queue or a single CPU resource per Child SA 😩. You can run packet stream encryption in parallel, but different parts of the hardware end up locking or waiting to get their sequence number assigned to the packet being encrypted. So a machine with lots of these resources is limited to using just one of them per Child SA, and that seriously caps the throughput πŸ’€.

For example, at the time of writing, an unencrypted link of 10 Gbps or more is commonly reduced to 2-5 Gbps when IPsec encrypts the link using AES-GCM πŸ“‰. With the implementation in this document, aggregate throughput went from 5 Gbps on 1 CPU to 40-60 Gbps on 25-30 CPUs πŸš€πŸ”₯. Literally main character energy.

Now, this could be (partially) fixed by setting up multiple narrowed Child SAs, for example with Populate From Packet (PFP) as described in the IPsec architecture [RFC4301]. But that feature would create either too many Child SAs (one per network flow) or too few (one network flow spread across multiple CPUs) πŸ™ƒ. Also, PFP isn't widely implemented, so it's not really the move πŸ™…β€β™€οΈ.

To make better use of multiple network queues and CPUs, it can be useful to negotiate and install multiple Child SAs with identical Traffic Selectors 🧚. IKEv2 [RFC7296] already allows installing multiple Child SAs with identical Traffic Selectors, but there's no way to say "this extra Child SA is for performance, and it's restricted to a certain resource (queue or CPU)" πŸ€·β€β™€οΈ.

When an IKEv2 peer gets more additional Child SAs for one set of Traffic Selectors than it's willing to create, it can send back an error notify of TS_MAX_QUEUE πŸ›‘.

1.1. Requirements Language πŸŽ€

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here πŸ“£.

1.2. Terminology 🧠

This doc uses these terms from IKEv2 [RFC7296]: Notification Data, Traffic Selector (TS), Traffic Selector initiator (TSi), Traffic Selector responder (TSr), Child SA, Configuration Payload (CP), IKE SA, CREATE_CHILD_SA, and NO_ADDITIONAL_SAS πŸ“š.

It also uses these terms from [RFC4301]: Security Policy Database (SPD) and SA πŸ—‚οΈ.

2. Performance Bottlenecks 🐒

There are a few pragmatic reasons why most implementations have to restrict a Child Security Association (SA) to one specific hardware resource πŸ™ˆ. The big one is that sharing cryptographic states, counters, and sequence numbers across multiple CPUs is a nightmare πŸ˜΅β€πŸ’«. When the CPUs try to use shared state at the same time, doing it without a big performance penalty becomes impractical πŸ“‰.

So it's necessary to negotiate and set up multiple Child SAs with identical Traffic Selector initiator (TSi) and Traffic Selector responder (TSr) on a per-resource basis 🫢✨.

3. Negotiation of Resource-Specific Child SAs 🀝

First, an initial IKEv2 exchange sets up an IKE SA and the initial Child SA 🌱. If you want multiple Child SAs with the same Traffic Selectors that are each bound to a single resource, the initiator adds the SA_RESOURCE_INFO notify payload to the exchange that negotiates the Child SA (for example, IKE_AUTH or CREATE_CHILD_SA) πŸ’Œ. If this initial Child SA is going to be tied to a specific resource, it MAY indicate that by including an identifier in the Notification Data 🏷️. A responder that's willing to have multiple Child SAs for the same Traffic Selectors replies by adding the SA_RESOURCE_INFO notify payload too, and it MAY add a non-zero Notification Data πŸ’….

Additional resource-specific Child SAs are negotiated as regular Child SAs using the CREATE_CHILD_SA exchange, and they're identified the same way, with an accompanying SA_RESOURCE_INFO notification 🎟️.

When each resource-specific Child SA gets installed, it's associated with an extra local selector, like the CPU πŸ–₯️. These resource-specific Child SAs MUST be negotiated with identical Child SA properties to the ones negotiated for the initial Child SA πŸ”’. That includes cryptographic algorithms, Traffic Selectors, Mode (for example, transport mode), compression usage, and so on 🧾. Each Child SA still gets its own keying material, though, which is individually derived through the regular IKEv2 process πŸ”‘.

The SA_RESOURCE_INFO notify payload MAY be empty or MAY contain some identifying data 🫧. If it has identifying data, that data SHOULD be a unique identifier within all the Child SAs with the same TS payloads, and the peer MUST only use it for debugging purposes 🐞.

Additional Child SAs can be started on demand or all at once ⏱️. Peers can also delete specific per-resource Child SAs if they decide the associated resource is idle 😴.

During the CREATE_CHILD_SA rekey for the Child SA, the SA_RESOURCE_INFO notification MAY be included. Whether or not it's included, though, the rekeyed Child SA should be bound to the same resource(s) as the Child SA being rekeyed πŸ”πŸ’–.

4. Implementation Considerations πŸ› οΈ

There are various things an implementation can look at to figure out the best way to install multiple Child SAs 🧩.

A simple approach is to install one additional Child SA on each CPU 🍰. An implementation can make sure one Child SA can be used by all CPUs. That way, while a new per-CPU Child SA is being negotiated (which typically takes 1 RTT of delay), a CPU without its own Child SA can still encrypt its packets with the shared one ⏳✨. Another option: if an implementation needs to encrypt a packet but the current CPU doesn't have the resources for it, it can relay that packet to a specific CPU that does, although this comes with a performance penalty πŸ’Έ.

Per-CPU Child SA negotiations can end up with both peers starting additional Child SAs at the same time πŸ‘―. This is especially likely if the per-CPU Child SAs are triggered by individual SADB_ACQUIRE messages [RFC2367] πŸ™ƒ. Responders should install the additional Child SA on the CPU with the fewest additional Child SAs for this TSi/TSr pair 🧁.

When the peers have different numbers of queue or CPU resources, the peer with fewer resources may decide not to install a second outbound Child SA for the same resource, since it will never use it to send traffic πŸ“­. However, it must install all inbound Child SAs, because it has committed to receiving traffic on the negotiated Child SAs πŸ“₯.

If per-CPU packet trigger messages (for example, SADB_ACQUIRE) are implemented (see Section 6), the Traffic Selector (TSi) entry containing the information from the trigger packet should be included in the TS set, just like regular Child SAs as specified in IKEv2 [RFC7296], Section 2.9 🎯. Based on that trigger TSi entry, an implementation can pick the most optimal target CPU to install the additional Child SA on 🧠. For example, if the trigger packet was for a TCP destination to port 25 (SMTP), it might be able to install the Child SA on the CPU that's also running the mail server process πŸ“¬πŸ’œ. Trigger packet Traffic Selectors are documented in IKEv2 [RFC7296], Section 2.9 too πŸ“–.

As per IKEv2, rekeying a Child SA SHOULD use the same (or wider) Traffic Selectors, so the new Child SA covers everything the rekeyed one did 🫢. That includes Traffic Selectors negotiated via Configuration Payloads such as INTERNAL_IP4_ADDRESS, which may use the original wide TS set or the narrowed TS set 🌷.


Part 1 is done, and I'm so hyped for part 2 πŸŽ€βœ¨, where we get into Payload Format, the TS_MAX_QUEUE stuff, Security Considerations, and the References πŸ’…πŸ”₯.

Okay so bestie, section 5 is where the bytes actually live πŸ’…

5. Payload Format ✨

The Notify Payload format is defined in IKEv2 [RFC7296], Section 3.10, and we're copying it here just for convenience πŸŽ€

All multi-octet fields representing integers are laid out in big endian order (also known as "most significant byte first", or "network byte order") 🧚

5.1. SA_RESOURCE_INFO Notify Message Status Type Payload 🌸

                       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        |
   +---------------+---------------+-------------------------------+
   |  Protocol ID  |   SPI Size    |      Notify Message Type      |
   +---------------+---------------+-------------------------------+
   |                                                               |
   ~               Resource Identifier (optional)                  ~
   |                                                               |
   +-------------------------------+-------------------------------+

Here's the breakdown, field by field πŸ”

  • (C)ritical flag: MUST be 0.
  • Protocol ID (1 octet): MUST be 0. MUST be ignored if not 0.
  • SPI Size (1 octet): MUST be 0. MUST be ignored if not 0.
  • Notify Status Message Type value (2 octets): set to 16444.
  • Resource Identifier (optional): this opaque data may be set to convey the local identity of the resource 🫢

5.2. TS_MAX_QUEUE Notify Message Error Type Payload πŸ’–

                       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        |
   +---------------+---------------+-------------------------------+
   |  Protocol ID  |   SPI Size    |      Notify Message Type      |
   +---------------+---------------+-------------------------------+

And the field rules for this one πŸŽ€

  • (C)ritical flag: MUST be 0.
  • Protocol ID (1 octet): MUST be 0. MUST be ignored if not 0.
  • SPI Size (1 octet): MUST be 0. MUST be ignored if not 0.
  • Notify Message Error Type (2 octets): set to 48.

There is no data associated with this Notify type. Nothing extra to see here, it's giving minimalism ✨

6. Operational Considerations πŸ¦‹

Okay this section is where it gets spicy, so pay attention πŸ‘€ Implementations supporting per-CPU SAs SHOULD extend their local SPD selector, and the mechanism of on-demand negotiation that is triggered by traffic, to include a CPU (or queue) identifier in their packet trigger (e.g., SADB_ACQUIRE) message from the SPD to the IKE daemon. An implementation that does not support receiving per-CPU packet trigger messages MAY initiate all its Child SAs immediately upon receiving the (only) packet trigger message it will receive from the IPsec stack. πŸ’… Such an implementation also needs to be careful when receiving a Delete Notify request for a per-CPU Child SA, because it has no method to detect when it should bring up such a per-CPU Child SA again later. Also, bringing the deleted per-CPU Child SA up again immediately after receiving the Delete Notify might cause an infinite loop between the peers πŸ’€ Another issue with not bringing up all its per-CPU Child SAs is that if the peer acts similarly, the two peers might end up with only the first Child SA without ever activating any per-CPU Child SAs. It is therefore RECOMMENDED to implement per-CPU packet trigger messages πŸ”₯

Peers SHOULD be flexible with the maximum number of Child SAs they allow for a given TSi/TSr combination, to account for corner cases 🌈 For example, during Child SA rekeying, there might be a large number of additional Child SAs created before the old Child SAs are torn down. Similarly, when using on-demand Child SAs, both ends could trigger multiple Child SA requests, because the initial packet causing the Child SA negotiation might have been transported to the peer via the first Child SA, and its reply packet might also trigger an on-demand Child SA negotiation to start. As additional Child SAs consume little additional resources, allowing at the very least double the number of available CPUs is RECOMMENDED. An implementation MAY allow unlimited additional Child SAs and only limit this number based on its generic resource protection strategies that are used to require COOKIES or refuse new IKE or Child SA negotiations. Although, having a very large number (e.g., hundreds or thousands) of SAs may slow down per-packet SAD lookup 🐒

Implementations might support dynamically moving a per-CPU Child SA from one CPU to another CPU. If this method is supported, implementations must be careful to move both the inbound and outbound SAs. If the IPsec endpoint is a gateway, it can move the inbound SA and outbound SA independently of each other. It is likely that for a gateway, IPsec traffic would be asymmetric. If the IPsec endpoint is the same host responsible for generating the traffic, the inbound and outbound SAs SHOULD remain as a pair on the same CPU. If a host previously skipped installing an outbound SA because it would be an unused duplicate outbound SA, it will have to create and add the previously skipped outbound SA to the SAD with the new CPU ID. The inbound SA may not have a CPU ID in the SAD. Adding the outbound SA to the SAD requires access to the key material, whereas updating the CPU selector on an existing outbound SA might not require access to key material. To support this, the IKE software might have to hold on to the key material longer than it normally would, as it might actively attempt to destroy key material from memory that the IKE daemon no longer needs access to πŸ”

An implementation that does not accept any further resource-specific Child SAs MUST NOT return the NO_ADDITIONAL_SAS error, because it could be misinterpreted by the peer to mean that no other Child SA with a different TSi and/or TSr is allowed either. Instead, it MUST return TS_MAX_QUEUE πŸ™…β€β™€οΈ

Read that last bit twice, bestie. Returning the wrong error code can make the peer think way more is blocked than actually is 😭

7. Security Considerations πŸ”

Okay, security time, and we are being very serious about it 🫑 Similar to how an implementation should limit the number of half-open SAs to limit the impact of a denial-of-service attack, it is RECOMMENDED that an implementation limits the maximum number of additional Child SAs allowed per unique TSi/TSr.

Using multiple resource-specific Child SAs makes sense for high-volume IPsec connections on IPsec gateway machines, where the administrator has a trust relationship with the peer's administrator and abuse is unlikely and easily escalated to resolve 🏰

This trust relationship is usually not present for the deployments of remote access VPNs, and allowing per-CPU Child SAs is NOT RECOMMENDED in these scenarios. Therefore, it is also NOT RECOMMENDED to allow per-CPU Child SAs by default 🚫

The SA_RESOURCE_INFO notify contains an optional data payload that can be used by the peer to identify the Child SA belonging to a specific resource. Notification data SHOULD NOT be an identifier that can be used to gain information about the hardware. For example, using the CPU number itself as the identifier might give an attacker knowledge of which packets are handled by which CPU ID, and it might optimize a brute-force attack against the system πŸ‘€ Don't do that, it's not it πŸ’€

8. IANA Considerations πŸŽ€

IANA has registered one new value in the "IKEv2 Notify Message Status Types" registry 🧁

          +=======+============================+===========+
          | Value | Notify Message Status Type | Reference |
          +=======+============================+===========+
          | 16444 | SA_RESOURCE_INFO           | RFC 9611  |
          +-------+----------------------------+-----------+

                               Table 1

IANA has registered one new value in the "IKEv2 Notify Message Error Types" registry πŸ“

           +=======+===========================+===========+
           | Value | Notify Message Error Type | Reference |
           +=======+===========================+===========+
           | 48    | TS_MAX_QUEUE              | RFC 9611  |
           +-------+---------------------------+-----------+

                                Table 2

9. References πŸ“š

9.1. Normative References 🌟

9.2. Informative References πŸ¦‹

Acknowledgements πŸ’œ

The following people provided reviews and valuable feedback: Roman Danyliw, Warren Kumari, Tero Kivinen, Murray Kucherawy, John Scudder, Valery Smyslov, Gunter van de Velde, and Γ‰ric Vyncke 🫢

Authors' Addresses 🧚

Antony Antony secunet Security Networks AG Email: antony.antony@secunet.com

Tobias Brunner codelabs GmbH Email: tobias@codelabs.ch

Steffen Klassert secunet Security Networks AG Email: steffen.klassert@secunet.com

Paul Wouters Aiven Email: paul.wouters@aiven.io

πŸ“¦ where this page comes from