Native IKEv2 and IPsec
Pre-Alpha. This page describes behaviour that may change.
Ze implements native IKEv2 in Go for route-based IPsec VPN tunnels. It does not require strongSwan, libreswan, or another external IKE daemon. The IKE engine, cryptographic primitives, wire codec, and XFRM dataplane integration are all in-tree. The Ze binary negotiates IKE SAs, installs XFRM policies and states, and programs routes through XFRM interfaces from the same YANG config tree as every other subsystem.
Architecture
The IPsec stack is split across several packages:
| Package | Role |
|---|---|
internal/component/ike/wire |
IKEv2 wire format codec for RFC 7296 payloads |
internal/component/ike/crypto |
DH groups, PRFs, integrity, encryption, and key derivation |
internal/component/ike/transport |
UDP transport with NAT-T keepalives and port 4500 encapsulation |
internal/core/eap |
EAP-MSCHAPv2, EAP-TLS and EAP MD5-Challenge authentication |
internal/component/ike/engine |
IKE_SA_INIT, IKE_AUTH, CREATE_CHILD_SA, and INFORMATIONAL state machines for initiator and responder roles, rekeying, and DPD |
internal/component/ike/ipsec |
YANG schema, configuration, and validation |
internal/component/ike/dataplane |
XFRM policy and state programming through netlink |
internal/component/pki |
X.509 certificate and private-key store |
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 an XFRM interface for route-based forwarding.
pki {
certificate my-cert {
certificate-file /etc/ze/certs/router.pem;
private-key-file /etc/ze/certs/router-key.pem;
}
ca-certificate my-ca {
certificate-file /etc/ze/certs/ca.pem;
}
}
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 are not listed per tunnel. Route-based IPsec encrypts traffic that the routing table forwards through the bound XFRM interface. The generated configuration reference documents the XFRM interface leaves.
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 re-negotiates
it, and the new configuration is what the new IKE SA and its Child SA carry. 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 that 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 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. Adding a second peer, editing a firewall rule or changing a log level leaves a running tunnel carrying traffic.
Three consequences are worth planning around:
- A tunnel bounce is how a narrowing takes effect. Narrowing a peer's
traffic-selectorlist to stop carrying a prefix 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: there
is no running tunnel to protect, and the peers that carry their own local-address still
come up.
IKE features
| Feature | Detail |
|---|---|
| IKEv2, RFC 7296 | Full IKE_SA_INIT, IKE_AUTH, CREATE_CHILD_SA, and INFORMATIONAL exchange support |
| Role | Initiator or responder per peer through connection-type initiate or respond |
| Proposals | AES-CBC, AES-GCM 128/256, and ChaCha20-Poly1305; MODP 2048/3072/4096/8192 and ECP 256/384/521 DH groups; SHA-256/384/512 PRFs |
| Authentication | Pre-shared key, X.509 certificates, EAP-MSCHAPv2, and EAP-TLS; Ze acts as the EAP authenticator in responder mode |
| NAT-T, RFC 3948 | Automatic NAT detection, UDP encapsulation on port 4500, and keepalives |
| DPD | Dead Peer Detection through INFORMATIONAL exchanges with configurable interval and timeout |
| Rekeying | On-wire CREATE_CHILD_SA rekeying for IKE and Child SAs, make-before-break installation, and collision handling |
| MOBIKE, RFC 4555 | Not implemented; an endpoint address change requires the SA to be re-established |
| Denial-of-service | COOKIE challenge before a half-open slot is committed, and the INVALID_KE_PAYLOAD group retry |
| Dataplane read-back | show vpn ipsec dataplane reads the kernel SAD and SPD, and a drift check compares them against the engine |
| Virtual IP pool | Not wired. The remote-access container parses and no session reads it |
IKEv2 responder role
Ze can act as an IKEv2 responder or 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, starts the exchange. The UDP transport always listens, so responder mode does not need a separate listen switch.
As responder, Ze authenticates with a pre-shared key, X.509 certificate, or EAP. For EAP it acts as the EAP-MSCHAPv2 or EAP-TLS server for a road-warrior client. It presents its own certificate or PSK first, runs the EAP method, and derives session keys from the EAP MSK.
A new inbound IKE_SA_INIT can 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. This lets a responder recover immediately after an operator clear or peer restart instead of waiting for Dead Peer Detection.
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 }, eap-tls or eap-md5, then reference a device certificate and ca-certificate from the PKI store. Configure the client as a site-to-site peer. Read the EAP MD5-Challenge section below before you select eap-md5.
The remote-access container is not wired yet. It parses, and no session reads
it, so a pool assigns no address and an eap-user list authenticates nobody.
plan/immediate/spec-ipsec-remote-access.md owns the work.
Every EAP mode and X.509 needs a ca-certificate. The daemon refuses a remote
certificate it cannot chain to that anchor. A certificate with no trust anchor
authenticates nobody: any self-signed certificate that carries 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.
ca-certificate holds one anchor, not a path. A two-level authority therefore
needs the peer to send its intermediate certificates. 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 intermediate that PKI entry carries. A refusal names the anchor
and the number of intermediate certificates the peer supplied.
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.
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 is never guessed from the value. A secret such as
abcdef0123456789 is valid ASCII and valid hex. 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 rather than read as ASCII.
The at-rest $9$ obfuscation is unwrapped first, so a hex secret can also be stored
obfuscated.
Remote identity
remote-id is the identity Ze expects the remote endpoint to assert. When it is
set, Ze enforces it twice on every authentication.
| Check | What it compares | Refusal |
|---|---|---|
| Policy | The IDi or IDr payload the peer sent against remote-id |
Every authentication mode |
| Certificate | The same asserted identity against the remote certificate | Certificate modes only |
The certificate check is the half that 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, because the peer chooses the identity its signature covers. Without the second check any client of that authority authenticates as any peer.
A subject alternative name binds. The subject common name binds only when the
certificate carries no alternative name extension at all. The two fields do not
carry the same attestation: X.509 name constraints reach the alternative names
and never the subject distinguished name. An authority that permits only
dNSName .branch.example.com leaves the common name free, so a common name read
after a present alternative name would defeat that constraint.
The value you configure picks the field. An address remote-id binds against
the address alternative names alone, never against a domain name that spells the
same address. Certificate authorities issue an address alternative name under a
tighter policy than a name, and this stops the peer from choosing the weaker
field.
Ze compares six identity types: ID_IPV4_ADDR, ID_IPV6_ADDR, ID_FQDN,
ID_RFC822_ADDR, ID_KEY_ID, and ID_DER_ASN1_DN. A domain name and a mail
address compare with the ASCII letters folded. An address compares as an address,
so the encoding does not matter. A distinguished name compares in RFC 4514 string
form, and it binds against the certificate subject octet for octet. A peer that
asserts ID_DER_ASN1_GN is refused, because Ze compares no general name.
A local-id written as a distinguished name is still refused at commit. Ze
derives the type it SENDS from the shape of the value, and that derivation has no
ID_DER_ASN1_DN form.
One peer, or a set of peers
A remote-id can name one peer or a group of them. RFC 4301 Section 4.4.3.1
requires both forms of Peer Authorization Database entry. Write a group with the
hierarchy separator in front. Such an entry admits the identities BELOW it and
never the entry text itself.
| You write | It admits |
|---|---|
vpn.example.com |
that name alone |
.example.com |
vpn.example.com, gw.branch.example.com |
gateway@example.com |
that address alone |
@example.com |
gateway@example.com |
CN=hq,O=Example |
that distinguished name alone |
,ST=MA,C=US |
every distinguished name whose top RDNs are ST=MA,C=US |
10.0.0.1 |
that address alone |
10.0.0.0/24 |
every address in the block |
RFC 4514 renders the most significant RDN last, so a distinguished-name group is
written with the top of the tree at the end and a leading comma in front. An
address group takes the same CIDR prefix a traffic selector takes. ID_KEY_ID
is always compared exactly: an opaque octet string has no hierarchy, so a
leading . or @ in one is content.
Substring matching is not offered. .example.com refuses notexample.com, and
it refuses the bare example.com too. Write the apex exactly when you mean it.
A group never widens what one certificate proves. The certificate check
above still binds the identity the peer ASSERTED, so a peer admitted by
.example.com must hold a certificate that carries its own name. A local-id
that names a group is refused at commit, because Ze asserts exactly one identity
and would send the pattern text verbatim.
RFC 7296 Section 4 requires that you can configure Ze to accept a PKIX peer whose
identity is ID_KEY_ID. An opaque key id matches no certificate field, so Ze
cannot derive that binding and denies it by default. Set remote-id-type key-id
to state that the chain to ca-certificate plus the exact key id IS the binding
you intend.
remote-id-type also pins ONE identity type for the peer. Without it a text
remote-id accepts ID_FQDN, ID_RFC822_ADDR and ID_KEY_ID alike. All three
compare as text. Set remote-id-type rfc822-address when you know the value is a
mail address. Ze then refuses a peer that asserts ID_FQDN with the same text.
The leaf governs what Ze ACCEPTS. The type Ze sends still follows local-id.
An unset remote-id runs neither check. Every certificate the configured
authority issued then authenticates as this peer. Ze logs a warning that names
the peer and the identity it accepted. Set remote-id whenever the authority
issues to more than one client.
Certificate chain length
certificate-count bounds the X.509 chain in both directions. It is the most
certificates Ze sends for the peer, and the most it accepts from it. RFC 7296
Section 3.6 sets that figure at four, and the default is four, so a peer you never
configure gets the conformant behaviour.
A peer that sends more than the bound is REFUSED. Ze does not truncate the chain. A silent trim hides from you that the limit was reached. It also makes the surviving certificates depend on the order the peer chose.
Name the intermediates on the PKI certificate entry, in order from the issuer of
your device certificate toward the trust anchor. Ze refuses at commit a peer whose
certificate-count is smaller than the chain its PKI entry holds.
Hash and URL certificates
hash-and-url replaces the certificate on the wire with a 20-octet SHA-1 hash and
a URL that resolves to it (RFC 7296 Section 3.6). It keeps IKE messages short. Set
certificate-url to the http URL 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. Ze must fetch the certificate before it CAN authenticate the peer. 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.
When you enable it, the lookup is bounded:
| 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.
The EAP peer validates the authenticator's certificate chain against that trust anchor. EAP-TLS has no server hostname, so the check validates the chain without DNS-name matching.
EAP method negotiation
The far end chooses the EAP method, and authentication { mode ... } sets the one
Ze runs. When the far end asks for a different authentication method, Ze answers
with an EAP Nak that names the configured method (RFC 3748 Section 5.3.1) rather
than ending the exchange. A concentrator that also runs that method then offers
it, and the tunnel establishes with no configuration change. A concentrator that
runs no other method ends the exchange, and Ze logs ike: EAP failed. Change
mode to a method both ends run.
The far end can also send an EAP Notification, which is how a concentrator
reports something such as an expiring password. Ze logs it as ike: EAP notification from the authenticator with the message, and the authentication
continues. The message is unauthenticated: it is chosen by whoever sent the
packet, so read it as a claim.
When Ze is the EAP server and the client refuses the offered method, the
ike: EAP authentication failed line names the types the client asked for and
the type Ze offered.
When Ze is the EAP-TLS client on TLS 1.3, it requires the RFC 9190 Section 2.5 protected success result indication before it accepts the EAP-Success. An authenticator that does not send it fails the peering, and the message names the authenticator, the negotiated version and two remedies: an authenticator that follows Section 2.5, or a peering on TLS 1.2, which RFC 5216 governs and where no indication exists. TLS 1.2 peerings are unaffected.
EAP MD5-Challenge
authentication { mode eap-md5 } runs EAP Type 4, the CHAP exchange of RFC 1994
carried inside EAP (RFC 3748 Section 5.4). Both ends read the pre-shared-secret
leaf as the CHAP secret. Ze runs it as the authenticator and as the peer.
It is discouraged, and the daemon says so. RFC 7296 Section 2.16: "EAP methods that do not establish a shared key SHOULD NOT be used, as they are subject to a number of man-in-the-middle attacks". The daemon writes one warning for each peer that carries the mode. It writes it when the configuration is applied, and not for each handshake.
What it costs you, against the two other EAP modes:
| Property | eap-md5 |
eap-mschapv2 and eap-tls |
|---|---|---|
| Key derivation | None. RFC 3748 Section 5.4 records "Key derivation: No" | An MSK of 64 octets |
| IKEv2 AUTH payloads | Keyed by SK_pi and SK_pr, which every party completing IKE_SA_INIT holds | Keyed by the EAP MSK |
| Who is authenticated by EAP | The client only | Both ends, for eap-tls |
| Secret at rest | The CHAP secret, needed in the clear on both ends | The same for eap-mschapv2; a private key for eap-tls |
The responder still authenticates itself with a public-key signature, because
RFC 7296 Section 2.16 requires one whichever EAP method runs. So eap-md5 needs
the same certificate and ca-certificate every other EAP mode needs. Select it
when the client runs no other method, and prefer eap-tls or eap-mschapv2
everywhere else.
EAP-TLS with TLS 1.3
RFC 9190 Section 2.5 requires an encrypted TLS record that carries application data
0x00. The EAP-TLS 1.3 server sends it once it has processed the client Finished and sent
its last handshake message. That record is the protected success result indication. Ze
sends it in the flight that round already carries, so the exchange keeps its round count.
The record is what makes a TLS 1.3 EAP-TLS client work. strongSwan derives no MSK without it, so 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 concluded with before, because the version test reads the negotiated version.
Ze issues a TLS session ticket on every EAP-TLS 1.3 authentication, because RFC 9190
Section 2.1.2 makes that a MUST with no condition on it. The ticket key belongs to the
peering rather than to the session, so a later authentication with the same peer can redeem
it. Whether Ze USES a ticket is the session-resumption leaf, which defaults to true and
covers 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 that was revoked or expired since the first
handshake is refused.
Ze caps the TLS version at 1.3 on both EAP-TLS roles. RFC 9190 Section 1 requires that cap, because EAP-TLS couples the TLS state machine to the EAP one and a version nobody integrated into the EAP half breaks the pair. Ze offers no setting that raises the cap. TLS 1.2 stays reachable under it, and an EAP-TLS peer that speaks only TLS 1.2 still authenticates with the RFC 5216 key derivation.
Ze puts the four-octet TLS Message Length only on the first fragment of a message it had to fragment. RFC 9190 Section 2.1.9 forbids the L bit on a message that fits in one EAP-TLS packet, so in a packet capture the TLS data of such a message starts one octet after the EAP Type, not five. Ze accepts an unfragmented message from the far end with the L bit or without it, which the same sentence requires.
Ze does not send local-id as the EAP-TLS identity
RFC 9190 Section 2.1.8 says that "a client supporting TLS 1.3 MUST NOT send its username (or
any other permanent identifiers) in cleartext in the Identity Response". So for
mode eap-tls Ze does not put local-id in the EAP-Response/Identity. It sends an anonymous
Network Access Identifier derived from it:
local-id |
EAP identity Ze sends | Why |
|---|---|---|
alice@example.com |
@example.com |
the username is omitted and the realm is kept, which is the form Section 2.1.8 recommends |
@example.com |
@example.com |
already anonymous |
ze-test-client |
anonymous |
✕ |
alice@localhost |
anonymous |
localhost is not a realm the RFC 7542 Section 2.2 grammar accepts |
The realm survives because it is what routes an EAP exchange to a home domain, and because
RFC 9190 Section 2.1.3 asks for the same realm again when a session resumes. Give local-id
a realm when the exchange has to route: an identity with none tells the far end nothing.
local-id is unchanged everywhere else. It is still IDi in IKE_AUTH, and the EAP-MSCHAPv2
and EAP MD5-Challenge methods still send it as their identity, because RFC 9190 governs
EAP-TLS alone and those methods carry their username inside the method exchange.
EAP-TLS 1.3 needs a certificate revocation list
RFC 9190 Section 5.4 says that "when EAP-TLS is used with TLS 1.3, the revocation status of all the certificates in the certificate chains MUST be checked (except the trust anchor)". Ze checks it on both roles: the authenticator over the client's chain, and the peer over the authenticator's chain.
The revocation lists come from the pki ca the peer validates against, in a crl
leaf-list. Each value is an X509 CRL PEM document or base64-encoded DER, and one CA can
hold several, which is what a segmented CRL or a rollover needs. A list this CA did not
sign is refused at commit, because it cannot answer for the certificates this CA issued.
pki {
ca my-ca {
certificate "-----BEGIN CERTIFICATE-----...";
crl "-----BEGIN X509 CRL-----...";
}
}
A CA with NO list and a list that revokes NOTHING are different answers, and the difference decides whether a session establishes:
| What the CA holds | TLS 1.3 | TLS 1.2 |
|---|---|---|
| A current list, and the chain is not on it | The session establishes | The session establishes |
| A current list naming a certificate on the chain | Ze refuses, naming the certificate, its serial number and the CA that withdrew it | The same |
| ✕ | Ze refuses: nothing can answer the question Section 5.4 makes mandatory | The session establishes |
A list whose nextUpdate has passed |
Ze refuses: an expired list says nothing about the present | The same |
The "no list at all" row is the only one where the two versions differ. TLS 1.2 is governed by RFC 5216 Section 5.4 instead, which asks only that an implementation "MUST support the use of Certificate Revocation Lists (CRLs)". Nothing there obliges a session the operator configured no list for, so a TLS 1.2 peer with no list still authenticates while the same peer on TLS 1.3 does not.
Every other row holds on both versions, because a list that IS configured is checked whatever was negotiated. An expired list is not a usable one, so it refuses on TLS 1.2 too: the operator asked for the check, and the source can no longer perform it.
Publish a fresh list before the nextUpdate of the one in the config passes. An expired
list refuses the same peers a missing one does.
EAP-TLS 1.3 staples the OCSP response you give it
RFC 9190 Section 5.4 says that "EAP-TLS servers supporting TLS 1.3 MUST implement
Certificate Status Requests (OCSP stapling)". Ze answers one with the response configured on
the certificate it presents, in an ocsp-response leaf beside that certificate:
pki {
certificate vpn-server {
certificate "-----BEGIN CERTIFICATE-----...";
ocsp-response "MIIBxAoBAKCB...";
private { key "..."; }
}
}
Fetch the value from the responder your CA publishes, with openssl ocsp -respout. Give it as
base64-encoded DER or as an OCSP RESPONSE PEM document. A response about another certificate
is refused at commit. A certificate with no response answers a Certificate Status Request with
no status, which RFC 6066 Section 8 permits, and the peer then falls back to the crl of the
CA that issued it.
An OCSP response carries a nextUpdate, and a peer that checks the status refuses the
certificate once that time passes. Replace the value before it does, the same discipline a
crl needs.
An EAP-TLS peer can require the stapled status
RFC 9190 Section 5.4 puts a MUST on a peer that uses Certificate Status Requests to check a
chain: "it MUST treat a CertificateEntry (but not the trust anchor) without a valid
CertificateStatus extension as invalid and abort the handshake with an appropriate alert". The
certificate-status-request leaf makes Ze such a peer, and it is false by default:
ipsec {
peer headquarters {
authentication {
mode eap-tls;
certificate-status-request true;
}
}
}
| What the authenticator staples | certificate-status-request true |
certificate-status-request false |
|---|---|---|
| A current response saying the certificate is good | The session establishes | The session establishes |
| Nothing | Ze refuses, naming the missing CertificateStatus | The session establishes |
| A response reporting the certificate revoked or unknown | Ze refuses, naming what the responder said | The session establishes |
A response whose nextUpdate has passed, or one about another certificate |
Ze refuses, naming which | The session establishes |
With the leaf false the chain is still checked against the crl of its CA, which Section 5.4
requires on TLS 1.3 either way.
One deployment limit comes with the leaf. Ze reads the stapled status of the LEAF certificate
and of no other, because Go's TLS stack parses the status extension of the first
CertificateEntry alone. Section 5.4 makes an entry whose status is not valid invalid, and an
entry Ze cannot read the status of is one it cannot call valid, so a chain carrying an
intermediate CA below the trust anchor is refused while the leaf is on. Give the authenticator
a certificate its trust anchor signed directly, or leave the leaf off and check by crl.
An EAP-TLS peer re-checks the server certificate once the tunnel is up
RFC 9190 Section 5.4 asks an EAP-TLS peer to "support checking for certificate revocation after authentication completes and network connectivity is available", and to "use a secure transport" for it. RFC 5216 Section 5.4 asks for the same with no TLS version attached.
Ze runs that check as soon as the Child SA is installed, which is the moment the tunnel it just authenticated gives it a network. It asks the OCSP responder named by the authenticator's own certificate, over https, and it needs no configuration: the responder URL comes from the certificate's authority information access extension.
| What the responder says | What Ze does |
|---|---|
| The certificate is revoked | Closes the SA and logs the revocation. The reconnect that follows runs a fresh authentication, which the same certificate fails inside the handshake |
| The certificate is good | Logs it and leaves the SA up |
| Nothing, because it cannot be reached | Logs a warning and leaves the SA up. A responder outage is not evidence of a revocation |
| Nothing, because the certificate names an http responder or none at all | The same warning. Section 5.4 requires a secure transport, so an http responder is refused before any connection opens |
The check runs once per authentication rather than on a timer, and it never delays the tunnel: the Child SA is up before it starts.
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 says so rather than failing silently: the log line names the peer, the negotiated version, RFC 7627 and the answers below.
strongSwan 5.9.14 is such a peer, by default rather than by limitation. charon ships
version_max = 1.2 in /etc/strongswan.d/charon.conf, with the comment "default to TLS
1.2 until 1.3 is stable for use in EAP", and it implements no extended master secret. Set
charon.tls.version_max = 1.3 and the same 5.9.14 build reaches an established SA, which
is what test/interop-ipsec/scenarios/eap-tls13 runs.
| Answer | What to change |
|---|---|
| Move the peer to TLS 1.3 | RFC 9190 covers EAP-TLS over TLS 1.3, and the export is unconditional there. This is the preferred answer, and on strongSwan it is one line: 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, and there is nothing to add to the
environment. Go 1.27 removed the tlsunsafeekm GODEBUG setting that once lifted it. A
removed setting carrying its old value is a fatal error that the Go runtime raises before
the daemon starts, so setting it stops ze rather than reaching the peer.
Denial-of-service protection
RFC 7296 Section 2.6 names two attacks on an IKE responder: state 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.
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, and one datagram every 30 seconds
would deny it indefinitely. Raise the value to tolerate that many half-open handshakes
before challenging.
vpn {
ipsec {
cookie-threshold 0;
}
}
The cookie is an HMAC-SHA256 over the initiator's nonce, its source address and its SPI, under a secret Ze generates and 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: the message is processed as though it carried no cookie, which for a pressured responder means issuing a fresh challenge.
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. Without this, Ze could never establish with a peer that prefers a different 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, which stops the same attacker choosing the group. Both COOKIE and INVALID_KE_PAYLOAD retries share one budget of three per connection attempt, so two peers can never oscillate between them.
Rekeying
Both Child SAs and IKE SAs are rekeyed with an on-wire CREATE_CHILD_SA exchange. This replaced an earlier local-only key roll that could silently desynchronise 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 and resets the message-ID counters.
Rekeying is make-before-break: the replacement SA is installed before the old one is deleted, so forwarding does not pause. Simultaneous rekeys are resolved by the RFC 7296 nonce rule. Section 2.8.1 states it as a recommendation: if redundant SAs are created, the endpoint that created the SA with the lowest of the four nonces SHOULD close that SA. Ze resolves the collision one step earlier and abandons its own pending exchange when its nonce is the lower one, so the redundant SA is never created and both ends converge on one SA. Ze rekeys at 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} and streamed as child-rekey events by monitor vpn ipsec.
Child SA and dataplane
Child SAs define traffic selectors and the ESP proposal. Ze programs XFRM policies and states through netlink. Route-based IPsec uses XFRM interfaces.
| Feature | Detail |
|---|---|
| ESP proposals | AES-GCM-16 128/256 and AES-CBC with HMAC-SHA-256/384/512 |
| Traffic selectors | IPv4 and IPv6 CIDR prefixes, an optional IP protocol, and an optional single port. See Traffic selectors and narrowing below |
| Encapsulation | Tunnel mode by default; mode transport negotiates transport mode per RFC 7296 Section 1.3.1 |
| Connection | connection-type initiate starts the exchange; connection-type respond waits for the peer |
| Replay protection | Anti-replay window, default 64, the size RFC 4303 Section 3.4.3 says SHOULD be the default |
| Lifetime | Time-based rekeying, from the ESP group's lifetime in seconds. Ze carries no byte lifetime, so RFC 4301 Section 4.4.2.1's simultaneous use of both is not met |
| Policy order | policy-priority ranks this peer's SPD entries, lower first. See SPD ordering below |
SPD ordering
RFC 4301 Section 4.4.1 makes the Security Policy Database an ordered database, because entries with range selectors overlap: "Thus, a user or administrator MUST be able to order the entries to express a desired access control policy."
policy-priority under a peer is that order. A LOWER value is searched FIRST,
in Ze and in the Linux kernel. Two peers that both negotiate 0.0.0.0/0
describe the same traffic, so give the one that must win the lower value.
The default is 2000, which every peer took before the leaf existed, so an unordered configuration installs what it installed before. The range starts at 101 because 100 holds the IKE control-plane bypass. A peer entry at or above that rank matches the IKE datagrams first, which hands the exchange that builds and rekeys the SA to the SA itself, and the tunnel could then never renegotiate. Ze refuses such a value at commit.
Security Policy Database entries
RFC 4301 Section 4.4.1 gives the Security Policy Database three dispositions: "PROTECT, BYPASS, or DISCARD". A site-to-site peer produces the PROTECT entries, because a protect entry names an ESP transform and a peer to negotiate keys with. The other two name neither, so no negotiation can produce them and they are written directly:
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
Section 7.4 makes the discard disposition an obligation: "All implementations MUST support DISCARDing of fragments using the normal SPD packet classification mechanisms." The list above is the management interface Section 4.4.1 requires for it.
| 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 |
protocol |
0 to 255 | 0 (any) | Next-layer protocol selector |
local prefix |
CIDR | none, required | Ze's own side of the boundary |
local port |
1 to 65535, or any |
any |
Local port selector |
remote prefix |
CIDR | none, required | The far side of the boundary |
remote port |
1 to 65535, or any |
any |
Remote port selector |
both installs a mirrored pair, one entry in each direction: the outbound entry
keeps local and remote as written, and the inbound entry swaps them, because the
local side of a flow is the destination of an inbound packet.
The default order of 1000 sits between the IKE control-plane bypass at 100 and a
peer's default rank of 2000, so an entry written with no order outranks the
tunnels and carves its hole out of them. Raise it above 2000 to let a tunnel
win instead. An order at or below 100 is refused at commit, for the reason
policy-priority states above.
Four rules are refused at commit rather than approximated at install time, because a widened selector fails in both directions: a widened bypass passes protected traffic in the clear, and a widened discard black-holes traffic the operator meant to carry.
- Both prefixes are required, and they must share an address family. One kernel policy selector carries one family.
- A port other than
anyneedsprotocolto name a transport that has ports: 6 (TCP), 17 (UDP) or 132 (SCTP). - A port range is not accepted. The kernel selector carries a port and a mask, which expresses every port or one exact port and nothing between them.
actionhas no default. A bypass written where a discard was meant passes exactly the traffic the entry exists to stop.
On Linux a discard entry is an XFRM policy with action block and a bypass is
allow with no template. On VPP they are IPSEC_API_SPD_ACTION_DISCARD and
IPSEC_API_SPD_ACTION_BYPASS. show vpn ipsec dataplane reports the
disposition of every installed policy, including one another daemon installed.
Ze installs these entries when the configuration is applied and removes them when the entry leaves the configuration or the engine stops. They need no peer, no key and no negotiation, so they are in force whether or not any tunnel is up.
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. It re-presents that datagram 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. Linux applies the inbound IPsec policy check before it queues a packet to a raw socket. Ze's own Child SA policy refuses exactly the datagrams this reader exists to recover. The exemption reaches that one socket. XFRM still decrypts, still owns the replay window, and still applies the inbound policy to the re-presented datagram.
The second-form reader serves IPv4. An SA that asks for both forms over IPv6 is refused rather than installed with one form. The form Ze SENDS is a separate decision and follows the NAT verdict alone: Ze encapsulates when it detected a NAT.
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; }
}
} } } }
When the list is absent the peer accepts whatever the remote endpoint proposes. That is the behaviour 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 a TS_UNACCEPTABLE notification. 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. 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. Narrowing
a live tunnel by waiting for the rekey timer is not an option the protocol offers.
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. It retries at each
lifetime tick, so 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, so accepting one would install an any-port policy.
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. Several selectors are still
allowed when they share that one address, for example to negotiate several ports.
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, and it needs no
extra configuration. Write the addresses each end's own stack holds in local-address,
local-id and the local half of every traffic-selector, and write the address you DIAL
in remote-address, remote-id and the remote half. A respond peer writes the address
it SEES the far end at, which is the translated one, because Ze accepts an unsolicited
IKE_SA_INIT only from the configured remote-address.
Ze then substitutes the selector addresses for you. RFC 7296 Section 2.23.1 requires it:
the peer answers in the addresses ITS stack sees, and those exist on neither node. Ze
replaces them with the addresses it observed, so the policy it programs matches the packets
its own kernel handles. This is the one place where the selectors on the wire and the
selectors in show vpn ipsec sa differ, and the command reports what was programmed.
show vpn ipsec sa names which side is translated. nat-detected says a NAT is on the
path, behind-nat says this node's own address was translated, and peer-behind-nat says
the far end's was. Both can be true at once. A transport tunnel that does not come up
behind a NAT is diagnosed with those three fields first: all false means the peer sent no
NAT_DETECTION notification, which no conforming IKEv2 implementation omits.
The command shows both selector pairs, so you can see the substitution rather than infer
it. original-tsi and original-tsr are the addresses the peer put on the wire, and
ts-local and ts-remote inside child-sa are the addresses the kernel programs. On a
tunnel-mode peer, and on any peer that never negotiated transport mode, the two original
fields are null.
Tunnel mode across the same NAT is unaffected. Section 2.23.1 governs transport mode alone, so a tunnel-mode Child SA negotiates the selectors you configured, untouched.
The VPP dataplane backend does not implement transport mode. It refuses a transport-mode install with a clear error rather than programming a tunnel-mode entry and reporting success. The SA path and the policy path each refuse it on their own.
The VPP dataplane backend cannot be driven by IKE, and no configuration selects it.
It installs security associations, and it refuses every security policy the IKE engine
produces for them. VPP has no node-wide policy database: a policy lives in a security
policy database that acts only on the interfaces it is bound to, and nothing tells the
backend which interface to use. The backend is reached only through a private test
override, it is compiled in only with the ze_vpp build tag, and no test has sent ESP
through it. Use the XFRM backend, which is the default and the production path.
PKI certificate store
The pki { } block stores X.509 certificates, private keys, CA certificates, and the certificate revocation lists each CA published. Certificates are loaded from PEM files and validated at commit time. The PKI store also serves TLS certificates for the web UI and gRPC API.
Health monitoring reports certificate expiry as a warning at 30 days and an error after expiry. Prometheus exposes ze_pki_certificate_expiry_seconds and ze_pki_certificate_valid.
XFRM interfaces
XFRM interfaces provide route-based IPsec. Traffic routed through the XFRM interface is encrypted, and incoming traffic is decrypted before it appears on the interface. The generated configuration reference documents the interface surface.
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. Three failures become visible: a state
the kernel refused, a state it expired, and a policy that outlived its owner.
| 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 <spi> selects every matching SPI, which can occur under different destinations or interfaces. SPI 0 is refused under RFC 4303 Section 2.1 |
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 expected Child SA identity absent from the kernel. The command exits non-zero when it finds drift, so a script can test it |
RFC 4301 Section 4.4 keeps the SPD and the SAD separate, and so does this command tree.
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 the wrong answer when the truth is that nobody asked the kernel.
Drift compares expected SA identities: SPI, destination, protocol, and XFRM interface ID. Extra kernel SAs are permitted during rekey. RFC 7296 Section 2.8 permits the old and new Child SAs to coexist until cleanup.
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. A failed or changing
observation sets counters-known: false and all counters to null. A stable SAD
read sets counters-known: true; a missing SA then has null counters, while an
SA that carried no traffic has numeric zero counters.
The health registry uses the same observation. A missing expected Child SA
reports degraded and names the peer. An unreadable or changing observation
also reports degraded, with an unknown-dataplane diagnosis.
IPsec is an enrolled kernel capability. When the configuration installs a Security
Association and the kernel holds no XFRM dataplane, ze doctor reports
doctor-ipsec-xfrm-unavailable as an ERROR, ze refuses to start with the same message
and exit 1, and ze config validate fails. There is no override: a daemon that negotiates
a tunnel and encrypts nothing is the hazard this removes.
The probe opens the XFRM netlink socket, which needs no CAP_NET_ADMIN, and it reports
absence only for the errno that means the kernel carries no XFRM protocol. Every other
failure, a permission denial included, is doctor-ipsec-xfrm-unknown at warning severity
and ze still starts: no kernel rebuild fixes a privilege fault.
An empty vpn { ipsec { } } block installs no Security Association, so it is not IPsec in
use. It gates nothing, warns about no kernel module, and binds no IKE listener.
doctor-ipsec-udp-encap is an error, and it covers a NAT-T socket that would not bind as
well as one the kernel will not decapsulate through. Both leave a tunnel that establishes
and carries no traffic.
CLI
| Command | Description |
|---|---|
show vpn ipsec status |
Tunnel and peer status summary |
show vpn ipsec sa |
Active IKE and Child SAs, including algorithms, kernel byte and packet counters, rekey timers, SPIs, NAT detection, and initiator role |
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 [peer <name>] |
Tear down and re-establish SAs, optionally for one peer |
monitor vpn ipsec |
Stream sa-up, sa-down, child-up, child-down, and child-rekey lifecycle events |
show pki certificates |
List loaded certificates with expiry information |
show vpn ipsec sa also reports peer-window-size, 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.
clear vpn ipsec sa sends a best-effort encrypted IKE Delete before removing
local state. Initiator peers then re-establish immediately. If the UDP Delete is
lost, the normal DPD path still removes the stale remote SA.
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, and down when critical tunnels fail.
Prometheus exposes ze_ipsec_sa_count, ze_ipsec_tunnel_up{peer}, ze_ipsec_tunnel_degraded{peer}, and ze_ipsec_rekey_total{peer}.
ze_ipsec_error_notify_sent_total{type,protected} counts error notifications Ze sent, by
notify type and by whether the carrying message was encrypted.
ze_ipsec_error_notify_suppressed_total{reason} counts the ones a guard stopped, by the
name of that guard.
Three more counters report the COOKIE challenge described under Denial-of-service protection. ze_ipsec_cookie_challenges_total{peer} counts the challenges Ze issues, ze_ipsec_cookie_verify_failures_total{peer} counts inbound cookies that did not verify, and ze_ipsec_sa_init_retries_total{peer,cause} counts the IKE_SA_INIT retries Ze sends, labeled cookie or invalid-ke-payload. A rising verify-failure count is either an attacker probing the half-open slot or a secret rotation catching an in-flight challenge. A rising retry count on the cookie cause is the signature of the forged-notify flood RFC 7296 Section 2.6 describes.
Two more gauges report kernel state. ze_ipsec_dataplane_sa_count{if_id} counts
SAs under each interface ID used by an installed Child SA.
ze_ipsec_dataplane_drift{peer} reads 1 when the kernel lacks an expected SA
identity. Kernel expiry or an external flush can produce drift while
ze_ipsec_tunnel_up still reports a successful install.
A failed or changing dataplane observation removes both gauge families' previous series. Absent series mean the observation is unknown, not that the kernel holds no SAs or that no drift exists. Peer removal also removes the peer's drift series and unused interface-ID series.
ze_ipsec_tunnel_up reads 1 only when the IKE SA is established and the Child SA is
installed in the dataplane. A tunnel whose ESP install the kernel refused reads
ze_ipsec_tunnel_up 0 and ze_ipsec_tunnel_degraded 1. Such a tunnel has a live
control plane and carries no encrypted traffic. Alert on the degraded gauge, because
the two gauges together separate a lost session from a session with no ESP.
The daemon logs child-sa: dataplane refused the ESP state, tunnel is degraded and carries no encrypted traffic when this happens.
Interop testing
The IKE implementation includes interop tests against strongSwan, from the Alpine 3.21 test
image. The infrastructure in test/interop-ipsec/ drives strongSwan containers as remote
IKE peers. Twenty-four scenarios run today:
| 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, and BGP routes exchanged with FRR over the tunnel |
| NAT traversal | Transport mode and tunnel mode with ESP in UDP 4500, each measuring what Ze's stack does with the inner TCP and UDP checksum after decapsulation (RFC 3948 Section 3.1.2) |
| Traffic selectors | Child SA rekey narrowing, an answer that narrows the initiator's proposal, ESN offered both ways, and a peer reload that narrows |
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.
Ze holds every gated MUST-level requirement extracted from RFC 7296 in
rfc/short/rfc7296.md. That is 222 of the summary's 227 rows, each proven in both
directions by RFC requirement: tagged tests. The remaining five rows are SHOULD-level and
ungated.
See also
- The generated configuration reference covers every IPsec and XFRM configuration leaf.
- Monitoring covers the health registry and operational visibility.
- Feature inventory shows the maturity of each IKE and IPsec capability.