When we introduced our ePDG solution in late 2025, the message was simple: VoWiFi belongs in the volte.io portfolio, and it runs cloud-native. In the year since, we have taken the ePDG significantly further – to the point where the implementation you see today is effectively a new one. This article explains what changed, then walks through what actually happens on the wire when a handset attaches to the ePDG.
Why This Matters for Operators
VoWiFi is no longer a nice-to-have. It is the difference between a subscriber who hangs up indoors and one who keeps using your network. The new volte.io ePDG is built to carry that traffic at scale, on commodity infrastructure, with the same operational model as the rest of our core.
Why We Rebuilt the ePDG
Four considerations drove the rebuild. Each reflects something we learned from operating other parts of the volte.io stack – the HSS, the IMS core, the charging function – under real-world conditions, and wanted to apply consistently to the ePDG.
1. One Unified Stack: Erlang + C
Event-driven signaling in Erlang/OTP, performance-critical data paths in C. Same supervisors, same metrics, same operational primitives as every other volte.io component.
2. Strict 3GPP Compliance
Every interface audited against the letter of the specification. The ePDG interoperates with commercial handsets out of the box – not only with a reference testbed.
3. Performance Through IPsec Offload
The data plane leaves encryption to the Linux kernel's XFRM subsystem, unlocking AES-NI on x86 and full SmartNIC offload on platforms that support it.
4. Horizontal Scaling with Shared State
Multi-replica Kubernetes deployment behind a LoadBalancer, with subscriber state shared through Redis. Capacity scales linearly. A pod failure is contained.
A Unified Software Stack: Erlang + C
The volte.io stack converges on two languages. Erlang/OTP handles everything that is event-driven, stateful, and protocol-heavy: Diameter peers, session management, SIP signaling, IKE state machines. C handles everything that is performance-critical or close to the kernel: packet processing, cryptographic primitives, tight inner loops.
The rebuilt ePDG fits this pattern cleanly. IKEv2 signaling, EAP-AKA and EAP-AKA' authentication, Diameter SWm/S6b exchanges, and GTP-C coordination all live in Erlang, sharing the same supervisor trees, metrics conventions, and operational primitives as the rest of our stack. The data plane leaves encryption and decryption to the Linux kernel's XFRM subsystem, where it can be accelerated in hardware when available.
For operators, the practical result is consistency: the ePDG logs the same way, exposes the same Prometheus metrics, responds to the same readiness probes, and follows the same Helm conventions as every other volte.io component.
Strict Standards Compliance
VoWiFi sits at the intersection of several dense 3GPP specifications. SWu between UE and ePDG follows TS 33.402 and a stack of IETF RFCs (7296, 5448, 4187, 5282, 3948). SWm to the AAA follows TS 29.273. S2b to the PGW follows TS 29.274. Each has details that matter on the wire – NAI prefixes, Diameter AVP vendor flags, GTPv2-C information-element instances, IKEv2 key derivation inputs – and getting any of them wrong silently breaks interoperability with real handsets.
Our rebuild audited every interface against the letter of the specification. The result is an ePDG that interoperates with commercial UEs out of the box, not only with a specific reference testbed.
Performance Through IPsec Offload
The data plane of the ePDG is not the place to spend CPU cycles. Encryption and decryption of ESP traffic happen in the Linux kernel via XFRM, with the userspace process limited to IKE signaling and GTP-U forwarding.
This architecture unlocks hardware offload wherever the platform supports it. On commodity x86 hosts, AES-NI handles the bulk cipher in a few cycles per block. On platforms with SmartNICs – the same NVIDIA/Mellanox ConnectX family we already use for IPsec offload on the IMS Gm interface – ESP transformation, encryption, integrity checking, and NAT-T UDP encapsulation can be pushed entirely to the NIC. The same hardware path that scales our P-CSCF now scales our ePDG.
Horizontal Scaling with Shared State
The rebuilt ePDG runs as a multi-replica Kubernetes deployment behind a shared LoadBalancer, with subscriber state shared across replicas through Redis. The IKE SA itself remains anchored to the pod that established it – the cryptographic state, the kernel XFRM policies, the ESP replay window – none of which can meaningfully migrate to another pod mid-session. What is shared is everything else: which subscribers are currently attached, which APNs they are using, charging counters, and the operational visibility that makes the deployment behave as a single logical ePDG rather than a collection of independent processes.
The benefits are operational rather than magical:
- Linear capacity scaling – more pods, more concurrent tunnels.
- Rolling upgrades without outage – new IKE_SA_INIT requests go to new pods while existing sessions drain gracefully on the old ones.
- Contained failures – only the sessions on the affected pod are impacted, and UEs re-attach within seconds against any surviving replica.
This is the difference between a solution that works in a demonstration cluster and one that is ready for production traffic.
The Architecture at a Glance
Before diving into the packet-by-packet flow, the diagram below shows how the ePDG sits in the network: between the subscriber's handset on untrusted WiFi and the trusted mobile core.
How the ePDG Authenticates a VoWiFi Subscriber
With the architectural context established, the rest of this article walks through what actually happens when a handset attaches to the ePDG. We follow a single authentication flow, from the first packet the UE sends to the moment the subscriber is fully authenticated and ready to get a bearer.
Three protocol layers interlock:
IKEv2 brings up the secure tunnel.
EAP-AKA or EAP-AKA' authenticates the subscriber end-to-end through the AAA to the HSS.
Diameter SWx fetches the authentication vector from the HSS.
Bearer establishment, GTP-C coordination with the PGW, and user-plane forwarding are covered in Part 2 of this series.
Step 1 – IKE_SA_INIT: Negotiating the Cryptographic Environment
The first exchange between the UE and the ePDG follows RFC 7296 §1.2. The UE, acting as IKE initiator, sends a message containing three payloads:
- An SA proposal listing the cryptographic transforms it is willing to use – encryption algorithm, integrity algorithm, pseudo-random function, Diffie-Hellman group.
- A KE payload with the UE's public Diffie-Hellman value.
- A nonce (
Ni), a random quantity used later in key derivation.
The ePDG responds with the mirror image: a chosen suite from the UE's proposals, its own DH public value, and its own nonce (Nr). From this point on, both sides can compute the same shared Diffie-Hellman secret and derive a common key for the IKE SA.
A Detail That Breaks Real Handsets
RFC 7296 §3.3.3 specifies that the Extended Sequence Numbers (ESN) transform is defined only for ESP and AH – that is, for Child SAs carrying user traffic – and not for the IKE SA itself. A strict implementation must accept IKE SA proposals without an ESN transform, and must not include one in its response. Handsets from every major vendor rely on this behavior.
The cryptographic suites that show up in practice today are narrower than the specification allows. AES-GCM-16-256 is common. AES-CBC-256 paired with HMAC-SHA-256-128 is common. PRFs are almost always HMAC-SHA-256 or HMAC-SHA-384. Diffie-Hellman groups 14 (2048-bit MODP) and 19 (256-bit ECP) are the ones to optimize for; a compliant ePDG should also accept groups 20 (384-bit ECP) and 31 (Curve25519) for future-proofing.
Step 2 – IKE_AUTH: Encrypted Signaling and Mutual Authentication
Once IKE_SA_INIT completes, every subsequent IKE message travels inside an encrypted SK payload. Two wire formats coexist:
AEAD mode (e.g. AES-GCM)
Encryption and integrity in a single primitive. A 4-byte salt derived from SK_e, concatenated with an 8-byte IV on the wire, forms the AEAD nonce. Authenticated data covers the IKE and SK headers.
Classical mode (AES-CBC + HMAC)
Random 16-byte IV, PKCS padding, explicit HMAC over header + IV + ciphertext. The HMAC is truncated to the algorithm-specific ICV length (16/24/32 bytes). ICV verification runs in constant time, before any attempt at decryption.
Real handsets pick the suite they want from what the ePDG offered, so a production-grade ePDG has to implement both paths correctly.
Key Derivation
The IKE SA keys come from the Diffie-Hellman shared secret through a carefully specified key-derivation function. RFC 7296 §2.14 defines it precisely:
SKEYSEED = prf(Ni | Nr, g^ir)
{ SK_d | SK_ai | SK_ar | SK_ei | SK_er | SK_pi | SK_pr }
= prf+(SKEYSEED, Ni | Nr | SPIi | SPIr)
The inclusion of the SPIs (SPIi and SPIr) in the second derivation is what binds the key material to exactly this IKE SA. Omitting them – or truncating the input in any way – produces keys that differ from the UE's by a byte or more, causing every subsequent HMAC check to fail with an integrity error that looks like a cryptographic problem but is actually an input-construction problem.
SK_d), two for integrity (SK_ai/SK_ar), two for encryption (SK_ei/SK_er), and two for AUTH payload construction (SK_pi/SK_pr).Identity Exchange and Tunnel Authentication
With encryption in place, the UE and ePDG identify themselves to each other.
The UE sends its identity (IDi) as a Network Access Identifier, constructed per TS 23.003 §19.3.2. For a subscriber with IMSI 262240000000003 in PLMN 262-24, it looks like:
0262240000000003@nai.epc.mnc024.mcc262.3gppnetwork.org
The leading 0 is not arbitrary – it signals the EAP method the UE wants to use (more on this below).
The ePDG sends its own identity (IDr) as a fully qualified domain name, also constructed per TS 23.003:
epdg.epc.mnc024.mcc262.3gppnetwork.org
It also sends a certificate binding that FQDN to a public key, and an AUTH payload signed with the corresponding private key. The signature covers a canonical representation of the IKE_SA_INIT exchange combined with a PRF output keyed by SK_pr:
AUTH = sign(IKE_SA_INIT_resp_bytes | Ni | prf(SK_pr, IDr'))
The UE verifies this signature against the certificate, closing the loop on mutual authentication of the tunnel endpoints.
At this point, the tunnel is cryptographically sound – but the subscriber has not yet been authenticated. That happens next.
Step 3 – EAP-AKA / EAP-AKA': Authenticating the Subscriber
Tunnel authentication proves that the UE and the ePDG are who they claim to be. It does not prove that there is a legitimate subscriber behind the UE. For that, we need to involve the USIM and, ultimately, the HSS.
The mechanism is EAP – Extensible Authentication Protocol – encapsulated inside IKE_AUTH messages. Two EAP methods are relevant for VoWiFi: EAP-AKA (RFC 4187) and EAP-AKA' (RFC 5448).
Method Selection from the NAI Prefix
The UE signals which method it wants to use through the first character of the NAI. TS 23.003 §19.3.2, together with RFC 4187 §4.1.1.6 and RFC 5448 §4.1.1.6, defines the convention:
| Prefix | Meaning |
|---|---|
0 | EAP-AKA, permanent identity |
1 | EAP-SIM, permanent identity (legacy) |
2 | EAP-AKA, pseudonym identity |
3 | EAP-SIM, pseudonym identity |
4 | EAP-AKA, fast re-authentication |
5 | EAP-SIM, fast re-authentication |
6 | EAP-AKA', permanent identity |
7 | EAP-AKA', pseudonym identity |
8 | EAP-AKA', fast re-authentication |
A Samsung handset attached to a German network typically uses prefix 0 – EAP-AKA with a permanent identity. A handset provisioned for EAP-AKA' uses prefix 6. Routing the subscriber through the correct EAP engine starts with reading this single character and nothing else.
The Authentication Flow
Once the method is selected, the exchange between the UE, the ePDG, the AAA server, and the HSS proceeds in a tightly choreographed sequence:
- Identity request. The ePDG sends an EAP-Request/Identity inside an IKE_AUTH response. The UE replies with an EAP-Response/Identity carrying the NAI.
- Diameter DER to the AAA. The ePDG forwards the EAP payload to the AAA server over the SWm interface via a Diameter-EAP-Request (DER), together with the subscriber's NAI, the session identifier, and the access network identity (
WLANfor VoWiFi per TS 24.302 §8.1.1.2). - SWx MAR to the HSS. The AAA server requests an authentication vector from the HSS via the Multimedia-Auth-Request, carrying
User-Name,SIP-Authentication-Scheme(EAP-AKAorEAP-AKA'), and – for EAP-AKA' – theAccess-Network-IdentifierAVP that binds the derived keys to the specific access network. - Vector generation at the HSS. The HSS, holding the subscriber's root key in its Authentication Centre, computes an authentication vector: a random challenge (
RAND), a network authentication token (AUTN), the expected response (XRES), and the confidentiality and integrity keys (CKandIK). For EAP-AKA', the HSS additionally derives access-network-bound keysCK'andIK'per TS 33.402 Annex A:(CK' | IK') = HMAC-SHA-256(CK | IK, 0x20 | ANID | L0 | (SQN ⊕ AK) | L1) - SWx MAA back to the AAA. The Multimedia-Auth-Answer returns the vector in a
SIP-Auth-Data-Itemgrouped AVP –SIP-AuthenticatecarriesRAND | AUTN,SIP-AuthorizationcarriesXRES,Confidentiality-KeycarriesCK/CK', andIntegrity-KeycarriesIK/IK'. - Challenge to the UE. The AAA server derives the EAP-layer keys (
K_encr,K_aut,MSK,EMSK) from the vector following RFC 5448 §3.3 (EAP-AKA') or RFC 4187 §7 (EAP-AKA), then constructs an EAP-Request/AKA-Challenge carryingRAND,AUTN, and a MAC computed withK_aut. The ePDG forwards it to the UE inside an IKE_AUTH response. - Challenge response from the UE. The USIM inside the UE verifies
AUTNagainst the network's identity and its own sequence-number state, computesRESfromRANDand the root key, and returns an EAP-Response/AKA-Challenge withRESand a MAC. - Response verification. The AAA server checks the MAC, then checks that
RESequalsXRES. If both match, authentication succeeds. The AAA server emits an EAP-Success, bundled with the Master Session Key (MSK) for the ePDG to use as keying material for the IPsec Child SA. - EAP-Success to the UE. The ePDG relays the EAP-Success to the UE inside the final IKE_AUTH response.
What the UE Has Now
The UE has cryptographic assurance that it is talking to a legitimate network. The network has cryptographic assurance that it is talking to a legitimate subscriber. Both sides share a Master Session Key they can use to establish the IPsec Child SA that will carry user-plane traffic.
What the UE does not have yet is an IP address inside the mobile core, a bearer to the PGW, or a path to the IMS. Those are established in the following exchange – covered in Part 2.
A Note on Implementation Rigor
One observation worth sharing, because it illustrates why strict specification-driven testing matters more than happy-path testing: the Erlang diameter library, when configured with {decode_format, list}, delivers Grouped AVPs as single-element lists wrapping the inner proplist. The SIP-Auth-Data-Item that carries the authentication vector therefore arrives as:
[[{'SIP-Authentication-Scheme', [<<"EAP-AKA'">>]},
{'SIP-Authenticate', [<<RAND_AUTN/binary>>]},
{'SIP-Authorization', [<<XRES/binary>>]},
{'Confidentiality-Key', [<<CK/binary>>]},
{'Integrity-Key', [<<IK/binary>>]}]]
This is a deliberate design choice in the library – arity-0..1 grouped AVPs are always wrapped – but it means that code reading these values has to peel one layer of list wrapping before looking up fields, or risk receiving empty binaries and silently producing keys that do not match the HSS's.
The specification says exactly what is on the wire. The library says exactly how it represents the wire format in memory. Production-grade code reconciles the two explicitly, rather than by accident.
Coming Up in Part 2
This article covered the authentication half of the VoWiFi attach: IKE tunnel establishment, EAP-AKA / EAP-AKA' over SWm, and SWx vector retrieval from the HSS. The subscriber is now authenticated, but not yet connected to anything.
Part 2 will cover what happens next:
- S2b to the PGW – how the ePDG uses GTP-C to request a bearer, negotiates the PDN type, and receives the IP address, P-CSCF, and DNS servers the UE needs.
- Child SA installation – how IPsec keys are derived from the MSK, how kernel XFRM state is programmed, and how NAT traversal is handled.
- User-plane forwarding – how decrypted uplink packets become GTP-U toward the PGW, and how GTP-U downlink becomes encrypted ESP back to the UE.
- Multi-pod operation – what Redis-backed shared state actually does (and does not) provide, why IKE SAs are pod-anchored by nature, and how LoadBalancer session affinity keeps each UE pinned to its pod for the duration of its session.
From Feature to Foundation
VoWiFi started as a feature in our portfolio. After a year of operational experience and a ground-up rebuild, it is now foundational infrastructure: carrier-grade, standards-compliant, Kubernetes-native, and ready for production traffic. If you are evaluating VoWiFi for an MVNO, a private network, or a modernization program, we would be happy to show you how it runs.