From Feature to Foundation: Our Production-Ready ePDG

A year after adding VoWiFi to the volte.io stack, we rebuilt our Evolved Packet Data Gateway from the ground up. Here is why – and how it authenticates a subscriber, step by step.

← Back to News Production-ready ePDG: UE over WiFi establishing a secure IPsec tunnel to the volte.io ePDG and IMS core, with step-by-step subscriber authentication

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:

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.

ePDG architecture overview: UE on WiFi connects through an IPsec tunnel to the ePDG, which coordinates with the AAA server, HSS, and PGW to provide VoWiFi access to the IMS core
ePDG architecture: the UE establishes an IPsec tunnel to the ePDG, which authenticates the subscriber via SWm/SWx to the AAA and HSS, then opens a bearer to the PGW for onward routing to the IMS 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.

VoWiFi authentication flow: sequence diagram showing IKE_SA_INIT, IKE_AUTH with EAP-AKA', Diameter DER to AAA and MAR/MAA between AAA and HSS, followed by EAP challenge, response, and EAP-Success
End-to-end authentication flow between the UE, ePDG, AAA server, and HSS.

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:

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.

IKEv2 key derivation pipeline: nonces Ni and Nr combined with the Diffie-Hellman shared secret produce SKEYSEED, which is expanded via prf+ into the seven IKE SA keys SK_d, SK_ai, SK_ar, SK_ei, SK_er, SK_pi, SK_pr
Seven keys emerge: one for deriving Child SA keys (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
0EAP-AKA, permanent identity
1EAP-SIM, permanent identity (legacy)
2EAP-AKA, pseudonym identity
3EAP-SIM, pseudonym identity
4EAP-AKA, fast re-authentication
5EAP-SIM, fast re-authentication
6EAP-AKA', permanent identity
7EAP-AKA', pseudonym identity
8EAP-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:

  1. 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.
  2. 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 (WLAN for VoWiFi per TS 24.302 §8.1.1.2).
  3. 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-AKA or EAP-AKA'), and – for EAP-AKA' – the Access-Network-Identifier AVP that binds the derived keys to the specific access network.
  4. 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 (CK and IK). For EAP-AKA', the HSS additionally derives access-network-bound keys CK' and IK' per TS 33.402 Annex A:
    (CK' | IK') = HMAC-SHA-256(CK | IK,
                               0x20 | ANID | L0 | (SQN ⊕ AK) | L1)
  5. SWx MAA back to the AAA. The Multimedia-Auth-Answer returns the vector in a SIP-Auth-Data-Item grouped AVP – SIP-Authenticate carries RAND | AUTN, SIP-Authorization carries XRES, Confidentiality-Key carries CK/CK', and Integrity-Key carries IK/IK'.
  6. 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 carrying RAND, AUTN, and a MAC computed with K_aut. The ePDG forwards it to the UE inside an IKE_AUTH response.
  7. Challenge response from the UE. The USIM inside the UE verifies AUTN against the network's identity and its own sequence-number state, computes RES from RAND and the root key, and returns an EAP-Response/AKA-Challenge with RES and a MAC.
  8. Response verification. The AAA server checks the MAC, then checks that RES equals XRES. 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.
  9. 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:

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.

Ready to Transform Your Network?

Contact us today and discover how volte.io can power your communications infrastructure.

Contact Us