Skip to content
Thomas Mangin edited this page Nov 9, 2026 · 10 revisions

Pre-Alpha. This page describes behavior that may change.

Ze implements native IKEv2 in Go for route-based IPsec VPN tunnels. No strongSwan, no libreswan, no external IKE daemon. The IKE engine, the cryptographic primitives, the wire codec, and the XFRM dataplane integration are all in-tree. The result is a single binary that negotiates IKE SAs, installs XFRM policies and states, and programs routes through XFRM interfaces, all driven from the same YANG config tree as every other Ze subsystem.

Architecture

The IPsec stack is split across several packages:

Package Role
internal/component/ike/wire IKEv2 wire format codec (RFC 7296). Encodes and decodes all payload types.
internal/component/ike/crypto Cryptographic primitives: DH groups, PRF, integrity, encryption, key derivation.
internal/component/ike/transport UDP transport with NAT-T (RFC 3948) keepalives, port 4500 encapsulation.
internal/component/ike/eap EAP authentication: EAP-MSCHAPv2, EAP-TLS.
internal/component/ike/engine IKE FSM: IKE_SA_INIT, IKE_AUTH, CREATE_CHILD_SA, INFORMATIONAL. Initiator and responder roles, rekeying, DPD.
internal/component/ike/ipsec Config, YANG schema, validation.
internal/component/ike/dataplane XFRM policy and state programming via netlink.
internal/component/pki PKI certificate store for X.509 certificates and private keys.

Configuration

IPsec is configured under vpn { ipsec { } }. Named ike-group and esp-group blocks hold the IKE and ESP proposals and lifetimes; each site-to-site peer references a group pair, sets its authentication, and binds to a VTI (XFRM) interface for route-based forwarding. A peer's and remote-access's ike-group and esp-group are references: commit, a SIGHUP reload and ze config validate refuse a name no group declares, naming the groups that exist, and the editor completes the names.

pki {
    ca my-ca {
        certificate "-----BEGIN CERTIFICATE-----...";
    }
    certificate my-cert {
        certificate "-----BEGIN CERTIFICATE-----...";
        private {
            key "-----BEGIN PRIVATE KEY-----...";
        }
    }
}

vpn {
    ipsec {
        interface eth0;

        ike-group site-b-ike {
            key-exchange ikev2;
            lifetime     28800;
            proposal 1 {
                encryption aes256gcm;
                hash       sha256;
                dh-group   14;
            }
            dead-peer-detection { interval 30; timeout 120; }
        }

        esp-group site-b-esp {
            lifetime 3600;
            pfs      enable;
            proposal 1 { encryption aes256gcm; }
        }

        site-to-site {
            peer site-b {
                ike-group       site-b-ike;
                esp-group       site-b-esp;
                connection-type initiate;
                local-address   198.51.100.2;
                remote-address  198.51.100.1;
                authentication {
                    mode           x509;
                    certificate    my-cert;
                    ca-certificate my-ca;
                }
                vti { bind vti0; }
            }
        }
    }
}

Traffic selectors constrain which protected networks a peer negotiates, and route-based forwarding also needs a route through the bound VTI (see Interfaces: XFRM), for example route 10.78.2.0/24 { next { interface vpn-xfrm {} } }. The empty next-hop entry selects the XFRM interface with no gateway. Established IKE, installed SAs and an up XFRM interface do not substitute for that route: a protected ping answered by Destination Net Unreachable from the local router means the route is missing.

On Linux a tunnel needs a fwd PROTECT policy as well as in and out, using the inbound protected prefixes, tunnel endpoints and XFRM if-id. Ze installs and removes the three together. A receiving SA whose packet count rises while XfrmInNoPols in /proc/net/xfrm_stat rises too has decrypted packets rejected by a later policy lookup; compare with show vpn ipsec dataplane policy.

Which edits restart a tunnel

Committing a change to any leaf inside a peer block restarts that peer, and so does a change to the ike-group or esp-group that peer names. The tunnel drops, Ze renegotiates it, and the new IKE SA and its Child SA carry the new configuration. There is no list of leaves that count and leaves that do not: the whole peer configuration and both resolved groups are compared, so a leaf Ze gains in a later release restarts the tunnel from the day it exists.

Rotating a cipher is the case worth naming, because it edits no peer block at all. A peer holds the name of its groups and none of the crypto, so an edit to ike-group IKE-1 { proposal 1 { encryption ... restarts every peer that names IKE-1 and no other.

A commit that leaves a peer's block and its two groups unchanged does not touch that peer, whatever else in the configuration moved.

One edit does not restart the tunnel. When a peer's IKE SA is up with no Child SA, because the two sides could not agree on ESP, a commit that changes only that peer's esp-group keeps the IKE SA: the new esp-group applies when the Child SA is created, with no reconnect.

Three consequences are worth planning around:

  • A tunnel bounce is how a narrowing takes effect. Narrowing a peer's traffic-selector list works, and it costs the tunnel a re-negotiation. RFC 7296 Section 2.9.2 leaves no other route: a Child SA cannot be narrowed in place.
  • Batch peer edits into one commit. Three commits touching one peer bounce it three times. One commit carrying all three edits bounces it once.
  • A group is shared, so an edit to it bounces every peer that names it. Give a peer its own group when you want to rotate its crypto without touching the others.

A commit is REFUSED when interface cannot supply an address and some peer has no local-address of its own. The transaction rolls back and every running tunnel keeps carrying traffic, because applying that configuration would restart each of those peers into a state that cannot bind. The error names the interface and how many peers depend on it. Give those peers a local-address, or bring the interface up, then commit again.

At daemon start the same configuration is applied and the condition is logged instead of refused: there is no running tunnel to protect, and every peer that carries its own local-address still comes up.

IKE features

Feature Detail
IKEv2 (RFC 7296) Full IKE_SA_INIT, IKE_AUTH, CREATE_CHILD_SA, INFORMATIONAL exchange support.
Role Initiator or responder, per peer (connection-type initiate / respond). See IKEv2 responder role below.
Proposals AES-CBC and AES-GCM (128/256) for the IKE SA and ESP. AES-CCM (128/256, 8, 12 or 16 octet ICV) for the IKE SA only. ESP-only: chacha20poly1305 with no hash, legacy 3des with hash sha1, and NULL encryption with an integrity algorithm (integrity-only ESP, RFC 4303 Section 1), each refused at commit in an ike-group. DH groups: 14 (MODP 2048), 19 (ECP 256), 20 (ECP 384). PRF: SHA-256/384/512.
Authentication Pre-shared key, X.509 certificates, EAP-MSCHAPv2, EAP-TLS, EAP MD5-Challenge. As responder, Ze is the EAP authenticator.
NAT-T (RFC 3948) Automatic NAT detection, UDP encapsulation on port 4500, keepalive. Per peer, nat-traversal prohibit refuses a translated path. See MOBIKE and NAT policy below.
DPD Dead Peer Detection via INFORMATIONAL exchange. Configurable interval and timeout. Ze declares a peer dead only after a repeated probe goes unanswered (RFC 7296 Section 2.4), never on a single unanswered attempt.
Rekeying Real CREATE_CHILD_SA rekeying of both the IKE SA and the Child SAs, make-before-break. Collision handling. See Rekeying below.
Identity checks remote-id is enforced against the asserted identity and against the peer certificate. remote-id-type pins one identity type. See Remote identity below.
Certificates Chain bounded by certificate-count (default 4), intermediates sent in order, and an optional bounded Hash-and-URL fetch.
Traffic selectors A traffic-selector list per peer, narrowed per RFC 7296 Section 2.9. See Traffic selectors below.
Transport mode mode transport negotiates USE_TRANSPORT_MODE. Tunnel mode is the default.
Denial of service COOKIE challenge before a half-open slot is committed, and the INVALID_KE_PAYLOAD group retry. See Denial-of-service protection below.
Dataplane read-back show vpn ipsec dataplane reads the kernel SAD and SPD, and a drift check compares them against what the engine believes.
MOBIKE (RFC 4555) IPv4 tunnel-mode endpoint movement, as initiator or responder, with authenticated return-routability checks. Needs the Linux 7.2 atomic XFRM state-migration API. See MOBIKE and NAT policy below.
Unmatched traffic vpn ipsec unmatched sets the disposition of the catch-all SPD entry: discard (default) or bypass (RFC 4301 Section 4.4.1). See Security Policy Database entries below.
Application overrides vpn ipsec application-override deny (default) stops other processes from exempting their sockets from the SPD. See below.
Second authentication round (RFC 4739) second-round adds one more PSK or X.509 round in each direction. See below.
Path MTU show mtu measures the path to every IPsec peer and names the XFRM interface MTU each tunnel should carry. See Path MTU.
Virtual IP pool A respond peer with virtual-ip-pool is leased a unique inner address from the named remote-access pool in IKE_AUTH (RFC 7296 Section 2.19). The eap-user list is not served yet.
Byte-based rekeying Not implemented. An SA is rekeyed on the time-based lifetime alone, and the kernel state carries no byte limit.

AES-CCM for the IKE SA

encryption aes128ccm8, aes256ccm8, aes128ccm12, aes256ccm12, aes128ccm16 and aes256ccm16 protect the IKEv2 Encrypted payload (RFC 5282). They belong under vpn ipsec ike-group <name> proposal <n>. Neither dataplane backend can install an AES-CCM ESP state, so an esp-group proposal that names one is refused at commit, and the error lists the ESP algorithms this build can install. The ike-aes-ccm16 interop scenario checks the IKE form against strongSwan.

ChaCha20, 3DES and integrity-only ESP

Prefer AES-GCM or ChaCha20-Poly1305 for new ESP deployments. 3des and sha1 are explicit legacy interoperability choices, not defaults. ChaCha20-Poly1305 uses a 32-byte key plus a four-byte salt and a 16-byte tag; 3DES uses a 24-byte key including DES parity, and SHA-1 integrity a 20-byte key and a 12-byte ICV. The fixed-size ciphers send no Key Length attribute. Linux XFRM and VPP have explicit algorithm mappings: an unavailable backend algorithm fails installation, never falls back to AES. Interop proof for the ChaCha20 and 3DES combinations is still pending. ChaCha20, 3DES and SHA-1 stay unavailable for the IKE control channel.

MOBIKE and NAT policy

IPv4 tunnel peers negotiate MOBIKE (RFC 4555) automatically when the dataplane can move a live ESP state without resetting its sequence number or replay window. On Linux that is the XFRM_MSG_MIGRATE_STATE API, first present in Linux 7.2, with CONFIG_XFRM_MIGRATE. Kernels without it, other backends and transport-mode peers do not advertise MOBIKE, and behave as before.

The initiator keeps the configured responder address. When its own local address disappears, it picks the routed source address and sends an authenticated address update. Both roles require a matching COOKIE2 return-routability answer before the Child SA moves. The IKE and Child SPIs, the keys and the inner selectors stay the same. An invalid COOKIE2 closes the IKE SA rather than installing an unverified path.

MOBIKE runs on UDP 4500 even without a NAT. That alone does not turn on ESP-in-UDP: the NAT detection result still decides the ESP encapsulation.

Leaf Values Default Meaning
vpn ipsec site-to-site peer <name> nat-traversal allow, prohibit allow prohibit rejects a translated path before a Child SA is established or moved, and sends NO_NATS_ALLOWED in the first IKE_AUTH and in every MOBIKE address update. A prohibiting peer also refuses an unprotected address update from its correspondent, so set the prohibition on both ends.

There is no MOBIKE enable leaf. A configuration edit, including a nat-traversal change, restarts the peer: mobility handles a live address change, not a configuration replacement. The mobike-initiator and mobike-responder interop scenarios remove the mobile peer's original address and require encrypted traffic both ways afterwards, on the same SPIs.

IKEv2 responder role

Ze can act as the IKEv2 responder as well as the initiator. A peer with connection-type respond waits for an unsolicited inbound IKE_SA_INIT from its configured remote-address, answers IKE_AUTH, and installs the first Child SA. (connection-type initiate, the default, is the original behavior.) The UDP transport always listens, so there is no separate listen switch: responder behavior is selected by connection-type respond plus a matching remote address.

As responder Ze authenticates with pre-shared key, X.509 certificate, or EAP. For EAP it is the EAP authenticator (the EAP-MSCHAPv2 or EAP-TLS server) for a road-warrior client: it presents its own certificate or PSK first, then runs the EAP method and derives the session keys from the EAP MSK.

vpn {
    ipsec {
        interface eth0;
        ike-group IKE-PSK {
            key-exchange ikev2;
            proposal 1 { encryption aes256gcm; hash sha256; dh-group 14; }
        }
        esp-group ESP-PSK {
            pfs enable;
            proposal 1 { encryption aes256gcm; }
        }
        site-to-site {
            peer swan {
                ike-group       IKE-PSK;
                esp-group       ESP-PSK;
                connection-type respond;
                local-address   172.28.0.2;
                remote-address  172.28.0.3;
                authentication {
                    mode              pre-shared-secret;
                    local-id          "172.28.0.2";
                    remote-id         "172.28.0.3";
                    pre-shared-secret "$9$encoded";
                }
            }
        }
    }
}

For a road-warrior EAP server, set authentication { mode eap-mschapv2 } (or eap-tls, or eap-md5) and reference a device certificate and ca-certificate from the PKI store. Configure the client as a site-to-site peer.

Virtual IP pools

A remote-access pool serves the site-to-site peers whose virtual-ip-pool leaf names it. Such a peer must use connection-type respond and send a CFG_REQUEST in IKE_AUTH. Ze leases it an inner address no other live peer holds (RFC 3948 Section 5.1), answers with a CFG_REPLY carrying the address and the pool's DNS servers, and narrows every Child SA to that address, rekeys included. The peer's proposed inner selector must contain the address (strongSwan's 0.0.0.0/0 does), or the Child SA gets TS_UNACCEPTABLE. A bound peer that asks for no address gets FAILED_CP_REQUIRED, and one Ze cannot serve INTERNAL_ADDRESS_FAILURE; both keep the IKE SA. The address returns to the pool when the IKE SA ends, and the same peer gets it back on reconnect while it is free. show vpn ipsec sa lists it under virtual-ip, ze_ipsec_virtual_ip_leases{pool} counts leases, and ze doctor warns (ipsec-virtual-ip-pool) when one family of a pool is at least 90% held.

Admission of clients from unconfigured sources and the eap-user list are not served yet, so an eap-user authenticates nobody.

Overlapping inner addresses

Ze never gives two peers tunnels that one packet could be sent into (RFC 3948 Section 5.1). When a peer proposes inner selectors that overlap a live tunnel of another peer, those selectors are left out of the answer; when nothing is left the peer gets TS_UNACCEPTABLE, keeps its IKE SA with no Child SA, and Ze logs a warning naming both peers, both selector pairs and the fix. The first peer to hold the address keeps it. Fixes: disjoint remote prefixes, a pool (virtual-ip-pool), or, for an overlap configured on purpose, different policy-priority values. Remote prefixes configured on two peers and negotiated exactly as written may overlap only at different priorities; an overlap a peer chose by narrowing is refused at any priority. Route-based peers on distinct XFRM interfaces never conflict.

When Ze is the initiator and the answer overlaps another peer's tunnel, Ze deletes the Child SA pair it just negotiated, keeps the IKE SA, and asks again after 30 seconds, doubling up to 300 seconds. A tunnel keeps its selectors reserved until its kernel policies are gone; if a policy cannot be removed, the warning lists the ip xfrm policy delete ... command for each one left.

The same rule covers a transport-mode peer, whose connection is held from its own address to Ze's: a tunnel another peer negotiates around that address is refused, and so is a transport-mode connection inside another peer's tunnel. Different policy-priority values order the two only when the tunnel is exactly a traffic selector configured on the tunnel peer and the transport connection runs exactly between the peer's configured addresses.

Gateway reauthentication

A replacement IKE SA takes over only once its Child SA is installed. If Ze refuses that Child SA, the new IKE SA is held beside the old one and the old tunnel keeps carrying traffic; the log names the refusal and its fix.

A gateway can also reauthenticate a tunnel Ze started (RFC 7296 Section 2.8.3). An initiate peer accepts a new IKE_SA_INIT while its IKE SA is established, only from the address that SA talks to, authenticates it with the same peer configuration, and moves the tunnel onto it without reconnecting. A peer that authenticates with EAP refuses it, because RFC 7296 then requires reauthentication from the original initiator: set reauth_time = 0s on a strongSwan gateway for such a peer. When gateway reauthentication crosses Ze's own rekey or reconnect, Ze keeps the IKE SA the gateway started.

Replacing a stale SA

A new inbound IKE_SA_INIT is allowed to proceed while an older established SA for the same peer is still being maintained. Once the replacement authenticates, its INITIAL_CONTACT notification tells the remote endpoint to discard the stale SA. A responder therefore recovers straight after an operator clear or a peer restart instead of waiting out Dead Peer Detection.

Both clear vpn ipsec sa forms (all, peer <name>) send a best-effort encrypted IKE Delete before dropping local state, so initiator peers re-establish immediately. If that UDP Delete is lost, the ordinary DPD path still removes the stale remote SA.

Authentication

Every EAP mode and X.509 needs a trust anchor

ca-certificate is mandatory for x509 and for every EAP mode, and the daemon refuses a remote certificate it cannot chain to that anchor. A certificate with no trust anchor authenticates nobody: any self-signed certificate carrying a valid signature would pass. RFC 7296 Section 2.16 requires EAP to run with a public-key authentication of the responder, and the anchor is what makes that signature attributable. An EAP mode also needs a certificate, because the responder signs its AUTH with it.

ca-certificate holds one anchor, not a path. A two-level authority therefore needs the peer to send its intermediates. RFC 7296 Section 3.6 puts the peer certificate first, and Ze reads every later CERT payload as a link toward the anchor. Ze sends its own chain the same way: the certificate entry first, then the intermediates that PKI entry carries, in order.

Ze refuses a config that names no ca-certificate at commit and at reload. It does NOT refuse it under ze config validate, which reads the schema and does not run a plugin's config verifier.

The EAP peer path-validates the authenticator's chain against that trust anchor, as RFC 5216 Section 5.3 requires. EAP-TLS carries no server hostname, so the chain is validated without DNS-name matching.

EAP-TLS with TLS 1.3

RFC 9190 Section 2.5 requires an EAP-TLS 1.3 server to send an encrypted TLS record carrying application data 0x00 once it has processed the client Finished and sent its last handshake message. That record is the protected success result indication, and Ze sends it in the flight that round already carries. Without it strongSwan derives no MSK, the AUTH payload of RFC 7296 Section 2.16 cannot be computed, and the SA never establishes. A TLS 1.2 exchange concludes with the bare EAP-Success it used before.

Ze issues a TLS session ticket on every EAP-TLS 1.3 authentication, because RFC 9190 Section 2.1.2 makes that a MUST. The ticket key belongs to the peering, so a later authentication with the same peer can redeem it. Whether Ze uses a ticket is the per-peer authentication { session-resumption } leaf, default true, covering both roles. With it false every authentication runs a full handshake and the ticket still goes out. A resumed authentication re-checks the cached chain against the current ca-certificate and crl, so a certificate revoked or expired since the first handshake is refused. Changing the leaf, the certificate, the ca-certificate or the crl discards every ticket held for the peer.

Ze caps the TLS version at 1.3 on both EAP-TLS roles (RFC 9190 Section 1). An EAP-TLS peer that speaks only TLS 1.2 still authenticates with the RFC 5216 key derivation.

As the EAP-TLS client on TLS 1.3, Ze requires the Section 2.5 protected success indication before it accepts the EAP-Success. An authenticator that does not send it fails the peering, and the log names the two remedies: an authenticator that follows Section 2.5, or a peering on TLS 1.2.

The EAP-TLS identity is anonymous

RFC 9190 Section 2.1.8 forbids a TLS 1.3 client to send its username in clear in the Identity Response. For mode eap-tls Ze sends an anonymous identity derived from local-id instead:

local-id EAP identity Ze sends
alice@example.com @example.com (the realm is kept, so the exchange still routes)
@example.com @example.com
ze-test-client anonymous
alice@localhost anonymous (localhost is not a valid realm under RFC 7542 Section 2.2)

local-id is still IDi in IKE_AUTH, and EAP-MSCHAPv2 and EAP MD5-Challenge still send it as their identity.

Certificate revocation for EAP-TLS

RFC 9190 Section 5.4 requires the revocation status of every certificate in the chain, except the trust anchor, to be checked on TLS 1.3. Ze checks it on both roles. The lists come from the pki ca the chain validates against, in a crl leaf-list: each value is an X509 CRL PEM document or base64 DER, and one CA can hold several. A list the CA did not sign is refused at commit.

pki {
    ca my-ca {
        certificate "-----BEGIN CERTIFICATE-----...";
        crl "-----BEGIN X509 CRL-----...";
    }
}
What the CA holds TLS 1.3 TLS 1.2
A current list that does not name the chain Established Established
A current list naming a certificate on the chain Refused, naming the certificate, its serial and the CA Refused
No list at all Refused: nothing can answer the Section 5.4 question Established (RFC 5216 asks only for CRL support)
A list whose nextUpdate has passed Refused Refused

Publish a fresh list before the nextUpdate of the configured one passes.

As the authenticator, Ze staples an OCSP response when the pki certificate it presents carries one in its ocsp-response leaf (base64 DER or OCSP RESPONSE PEM, fetched with openssl ocsp -respout). A response about another certificate is refused at commit.

As the peer, authentication { certificate-status-request true; } (default false) makes Ze require that stapled status: it refuses an authenticator that staples nothing, staples an expired response or one about another certificate, or staples one reporting the certificate revoked or unknown. With the leaf on, a chain with an intermediate below the trust anchor is refused too, because Go's TLS stack reads the stapled status of the leaf certificate alone. With the leaf off, the chain is checked against the CA's crl.

Once the Child SA is installed, the EAP-TLS peer also re-checks the authenticator's certificate with the OCSP responder its own certificate names, over https (RFC 9190 Section 5.4). A revoked answer closes the SA. A good answer leaves it up. An unreachable responder, or a certificate that names an http responder or none, logs a warning and leaves the SA up. The check runs once per authentication and never delays the tunnel.

EAP method negotiation

When the far end asks for a method other than the configured mode, Ze answers with an EAP Nak naming its own method (RFC 3748 Section 5.3.1). A concentrator that also runs that method then offers it. An EAP Notification from the far end, for example an expiring password, is logged as ike: EAP notification from the authenticator and the exchange continues; the text is unauthenticated. A forged EAP-Success, and an EAP Response answering no outstanding Request, are discarded (RFC 3748 Section 4).

EAP MD5-Challenge

authentication { mode eap-md5 } runs EAP Type 4, the CHAP exchange of RFC 1994 inside EAP, as authenticator or peer. Both ends read pre-shared-secret as the CHAP secret. It derives no key, so the IKEv2 AUTH payloads are keyed only by SK_pi and SK_pr, and only the client is authenticated by EAP. RFC 7296 Section 2.16 discourages such methods, and the daemon logs one warning per peer that uses it when the configuration is applied. The responder still signs with a certificate, so eap-md5 needs certificate and ca-certificate like every other EAP mode. Use it only for a client that runs no other method.

EAP-TLS with TLS 1.2 needs RFC 7627

RFC 5216 Section 2.3 derives the EAP-TLS MSK from a TLS key material export, and Go refuses that export on a TLS 1.2 session that did not negotiate the RFC 7627 extended master secret. Ze cannot authenticate such a peer. It reports the refusal instead of failing silently: the log line names the peer, the negotiated TLS version, RFC 7627, and the answers below.

strongSwan 6.1.0, the lab's peer, is such a peer by default, not by limitation: its libtls caps at TLS 1.2 when charon.tls.version_max is unset and implements no extended master secret. Setting charon.tls.version_max = 1.3 on the same build reaches an established SA.

Answer What to change
Move the peer to TLS 1.3 RFC 9190 covers EAP-TLS over TLS 1.3, where the export is unconditional. On strongSwan this is charon.tls.version_max = 1.3.
Add RFC 7627 to the peer's TLS 1.2 stack The export then works, and Ze needs no change.
Configure another EAP method EAP-MSCHAPv2 derives its key without a TLS export.

Ze offers no setting that lifts the refusal. An earlier release accepted GODEBUG=tlsunsafeekm=1 as a workaround. Go 1.27 removed that setting, and a removed setting carrying its old value is a fatal error the Go runtime raises before the daemon starts. Setting it now stops Ze rather than reaching the peer.

Remote identity

remote-id is the identity Ze expects the remote endpoint to assert. When it is set, Ze enforces it twice: once against the IDi or IDr payload the peer sent, and once against the remote certificate in the certificate modes. The second check is what matters when one authority issues to several clients. The chain proves the authority issued the certificate. It does not prove the certificate speaks for this peer.

A subject alternative name binds. The subject common name binds only when the certificate carries no alternative name extension at all. An address remote-id binds against the address alternative names alone, never against a domain name that spells the same address.

Ze compares six identity types: ID_IPV4_ADDR, ID_IPV6_ADDR, ID_FQDN, ID_RFC822_ADDR, ID_KEY_ID, and ID_DER_ASN1_DN. remote-id-type pins ONE of them for the peer. Without it a text remote-id accepts ID_FQDN, ID_RFC822_ADDR, and ID_KEY_ID alike, because all three compare as text. Set remote-id-type key-id to accept an opaque key id, which matches no certificate field and is denied by default.

An unset remote-id runs neither check. Every certificate the configured authority issued then authenticates as this peer. Ze logs a warning naming the peer and the identity it accepted. Set remote-id whenever the authority issues to more than one client.

Matching a set of peers with remote-id

remote-id can name a SET of peers, not only one exact identity. RFC 4301 Section 4.4.3.1 defines these sub-tree matches for a Peer Authorization Database entry. Ze accepts four set forms:

Form Example Matches
DNS suffix .example.com any FQDN identity ending in that suffix
Email suffix @example.com any RFC 822 identity ending in that suffix
Distinguished-name suffix ,ST=MA,C=US any DN identity ending in those RDNs, in RFC 4514 order
Address range 10.0.0.0/24 any address identity inside the prefix

ID_KEY_ID stays an exact match and takes no set form. local-id refuses a set-form value, because a local identity names one identity and never a set.

Certificate chains and Hash-and-URL

certificate-count bounds the X.509 chain in both directions: the most certificates Ze sends, and the most it accepts. RFC 7296 Section 3.6 sets that figure at four, and so does the default. A peer that sends more is REFUSED rather than truncated, because a silent trim hides that the limit was reached.

hash-and-url replaces the certificate on the wire with a 20-octet SHA-1 hash and a URL that resolves to it, which keeps IKE messages short. certificate-url is where you publish this device's certificate.

The leaf defaults to false, and that default is a security property. Resolving a URL a peer sends makes an outbound request on behalf of a peer that is not yet authenticated. With the leaf false Ze advertises nothing, a conforming peer sends no such payload, and Ze drops one from a peer that sends it anyway.

Control Bound
Scheme http only; every other scheme is refused before any name resolution
Response size 64 KiB
Total timeout 5 seconds
Redirects None. A redirect is refused
Destination Loopback, private, link-local, multicast, and metadata addresses are denied
Hash The SHA-1 is verified BEFORE any parser reads the fetched bytes
Cache Keyed by the hash, never by the URL

Use certificate-url-allow to permit a destination the deny list refuses. Run ze doctor to check a configured certificate-url.

Pre-shared secret encoding

pre-shared-secret-encoding reads the secret as ascii, the default, or as hex. RFC 7296 Section 2.15 requires the management interface to accept both. Use hex for a secret that holds non-printable octets.

The encoding is stated and never guessed from the value. A secret such as abcdef0123456789 is valid ASCII and valid hex, so a daemon that read the value to decide would silently reinterpret a deployed secret. A hex value with an odd length or a non-hexadecimal character is refused at commit. The at-rest $9$ obfuscation is unwrapped first, so a hex secret can also be stored obfuscated.

Intermediate certificates for EAP-TLS clients

An eap-tls peer whose client sends its certificate without the issuing intermediate can still authenticate. Store the intermediate as a PKI ca entry and name it in the intermediate-certificate list. As EAP-TLS authenticator, Ze builds the client's path from the certificates the client sent plus this list, anchored at ca-certificate only: an intermediate completes a path and never ends one. On TLS 1.3 give the intermediate entry its own crl. Ze does not fetch intermediates from the Authority Information Access URL (an RFC 5216 Section 5.3 SHOULD it does not meet); this list is the pre-configuration that section names.

pki {
    ca corp-root { certificate "<pem>"; crl "<pem>"; }
    ca corp-sub  { certificate "<pem>"; crl "<pem>"; }
}
vpn ipsec site-to-site peer branch authentication {
    mode eap-tls
    certificate ze-server
    ca-certificate corp-root
    intermediate-certificate corp-sub
}

Commit, reload and ze config validate refuse a name that is not a PKI ca entry, an entry holding no CA certificate, and the list on a peer whose mode is not eap-tls.

As authenticator, Ze resumes TLS 1.3 sessions only. A TLS 1.2 handshake runs with session tickets off, because RFC 5216 Section 2.1.2 allows nothing but change_cipher_spec and finished after a resuming TLS 1.2 server_hello.

Signature methods

Ze announces RFC 7427 signature authentication in every IKE_SA_INIT, and a peer that announces it too gets an RFC 7427 Digital Signature (method 14). A peer that does not (strongSwan with charon.signature_authentication = no) gets the method that fits Ze's key: RSA (method 1, SHA-1), or ECDSA-256, -384 or -521 (methods 9, 10, 11).

An Ed25519 or Ed448 key signs under RFC 7427 only, as RFC 8420 defines it: Ze announces the Identity hash, and when the peer announces it too, signs the AUTH octets with pure EdDSA. A peer that does not announce Identity cannot be signed for with an EdDSA key, and IKE_AUTH fails with a message naming the peer. Ze verifies all of these methods from the peer. An Ed448 certificate must be issued by an RSA, ECDSA or Ed25519 CA. A Ze started with GODEBUG=fips140=only refuses method 1.

A second authentication round (RFC 4739)

A site-to-site peer can authenticate twice in each direction, for example a device certificate first, then a pre-shared key that belongs to a user behind the gateway:

vpn ipsec site-to-site peer branch-1 authentication {
    mode x509
    local-id ze-device.example.com
    remote-id 192.0.2.1
    certificate ze-device
    ca-certificate site-ca
    second-round {
        local {
            mode pre-shared-secret
            pre-shared-secret <secret>
            id ze-user.example.com
        }
        remote {
            mode pre-shared-secret
            pre-shared-secret <secret>
            id gateway-user.example.com
        }
    }
}

local is the credential Ze proves in its second round, and remote the one it requires from the peer; either can be left out. Each round runs in its own IKE_AUTH exchange, and the tunnel comes up only once every configured round has succeeded. A round is mode pre-shared-secret or mode x509.

Situation What Ze does
The peer asks for a round second-round remote does not configure Refuses with AUTHENTICATION_FAILED
second-round remote is set and the peer finishes without it Refuses with AUTHENTICATION_FAILED; the log names the leaf
The peer asks for a third round Refuses with AUTHENTICATION_FAILED
second-round local is set and the peer does not announce RFC 4739 Completes with the first round alone, and logs one warning

Commit refuses a second round on a peer whose first round is EAP, a PSK round with no secret, an x509 round with no certificate (local) or ca-certificate (remote), and a local round with no id. hash-and-url applies to the first round only. Ze announces MULTIPLE_AUTH_SUPPORTED only to a peer that configures a second round. As the RFC says, when the initiator authenticates more than once, the responder reveals its identity before all the initiator's rounds complete (RFC 4739 Section 5).

Denial-of-service protection

RFC 7296 Section 2.6 names two attacks on an IKE responder: state exhaustion and CPU exhaustion from initiation requests with forged source addresses. The answer is a COOKIE. Ze answers an inbound IKE_SA_INIT with a COOKIE notification and commits no state, and the initiator must repeat its request with that cookie as the first payload before Ze creates an SA or takes the peer's half-open handshake slot.

vpn {
    ipsec {
        cookie-threshold 0;
    }
}

cookie-threshold sets how many half-open IKE SAs Ze tolerates before it challenges. It defaults to 0, which challenges every inbound initiation. That default costs a genuine peer one extra round trip and closes a real availability hole: a responder peer has one half-open slot, held for 30 seconds, so a single spoofed datagram bearing that peer's source address would otherwise deny it service.

The cookie is an HMAC-SHA256 over the initiator's nonce, its source address, and its SPI, under a secret Ze rotates every 60 seconds. A cookie minted under the previous secret stays valid for one further interval, so a rotation never breaks an in-flight handshake. A cookie that does not match is ignored rather than rejected, as the RFC requires.

Diffie-Hellman group correction

An initiator has to guess which Diffie-Hellman group the responder will pick. When the guess is wrong the responder answers INVALID_KE_PAYLOAD naming the group it accepts, and Ze retries the IKE_SA_INIT under that group.

The retry re-offers the whole configured proposal set rather than narrowing it to the suite carrying the responder's group. RFC 7296 Section 1.2 requires that, because the rejection is unauthenticated and a narrowed re-offer would let an attacker who forges one notify choose the cipher. Ze also refuses a group it never proposed. Both the COOKIE and the INVALID_KE_PAYLOAD retry share one budget of three per connection attempt, so two peers can never oscillate between them.

Rekeying

Both the Child SAs and the IKE SA are rekeyed with a real on-the-wire CREATE_CHILD_SA exchange (RFC 7296), replacing an earlier local-only key roll that could silently desync a live tunnel. A Child SA rekey carries N(REKEY_SA) with fresh nonces and traffic selectors; an IKE SA rekey performs a fresh Diffie-Hellman exchange (the peer supplies its key-exchange payload) and resets the message-ID counters.

A Child SA rekey performs a fresh Diffie-Hellman exchange when the esp-group has pfs enable, which is the default. Earlier builds reused the old key material despite that setting.

Rekeying is make-before-break: the replacement SA is installed before the old one is deleted, so forwarding never pauses. Simultaneous rekeys are resolved by the RFC 7296 nonce rule (the lower nonce wins) so both ends converge on one SA. Ze rekeys on the ESP or IKE soft-lifetime and retransmits a lost CREATE_CHILD_SA before tearing the tunnel down. Rekeys are counted by ze_ipsec_rekey_total{peer} (both Child SA and IKE SA rekeys) and streamed as child-rekey on monitor vpn ipsec.

Child SA and dataplane

Child SAs define the traffic selectors and the ESP proposal. Ze programs XFRM policies and states via netlink. Route-based IPsec uses XFRM interfaces (see Interfaces: XFRM).

Feature Detail
ESP proposals AES-GCM-16 (128/256), AES-CBC + HMAC-SHA-256/384/512. AES-CCM is refused for ESP.
Traffic selectors IPv4 and IPv6 CIDR prefixes, an optional IP protocol, and an optional single port.
Encapsulation Tunnel mode by default; mode transport negotiates transport mode (RFC 7296 Section 1.3.1).
Connection Per peer: connection-type initiate starts the exchange, connection-type respond waits for the peer (see IKEv2 responder role).
Replay protection Anti-replay window (default 64, RFC 4303 Section 3.4.3).
Lifetime Time-based only, from the ESP group's lifetime in seconds (0-86400). There is no byte or packet lifetime, so the RFC 4301 Section 4.4.2.1 requirement to support both at once is not met.

Policy priority

The Security Policy Database is an ordered database, and RFC 4301 Section 4.4.1 states that "a user or administrator MUST be able to order the entries". policy-priority is that control for a peer's Child SA policies.

vpn { ipsec { site-to-site { peer branch-1 {
    policy-priority 500
} } } }

The value is a uint32 in the range 101 to 2147483647, and it defaults to 2000. A LOWER value is searched first. Ze maps the value onto the kernel XFRM policy order. A rekeyed Child SA inherits the value of the Child SA it replaces.

Security Policy Database entries

RFC 4301 Section 4.4.1 gives the Security Policy Database three dispositions: PROTECT, BYPASS and DISCARD. A site-to-site peer produces the PROTECT entries. The other two need no peer and no key, so they are written directly under vpn ipsec policy:

set vpn ipsec policy drop-guest action discard
set vpn ipsec policy drop-guest local prefix 10.0.0.0/24
set vpn ipsec policy drop-guest remote prefix 192.168.99.0/24
set vpn ipsec policy drop-guest order 800
Leaf Values Default Meaning
action bypass, discard none, required What the entry does with matching traffic
order 101 to 2147483647 1000 Rank, lowest searched first
direction out, in, both both Which half of the database the entry joins. both installs a mirrored pair
protocol 0 to 255 0 (any) Next-layer protocol selector
local prefix CIDR none, required Ze's own side
local port 1 to 65535, or any any Local port selector
remote prefix CIDR none, required The far side
remote port 1 to 65535, or any any Remote port selector

The default order of 1000 sits between the IKE control-plane bypass at 100 and a peer's default of 2000, so an entry with no order carves its hole out of the tunnels. Refused at commit: prefixes of different families, a port without a protocol that has ports (6, 17 or 132), a port range, and a missing action. On Linux a discard entry is an XFRM block policy and a bypass is allow with no template. The entries are installed when the configuration is applied, whether or not any tunnel is up, and removed when they leave the configuration or the engine stops.

The catch-all entry

With no matching policy the Linux kernel passes a packet in the clear, while RFC 4301 Section 5 says it "MUST be discarded". Ze therefore installs a last wildcard entry itself, in, out and forward, for IPv4 and IPv6, at the largest priority the kernel holds. Its disposition is one leaf:

set vpn ipsec unmatched discard
Value Meaning
discard (default) Traffic no entry matches is dropped, the disposition Section 5 names
bypass Traffic no entry matches crosses in the clear

The default is discard, the nominal final entry RFC 4301 Section 4.4.1 asks for. It also drops the router's own traffic wherever it crosses in the clear: BGP, SSH and web logins, DNS and NTP; ICMP, including ping, traceroute, show mtu probes and path-MTU errors; and IPv6 neighbor discovery. Only IKE (the control-plane bypass at order 100), the negotiated tunnels and your policy entries stay open. Write a bypass entry for every flow that must cross in the clear, for example a BGP neighbor and ICMP:

set vpn ipsec policy frr-bgp action bypass
set vpn ipsec policy frr-bgp local prefix 172.28.0.2/32
set vpn ipsec policy frr-bgp remote prefix 172.28.0.4/32
set vpn ipsec policy icmp4 action bypass
set vpn ipsec policy icmp4 protocol 1
set vpn ipsec policy icmp4 order 3000
set vpn ipsec policy icmp4 local prefix 0.0.0.0/0
set vpn ipsec policy icmp4 remote prefix 0.0.0.0/0
set vpn ipsec policy icmp6 action bypass
set vpn ipsec policy icmp6 protocol 58
set vpn ipsec policy icmp6 order 3000
set vpn ipsec policy icmp6 local prefix ::/0
set vpn ipsec policy icmp6 remote prefix ::/0

Order 3000 ranks the ICMP entries after a tunnel's default 2000, so ICMP a tunnel covers still goes through it. Or write set vpn ipsec unmatched bypass to pass all of that traffic.

A commit that would turn the discard on while unmatched is unset is refused, so configuring vpn ipsec over SSH cannot cut your own session by default. The refusal names what stays open and the two commands that settle it. A written unmatched leaf, either value, always passes. The check runs on commit only: a daemon starting with unmatched unset applies the discard and logs it at error level with the same facts and commands. A box with no vpn ipsec block keeps a bypass catch-all.

To turn discard on without a gap, commit the bypass entries while unmatched is still bypass, then set unmatched discard in a later commit: the catch-all is installed before the policy entries of the same configuration, so entries added in the same commit leave a short window where their traffic is dropped.

A catch-all that cannot be installed (the VPP backend, a kernel without XFRM, a daemon without CAP_NET_ADMIN) is never reported as applied: the commit or the start fails with the fix. When the engine stops, tunnels go first, then the IKE bypass and your policy entries, and the catch-all last; an entry that cannot be removed is logged with the exact ip xfrm policy delete (or vppctl ipsec policy del) command.

Route-based tunnels under discard

Linux keeps one policy database per XFRM if_id, and the catch-all lives at if_id 0, which a packet routed into an XFRM interface meets first. So for every XFRM interface a peer binds, Ze installs bypass entries at if_id 0, ranked just above the catch-all: out and fwd for any address leaving through the interface, and out for each IPv6 address the interface holds as source. None is installed under unmatched bypass.

Each bound XFRM interface's own database also gets a final discard (in, out and fwd, both families), ranked last, under both values of unmatched: a packet routed into the interface that no Child SA covers is dropped there and counted in XfrmOutPolBlock. If it cannot be installed, the apply fails. The entries follow the interface's index, addresses and if_id as they change.

The IPv6 source entry is wider than the interface it stands for, so under discard Ze guards each such address: a from <address>/128 rule into a routing table Ze owns (tables 3000 to 3999, routes marked ze-ipsec-guard, ending in unreachable ::/0), and the nft table ze_ipsec_source_guard, which drops IPv6 from the address leaving any other interface. Routing table IDs 1000 to 3999 are therefore reserved. A peer whose local-address belongs to an interface some peer binds with vti bind is refused at commit; use a WAN address.

Application overrides

RFC 4301 Section 4.4.1 says the administrator "MUST be able to specify whether or not a user or application can override (default) system policies." On Linux a process holding CAP_NET_ADMIN overrides the SPD with a per-socket policy (IP_XFRM_POLICY, IPV6_XFRM_POLICY, IP_IPSEC_POLICY, IPV6_IPSEC_POLICY), which the kernel consults before any entry.

vpn ipsec application-override Meaning
deny (default) Ze attaches a cgroup BPF program to the cgroup2 root that refuses those four options with EPERM to every process in Ze's network namespace except Ze itself
allow No program; Linux's CAP_NET_ADMIN check is the only gate

deny needs CAP_BPF and CAP_NET_ADMIN, a cgroup2 mount, and a kernel with CONFIG_CGROUP_BPF, Linux 6.10 or later; Ze's kernel carries both. When any is missing the apply fails rather than run without the guard, and ze doctor reports doctor-ipsec-override-guard. A reload moves between the two values with no restart.

Limit Doctor
A socket policy set before the guard attached stays until its socket closes doctor-ipsec-socket-policy
A CAP_NET_ADMIN holder can still write the SPD over netlink doctor-ipsec-spd-writer
A process holding CAP_SYS_ADMIN or CAP_BPF can detach the program doctor-ipsec-override-guard

show vpn ipsec dataplane policy lists every socket policy with SPD socket and role esp-form-socket (Ze's own ESP form reader) or socket-override.

Traffic selectors and narrowing

A peer's traffic-selector list states which traffic its Child SAs carry. It is also the policy RFC 7296 Section 2.9 narrows a peer's proposal against.

vpn { ipsec { site-to-site { peer branch-1 {
    traffic-selector 1 {
        local  { prefix 10.1.0.0/16; }
        remote { prefix 10.2.0.0/16; }
    }
    traffic-selector 2 {
        protocol 6
        local  { prefix 10.3.0.0/24; port 179; }
        remote { prefix 10.4.0.0/24; }
    }
} } } }

A Child SA carries every combination of its local and remote selectors (RFC 7296 Section 3.13), so the list must name every combination it uses. An IPv4 selector is never combined with an IPv6 one. A list such as local 10.1.0.0/16 with remote 10.2.0.0/16, then local 10.3.0.0/16 with remote 10.4.0.0/16, would also offer 10.1.0.0/16 with 10.4.0.0/16, so ze config verify refuses it and names the missing combination. As responder, when the pairs Ze accepts are not every combination of their own selectors, it answers the largest part that is.

When the list is absent the peer accepts whatever the remote endpoint proposes. That is the behavior of every configuration written before the list existed, so adding the list is what restricts a peer, never omitting it.

As responder, Ze narrows the initiator's proposed TSi and TSr to a subset the list allows, and leads the answer with the initiator's first choices. A proposal it cannot narrow to a non-empty subset draws TS_UNACCEPTABLE. As initiator, Ze proposes the list in order, because RFC 7296 reads the order as the preference order.

The selectors Ze puts on the wire are the selectors it programs. A proposal it cannot program exactly is narrowed FURTHER, never rounded outward: an address range that is not a prefix becomes the largest prefix inside it, and a port range that is neither all ports nor one port becomes its first port. A rekey is never narrowed below the scope in use.

Editing a peer's traffic-selector list and committing restarts that peer (see Which edits restart a tunnel above). The tunnel drops and comes back carrying the new selectors, and the kernel policy follows. This is what RFC 7296 Section 2.9.2 asks for: a rekey that would need a narrower scope means the policy changed, and the SA should have been deleted when the change took effect.

A remote peer whose selectors narrow while a tunnel is up proposes exactly that at its next rekey. Ze refuses such a rekey with TS_UNACCEPTABLE instead of replacing the SA with a narrower one, and the log line names both selector sets. A repeating refusal means the other end narrowed its policy and left its tunnel up. Narrow the selectors on the Ze end too, or clear the peer.

The same floor applies to a rekey Ze starts. Ze installs the selectors the peer answered with, never the retired SA's, so both ends program the same traffic. An answer narrower than the scope in use is refused: Ze keeps the SA that is carrying traffic, sends no error notification (RFC 7296 Section 2.21.3), and logs both selector sets. A repeating "narrows the scope in use" line means the peer's policy shrank and the tunnel will hard-expire unless one end is corrected.

Port value Meaning RFC 7296 Section 3.13.1 encoding
any (the default) every port start 0, end 65535
1..65535 one port start N, end N

A port other than any needs protocol to name a protocol that defines ports, such as 6 for TCP or 17 for UDP. Section 3.13.1 requires the port fields to be 0 and 65535 whenever the protocol is 0, so the combination is refused at commit rather than widened silently. Opaque ports are refused for the same reason: no dataplane backend can express an exact match on port 0.

IPv6 inside an IPv4 tunnel

The inner family need not match the outer one (RFC 4301 Section 5.1.2). Ze's IKE sockets are IPv4 only, so a tunnel has an IPv4 outer pair and carries IPv4 or IPv6 inside it. A route-based peer that carries both families writes one entry per family; a peer with no list proposes the wildcard of its outer family alone.

interface {
    xfrm vti0 {
        if-id 7;
        unit 0 { ipv6 { address [ fd00:db8:1::1/128 ]; } }
    }
}
static {
    table default {
        route fd00:db8:1::/112 { next { interface vti0 { } } }
    }
}
vpn { ipsec { site-to-site { peer hq {
    vti { bind vti0; }
    traffic-selector 1 { local { prefix 0.0.0.0/0; } remote { prefix 0.0.0.0/0; } }
    traffic-selector 2 { local { prefix ::/0; } remote { prefix ::/0; } }
} } } }

A responder that names IPv6 alone (strongSwan with local_ts = remote_ts = ::/0) answers ::/0; Ze installs only the IPv6 policies, so its own IPv4 traffic never enters vti0. The vti-ipv6-inner-ipv4-outer interop scenario and the mgmt-tunnel and mgmt-tunnel-ed25519 topology scenarios run this shape.

Transport mode

mode transport asks the peer for transport mode with the USE_TRANSPORT_MODE notification of RFC 7296 Section 1.3.1. Tunnel mode is the default, and it is the RFC's own default.

vpn { ipsec { site-to-site { peer host-1 {
    mode transport
    transport-required true
    traffic-selector 1 {
        local  { prefix 10.0.0.3/32; }
        remote { prefix 10.0.0.4/32; }
    }
} } } }

Transport mode constrains the selectors. RFC 7296 Section 2.23.1 requires exactly one IP address in TSi and in TSr, so every prefix must be a single host, and a vti binding is refused because an XFRM interface carries tunnel encapsulation. A transport-mode Child SA also carries exactly one selector pair, because the kernel's transport-mode SA holds one selector, so commit refuses a transport-mode peer with more than one traffic-selector entry. As responder Ze answers a proposal of several pairs with the first one; as initiator it refuses an answer of several pairs.

A peer that declines the request establishes the Child SA in tunnel mode. Set transport-required true when that downgrade is unacceptable: Ze then deletes the SA instead, which is what Section 1.3.1 asks of an initiator. It defaults to false, so a peer without transport mode keeps a working tunnel.

Transport mode behind a NAT

Transport mode works across an address-translating NAT, on both roles, with no extra configuration. Write the addresses each end's own stack holds in local-address, local-id and the local half of every selector, and the address you dial in remote-address, remote-id and the remote half. A respond peer writes the translated address it sees, because Ze accepts an unsolicited IKE_SA_INIT only from remote-address.

Ze substitutes the selector addresses itself, as RFC 7296 Section 2.23.1 requires, so the policy it programs matches the packets its kernel handles. show vpn ipsec sa shows both sides of the substitution:

Field Meaning
nat-detected A NAT is on the path
behind-nat This node's own address was translated
peer-behind-nat The far end's address was translated
original-tsi, original-tsr The selector addresses the peer put on the wire. Null on a tunnel-mode peer
ts-local, ts-remote (in child-sa) The selector addresses the kernel programs

All three NAT fields false on a transport tunnel that will not come up behind a NAT means the peer sent no NAT_DETECTION notification. Tunnel mode across the same NAT is unaffected.

Both ESP wire forms

RFC 7296 Section 2.23 requires a device that supports NAT traversal to receive UDP-encapsulated ESP and bare ESP at any time. One Linux XFRM state serves one form: a state that carries the encapsulation template refuses bare ESP, and a state without one refuses encapsulated ESP.

Ze serves the second form beside the kernel. A raw IPPROTO_ESP reader takes the datagram XFRM refused and re-presents it through port 4500, which carries UDP_ENCAP, so the kernel hands XFRM the form its template wants. One established Child SA therefore keeps carrying traffic when its peer changes ESP form mid-session, with no rekey and no delete. The reader carries a per-socket inbound policy exemption, because Linux applies the inbound IPsec policy check before it queues a packet to a raw socket. XFRM still decrypts, still owns the replay window, and still applies the policy to the re-presented datagram.

The second-form reader serves IPv4. The form Ze SENDS is a separate decision and follows the NAT verdict alone.

Reading the kernel dataplane

Every other IPsec command reports what the IKE engine believes it installed. The show vpn ipsec dataplane commands read the kernel instead, so a state the kernel refused, a state it expired, and a policy that outlived its owner all become visible.

Command What it reads
show vpn ipsec dataplane sa [spi <spi>] The Security Association Database: SPI, addresses, if-id, mode, algorithms, replay window, byte and packet counters, and timestamps. SPI 0 is refused, because RFC 4303 Section 2.1 reserves it.
show vpn ipsec dataplane policy The Security Policy Database: selector prefixes and ports, direction, priority, upper-layer protocol, if-id, tunnel endpoints, and the peer that installed each policy. A policy Ze did not install reports its owner as unknown.
show vpn ipsec dataplane drift Each Child SA the engine counts as installed whose SPI the kernel does not hold. The command exits non-zero on drift, so a script can test it.

A read that cannot happen is an error, never an empty table. No backend loaded, a backend that cannot enumerate, and a process without CAP_NET_ADMIN each get their own message. An empty table answers "is my tunnel programmed?" with "no", and that is wrong when the truth is that nobody asked the kernel.

Drift is compared in ONE direction. An SPI the kernel holds that the engine does not name is not drift: RFC 7296 Section 2.8 keeps the old and the new Child SA alive together until the old one is deleted, so a rekey window holds two SPIs.

show vpn ipsec sa and show vpn ipsec peer name <name> carry the kernel counters in each child SA object: bytes-in, packets-in, bytes-out, and packets-out. They come from the kernel because the IKE engine never sees ESP payload. Each is null rather than zero when the SAD could not be read, and counters-known says which answer you hold.

The health registry folds the same comparison in: a peer whose Child SA the kernel does not hold reports degraded and names the peer. ze doctor adds doctor-ipsec-xfrm-unavailable, a warning raised when vpn ipsec is configured and the kernel XFRM dataplane does not answer, which is a kernel without CONFIG_XFRM_USER or a process without CAP_NET_ADMIN.

Dataplane backends

XFRM is the default backend and the production path. A VPP backend exists behind the ze_vpp build tag and programs a real VPP through the generated binapi, but it cannot be driven by IKE and no configuration selects it. It installs security associations and refuses every security policy the IKE engine produces for them, because a VPP policy lives in a security policy database bound to interfaces and nothing tells the backend which interface to use. It also implements no transport mode, and refuses such an install rather than programming a tunnel-mode entry and reporting success.

PKI certificate store

The pki { } block stores X.509 certificates, private keys, CA certificates, and the certificate revocation lists each CA published. A ca entry holds a CA certificate and its crl list; a certificate entry holds its certificate, its intermediate list and an optional private { key }. Each value is written inline in the config as PEM, not read from a file, and is validated at commit time. The key accepts PKCS8, SEC1 and PKCS1 encodings, and IKEv2 signs with RSA, ECDSA, Ed25519 and Ed448 keys (EdDSA only for a peer that announces RFC 8420's Identity hash). The PKI store also serves TLS certificates for the web UI and the gRPC API.

Health monitoring reports certificate expiry as warnings (30 days) and errors (expired). Prometheus metrics: ze_pki_certificate_expiry_seconds, ze_pki_certificate_valid.

XFRM interfaces

XFRM interfaces provide route-based IPsec. Traffic routed through the XFRM interface is encrypted; traffic arriving on it is decrypted. See Interfaces: XFRM for the config surface.

CLI

Command Description
show vpn ipsec status Tunnel and peer status summary.
show vpn ipsec sa Active IKE and Child SAs: algorithms, kernel byte and packet counters, rekey timers, SPIs, NAT detection (nat-detected, behind-nat, peer-behind-nat), original-tsi and original-tsr, is-initiator (initiator vs responder role), and peer-window-size.
show vpn ipsec peer name <name> Detail for one configured peer.
show vpn ipsec dataplane sa [spi <spi>] The SAs the kernel holds. See Reading the kernel dataplane.
show vpn ipsec dataplane policy The policies the kernel holds.
show vpn ipsec dataplane drift Child SAs the engine expects that the kernel does not hold. Exits non-zero on drift.
clear vpn ipsec sa all, clear vpn ipsec sa peer <name> Tear down and re-establish every SA, or one peer's. The bare clear vpn ipsec sa is refused.
monitor vpn ipsec Stream SA and child lifecycle events (sa-up, sa-down, child-up, child-down, child-rekey).
show pki certificates List loaded certificates with expiry.
show mtu Measure the path to each IPsec peer and size each tunnel. See Path MTU.

peer-window-size is the number of outstanding requests the peer promised in its SET_WINDOW_SIZE notification. Zero means the peer sent none, which RFC 7296 Section 2.3 reads as a window of one. Ze holds one request outstanding and accepts exactly one request id.

Health and metrics

The IPsec component registers with the health registry. It reports healthy when all configured tunnels are established, degraded when some are down or when the kernel does not hold a Child SA the engine expects, and down when critical tunnels fail.

Metric What it reports
ze_ipsec_sa_count Active IKE SAs.
ze_ipsec_virtual_ip_leases{pool} Virtual IP addresses live IKE SAs hold in each remote-access pool.
ze_ipsec_tunnel_up{peer} 1 only when the IKE SA is established AND the Child SA is installed in the dataplane.
ze_ipsec_tunnel_degraded{peer} 1 for a tunnel with a live control plane whose ESP install the kernel refused. Alert on this one: with tunnel_up it separates a lost session from a session with no ESP.
ze_ipsec_rekey_total{peer} Child SA and IKE SA rekeys.
ze_ipsec_cookie_challenges_total{peer} COOKIE challenges Ze issued.
ze_ipsec_cookie_verify_failures_total{peer} Inbound cookies that did not verify. A rising count is an attacker probing the half-open slot, or a secret rotation catching an in-flight challenge.
ze_ipsec_sa_init_retries_total{peer,cause} IKE_SA_INIT retries Ze sent, labeled cookie or invalid-ke-payload.
ze_ipsec_error_notify_sent_total{type,protected} Error notifications sent, by notify type and by whether the carrying message was encrypted.
ze_ipsec_error_notify_suppressed_total{reason} Error notifications a guard stopped, by the name of the guard.

Interop testing

The IKE implementation includes interop tests against strongSwan 6.1.0, built from the upstream release (test/interop-ipsec/Dockerfile.strongswan). The infrastructure lives in test/interop-ipsec/ and drives Docker containers running strongSwan as the remote IKE peer. Thirty-seven scenarios exist today, including:

Area Scenarios
Authentication PSK, EAP-MSCHAPv2, EAP-TLS, and EAP-TLS over TLS 1.3.
Ze as responder PSK, EAP-MSCHAPv2, EAP-TLS over TLS 1.3, IKE SA rekey started by strongSwan, and a fresh IKE_SA_INIT accepted beside an established SA.
Rekey and teardown Child SA rekey with make-before-break, clear vpn ipsec sa and re-establishment, and a Delete sent while the one request window is held.
Negotiation The INVALID_KE_PAYLOAD retry and the COOKIE challenge.
Dataplane A live Child SA whose peer changes ESP form mid-session, the kernel read-back, BGP routes exchanged with FRR over the tunnel, and IPv6 inside an IPv4 Child SA on an XFRM interface (vti-ipv6-inner-ipv4-outer).
NAT traversal Transport and tunnel mode with ESP in UDP 4500, and transport mode through a real NAT with Ze as initiator and as responder.
MOBIKE mobike-initiator and mobike-responder: the mobile peer loses its original address and traffic resumes on the same SPIs.
Algorithms and EAP AES-CCM for the IKE SA, EAP Nak method negotiation, and an EAP-TLS 1.3 client refused for a revoked certificate.
Path MTU Tunnel sizing, the negotiated transform, a NAT-installed endpoint, and the padded IKE probe.

There is no certificate-only (mode x509) scenario. The certificate paths are proven by unit tests and by the EAP-TLS scenarios, which authenticate both ends with certificates.

A WAN address that comes and goes

A CPE often dials its tunnel from a PPPoE or DHCP WAN whose address does not exist at boot and can change on every reconnect. Ze follows it:

You write Local address of a peer with no local-address
vpn ipsec interface <name> The interface's first IPv4 address, followed as it changes
Neither interface nor local-address The source address the route to the remote selects, at the start of every connection attempt

With interface set, peers without their own local-address wait at boot until the address appears (ze doctor reports them under ipsec-interface-address), restart from a new address when it changes, and stop and wait when it leaves or the interface is deleted (as ppp0 is when a PPPoE session ends). A peer with its own local-address is never restarted by any of this. local-address takes an IP address only; local-address any is refused.

remote-address may be a DNS name, resolved at the start of every connection attempt and retried with the reconnect backoff (1 s doubling to 60 s) until it resolves. A respond peer configured by name accepts an initiation from any address the name resolved to at the last refresh (every 60 s and after each commit); an incoming packet never causes a DNS query.

RFC 7296 conformance

Ze holds every gated MUST-level requirement extracted from RFC 7296 in rfc/short/rfc7296.md: 222 of the summary's 227 rows, each implemented and proven in both directions by RFC requirement: tagged tests. The remaining five rows are SHOULD-level and ungated. See RFC Implementation for what that proof means and what it does not.

See also

Home

About

First Steps

Configuration

Operation

Interfaces

Plugins

Plugin Development

Chaos Testing

Blueprints

Development

Reference

Clone this wiki locally