Diameter Security
OmniDRA provides a comprehensive suite of Diameter security modules aligned with GSMA FS.19 (Diameter Interconnect Security, v10.0) and GSMA FS.21 (Interconnect Signalling Security Recommendations, v12.0). These modules protect the network against signalling-level attacks on Diameter interconnect interfaces.
Each module is independently configurable, can be enabled or disabled without affecting the others, and emits its own telemetry events for monitoring and alerting.
Architecture Overview
Processing Order
Inbound Diameter requests pass through the security modules in the following order. A message rejected at any stage is never forwarded to subsequent stages.
| Order | Module | Direction | Purpose |
|---|---|---|---|
| 1 | Rate Limiter | Inbound | Volumetric flood protection |
| 2 | Diameter Firewall | Inbound | FS.19 protocol and content filtering |
| 3 | AVP Sanitizer | Inbound | AVP validation and stripping |
| 4 | Routing Engine | - | Standard Diameter routing |
| 5 | Topology Hiding | Outbound | Network topology concealment |
Actions
All security modules support configurable actions when a violation is detected. Actions are configured per-module (and per-category for the Diameter Firewall).
| Action | Behaviour | Diameter Result |
|---|---|---|
{:error, 3002} | Respond with a Diameter error answer | DIAMETER_UNABLE_TO_DELIVER |
{:error, 3004} | Respond with a Diameter error answer | DIAMETER_TOO_BUSY |
{:error, 3007} | Respond with a Diameter error answer | DIAMETER_APPLICATION_UNSUPPORTED |
:drop | Silently discard the message | No response sent |
:log_only | Log the violation but allow the message through | Message continues |
Diameter Firewall
The Diameter Firewall implements the four filtering layers defined in GSMA FS.19 Section 3.3 and the protocol-agnostic packet categorisation from FS.21 Section 7. Messages are evaluated through each layer in order; a violation at any layer stops further processing.
Low-Layer Format Filtering
GSMA Reference: FS.19 Section 3.3.4, FS.21 Section 7.3.1
Low-layer filtering detects protocol-level violations and basic anti-spoofing attempts without needing to understand upper-layer application semantics. This layer catches malformed messages before they reach deeper inspection.
Checks performed:
| Check | FS.19 Reference | Description |
|---|---|---|
| AVP Duplication | Section 3.3.4, 4.8.1 | Detects duplicate instances of AVPs that must appear at most once (e.g. Origin-Host, Origin-Realm). Prevents AVP doubling evasion attacks. |
| Session-Id Ordering | Section 3.3.4 | Validates that Session-Id (AVP 263) is the first AVP in the message, per RFC 6733 Section 8.8. |
| Destination-Host in Answers | Section 3.3.4 | Rejects answer messages that contain a Destination-Host AVP, which is a protocol violation that may indicate filter evasion. |
| Mandatory AVP Validation | Section 3.3.5 | Validates that required AVPs are present for each command code (e.g. ULR must contain User-Name and Visited-PLMN-Id per 3GPP TS 29.272 Section 7.2.3). |
low_layer: %{
enabled: true,
action: {:error, 3002},
# AVP codes that must not appear more than once (FS.19 Section 3.3.4)
# 264 = Origin-Host, 296 = Origin-Realm, 283 = Destination-Realm, 293 = Destination-Host
single_instance_avps: [264, 296, 283, 293],
# Session-Id must be the first AVP (RFC 6733 Section 8.8)
enforce_session_id_first: true,
# Answer messages must not contain Destination-Host (FS.19 Section 3.3.4)
reject_destination_host_in_answers: true,
# Mandatory AVPs per command code (3GPP TS 29.272)
# 1 = User-Name (IMSI), 1407 = Visited-PLMN-Id, 264 = Origin-Host, 296 = Origin-Realm
mandatory_avps: %{
316 => [1, 1407, 264, 296],
318 => [1, 1407, 264, 296]
}
}
Category 1 — Interface-Unauthorised Packet Filtering
GSMA Reference: FS.19 Section 3.3.5, FS.21 Section 7.2.1
Category 1 filtering ensures that only authorised Diameter Application IDs and Command Codes are accepted on each interconnect interface. This prevents external access to internal-only interfaces (e.g. blocking Sh commands over an S6a interface) and enforces that roaming partners only send message types covered by their roaming agreements.
The whitelist approach follows the FS.19 recommendation: block all Diameter messages except those explicitly required for a given interface.
Whitelists can be configured globally (applying to all peers) or per-peer, allowing different roaming partners to have different permitted message sets.
category_1: %{
enabled: true,
action: {:error, 3007},
whitelists: %{
# Per-peer whitelist — restrict this partner to ULR and AIR only (FS.19 Section 3.3.5)
"restricted-partner.roaming.com" => %{
16_777_251 => [316, 318]
},
# Default whitelist — standard S6a commands permitted for all other peers
all: %{
# S6a/S6d (FS.19 Section 3.3.5, Table 2)
16_777_251 => [316, 317, 318, 319, 320, 321, 323],
# S13 — ME Identity Check (FS.19 Section 3.3.5)
16_777_252 => [324],
# S6c — SMS via HSS (FS.19 Section 3.3.5.1)
16_777_312 => [8388647, 8388648],
# SGd — SMS via MME (FS.19 Section 3.3.5.2)
16_777_313 => [8388645, 8388646]
}
}
}
Common S6a/S6d Whitelist:
| Command Code | Name | Direction | Reference |
|---|---|---|---|
| 316 | Update-Location-Request/Answer | MME → HSS | 3GPP TS 29.272 §7.2.3 |
| 317 | Cancel-Location-Request/Answer | HSS → MME | 3GPP TS 29.272 §7.2.7 |
| 318 | Authentication-Information-Request/Answer | MME → HSS | 3GPP TS 29.272 §7.2.5 |
| 319 | Insert-Subscriber-Data-Request/Answer | HSS → MME | 3GPP TS 29.272 §7.2.9 |
| 320 | Delete-Subscriber-Data-Request/Answer | HSS → MME | 3GPP TS 29.272 §7.2.11 |
| 321 | Purge-UE-Request/Answer | MME → HSS | 3GPP TS 29.272 §7.2.13 |
| 323 | Notify-Request/Answer | MME → HSS | 3GPP TS 29.272 §7.2.15 |
Common Application IDs for roaming interconnect (FS.19 Section 3.3.5):
| Application ID | Interface | Reference |
|---|---|---|
| 16777251 | S6a/S6d | 3GPP TS 29.272 |
| 16777252 | S13 | 3GPP TS 29.272 |
| 16777312 | S6c | 3GPP TS 29.338 |
| 16777313 | SGd | 3GPP TS 29.338 |
| 16777255 | SLg | 3GPP TS 29.172 |
| 16777267 | S9 | 3GPP TS 29.215 |
Category 2 — Home-Network Packet Filtering
GSMA Reference: FS.19 Section 3.3.6, FS.21 Section 7.2.2
Category 2 filtering protects home subscribers from being targeted by messages arriving from the interconnect. Messages on protected command codes are inspected for subscriber identity (IMSI in the User-Name AVP, MSISDN in AVP 701). If the identity matches a home subscriber prefix, the message is rejected — legitimate traffic for home subscribers should originate from within the home network, not from external peers.
This prevents attacks where an external entity sends location updates, subscriber data requests, or authentication queries targeting the operator's own subscribers.
# Top-level: define home subscriber prefixes
home_imsi_prefixes: ["31338", "31339"],
home_msisdn_prefixes: ["+1313"],
category_2: %{
enabled: true,
action: {:error, 3002},
# S6a command codes that carry subscriber identity (FS.19 Section 3.3.6, Table 12)
protected_command_codes: [316, 317, 318, 319, 320, 321, 323]
}
Category 3 — Plausible-Network Packet Filtering
GSMA Reference: FS.19 Section 3.3.7, FS.21 Section 7.2.3
Category 3 filtering detects implausible location changes by tracking the last-seen network for each subscriber (IMSI). When an Update-Location-Request arrives from a different visited network than the previous one, the time elapsed is compared against a configurable threshold. A realm change occurring faster than physically possible indicates a potential attack (e.g. spoofed location updates).
This module maintains stateful per-IMSI tracking using an ETS table with automatic cleanup of stale entries (entries older than 24 hours are removed periodically).
Checks performed:
| Check | FS.19 Reference | Description |
|---|---|---|
| Previous Location Check | Section 3.3.7.1 | Compares the Origin-Realm of the current ULR with the last-seen Origin-Realm for that IMSI |
| Velocity/Time Check | Section 3.3.7.2 | Flags realm changes that occur within a configurable minimum time window |
category_3: %{
enabled: true,
# Start with log_only to observe patterns before enforcing (FS.19 Section 3.3.7)
action: :log_only,
# Maximum plausible velocity in km/h (FS.19 Section 3.3.7.2)
max_velocity_kmh: 1200,
# Minimum seconds between ULRs from different realms for the same IMSI
min_time_between_updates_seconds: 2
}
Configuration
The Diameter Firewall is enabled at the top level, with each filtering layer configured independently. The config snippets shown above within each category section are combined under a single module_diameter_firewall key:
config :dra,
module_diameter_firewall: %{
enabled: true,
home_imsi_prefixes: ["31338", "31339"],
home_msisdn_prefixes: ["+1313"],
low_layer: %{ ... }, # See Low-Layer Format Filtering above
category_1: %{ ... }, # See Category 1 above
category_2: %{ ... }, # See Category 2 above
category_3: %{ ... } # See Category 3 above
}
Top-Level Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enabled | Boolean | Yes | false | Enable or disable the entire Diameter Firewall module. When false, all messages pass through without inspection. |
children | List | No | [] | Processes started under the DRA supervisor when the module is enabled. For the firewall set to [DRA.Router.Modules.DiameterFirewall.LocationTracker], which is required for Category 3 stateful (velocity) tracking. |
home_imsi_prefixes | List | No | [] | List of IMSI prefix strings identifying home subscribers. Used by Category 2 filtering. Example: ["31338", "31339"]. |
home_msisdn_prefixes | List | No | [] | List of MSISDN prefix strings identifying home subscribers. Used by Category 2 filtering. Example: ["+1313"]. |
Low-Layer Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enabled | Boolean | No | true | Enable or disable low-layer format filtering. |
action | Action | No | {:error, 3002} | Action to take when a low-layer violation is detected. |
single_instance_avps | List | No | [264, 296, 283, 293] | AVP codes that must not appear more than once in a message. Default covers Origin-Host (264), Origin-Realm (296), Destination-Realm (283), and Destination-Host (293). |
enforce_session_id_first | Boolean | No | true | Reject messages where Session-Id is not the first AVP. |
reject_destination_host_in_answers | Boolean | No | true | Reject answer messages that contain a Destination-Host AVP. |
mandatory_avps | Map | No | %{} | Map of command code to list of required AVP codes. Example: %{316 => [1, 1407, 264, 296]} requires User-Name, Visited-PLMN-Id, Origin-Host, and Origin-Realm in ULR messages. |
Category 1 Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enabled | Boolean | No | true | Enable or disable Category 1 filtering. |
action | Action | No | {:error, 3007} | Action to take when a Category 1 violation is detected. Default responds with DIAMETER_APPLICATION_UNSUPPORTED. |
whitelists | Map | Yes | - | Map of peer hostname (or :all) to permitted Application ID / Command Code combinations. The :all key provides a default whitelist. Peer-specific entries take priority. |
Category 2 Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enabled | Boolean | No | true | Enable or disable Category 2 filtering. |
action | Action | No | {:error, 3002} | Action to take when a home subscriber is targeted from interconnect. |
protected_command_codes | List | No | [] | Command codes that trigger home subscriber identity checks. Typically the S6a command codes that carry subscriber identity. |
Category 3 Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enabled | Boolean | No | false | Enable or disable Category 3 filtering. Requires the LocationTracker process to be running (started automatically by the supervisor when enabled). |
action | Action | No | :log_only | Action to take when a location plausibility check fails. Recommended to start with :log_only to tune thresholds before enforcing. |
max_velocity_kmh | Integer | No | 1200 | Maximum plausible velocity in km/h. Reserved for future geo-distance calculations. |
min_time_between_updates_seconds | Integer | No | 2 | Minimum time in seconds between location updates from different realms for the same IMSI. Realm changes faster than this are flagged. |
Telemetry Events
| Event | Description |
|---|---|
[:diameter, :firewall, :low_layer, :block] | Low-layer format violation detected and blocked |
[:diameter, :firewall, :category_1, :block] | Category 1 violation detected and blocked |
[:diameter, :firewall, :category_2, :block] | Category 2 violation detected and blocked |
[:diameter, :firewall, :category_3, :block] | Category 3 violation detected and blocked |
[:diameter, :firewall, :pass, :count] | Message passed all firewall checks |
All events include metadata: origin_host, application_id, command_code, application_name, command_name, and reason.
Topology Hiding
GSMA Reference: FS.19 Section 2.4, Section 3.4; FS.21 Section 3.6
The Topology Hiding module prevents internal network topology from being exposed to external peers. When Diameter messages traverse multiple internal nodes, they accumulate Route-Record AVPs and carry Origin-Host/Origin-Realm values that reveal internal hostnames, network structure, and node counts. This information can be used by attackers to map the internal network for targeted attacks.
Topology hiding operates on the outbound path — after routing decisions have been made, before messages are forwarded to the destination peer.
Features
| Feature | FS.19 Reference | Description |
|---|---|---|
| Route-Record Stripping | Section 2.4 | Removes all Route-Record AVPs (282) that reveal internal routing paths and node hostnames |
| Origin-Host Rewriting | Section 3.4 | Replaces the Origin-Host AVP with the DRA's own identity (or a custom value) on answer messages |
| Origin-Realm Rewriting | Section 3.4 | Optionally replaces the Origin-Realm AVP to hide internal realm structure |
| Per-Peer Control | - | Apply topology hiding selectively — only to external peers, or to all peers |
module_topology_hiding: %{
enabled: true,
# Strip Route-Record AVPs revealing internal paths (FS.19 Section 2.4)
strip_route_records: true,
# Rewrite Origin-Host on answers to hide internal node names (FS.19 Section 3.4)
rewrite_origin_host: %{enabled: true, replacement: :self},
# Optionally hide internal realm structure
rewrite_origin_realm: %{enabled: false, replacement: :self},
# Apply to all external peers, or list specific hostnames
external_peers: :all
}
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enabled | Boolean | Yes | false | Enable or disable the Topology Hiding module. |
strip_route_records | Boolean | No | true | Remove all Route-Record AVPs (code 282) from messages before forwarding to external peers. |
rewrite_origin_host | Map | No | See below | Controls Origin-Host rewriting on answer messages. |
rewrite_origin_realm | Map | No | See below | Controls Origin-Realm rewriting on answer messages. |
external_peers | :all or List | No | :all | Peers considered external. Topology hiding is only applied to messages destined for these peers. Use :all for interconnect deployments. Use a list of hostnames for selective application. |
Rewrite Parameters (apply to both rewrite_origin_host and rewrite_origin_realm):
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enabled | Boolean | No | varies | Enable rewriting for this AVP. Default is true for Origin-Host, false for Origin-Realm. |
replacement | :self or String | No | :self | Replacement value. :self uses the DRA's own identity from the diameter config (host.realm). A string value is used as-is. |
Telemetry Events
| Event | Description |
|---|---|
[:diameter, :topology_hiding, :route_record, :stripped] | Route-Record AVPs removed. Measurement includes count of AVPs stripped. |
[:diameter, :topology_hiding, :origin_host, :rewritten] | Origin-Host AVP rewritten |
[:diameter, :topology_hiding, :origin_realm, :rewritten] | Origin-Realm AVP rewritten |
Rate Limiter
GSMA Reference: FS.19 Section 3.4 (Availability / DoS Protection)
The Rate Limiter enforces per-peer message rate limits to protect against volumetric attacks and message flooding. It operates as the first security check in the pipeline — before any message parsing or content inspection — to shed excess load as early as possible.
Rate limits are tracked per-peer using a sliding window counter. Each peer has an independent counter that resets every second.
module_rate_limiter: %{
enabled: true,
# Default limit for all peers (FS.19 Section 3.4)
default_max_requests_per_second: 1000,
default_action: {:error, 3004},
# Per-peer overrides
peer_limits: %{
"high-volume-partner.roaming.com" => %{max_requests_per_second: 5000, action: {:error, 3004}},
"restricted-peer.roaming.com" => %{max_requests_per_second: 100, action: :drop}
}
}
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enabled | Boolean | Yes | false | Enable or disable the Rate Limiter module. |
children | List | No | [] | Processes started under the DRA supervisor when enabled. Set to [DRA.Router.Modules.RateLimiter] so the per-peer rate-counter process runs. |
default_max_requests_per_second | Integer | No | 1000 | Default maximum requests per second allowed from any single peer. |
default_action | Action | No | {:error, 3004} | Default action when a peer exceeds its rate limit. {:error, 3004} responds with DIAMETER_TOO_BUSY. |
peer_limits | Map | No | %{} | Map of peer hostname to override configuration. Peers not listed use the defaults. |
Per-Peer Override Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
max_requests_per_second | Integer | No | Inherits default | Maximum requests per second for this specific peer. |
action | Action | No | Inherits default | Action when this peer exceeds its rate limit. |
Telemetry Events
| Event | Description |
|---|---|
[:diameter, :rate_limiter, :throttled] | A message was rate limited. Measurements include current_rate and limit. |
[:diameter, :rate_limiter, :allowed] | A message was within rate limits. |
AVP Sanitizer
GSMA Reference: FS.19 Section 3.3.4, 4.8.1, 4.8.2; FS.21 Section 3.6
The AVP Sanitizer validates and sanitizes Diameter AVPs at the interconnect boundary. It addresses the FS.21 Section 3.6 recommendations for handling protocol-level manipulation attacks that operate below the level of the FS.19 filtering categories.
Features
| Feature | GSMA Reference | Description |
|---|---|---|
| Unknown Vendor AVP Stripping | FS.21 Section 3.6 | Removes AVPs from non-whitelisted vendors. Prevents injection of proprietary AVPs that may trigger unintended behaviour in backend nodes. |
| Grouped AVP Nesting Depth | FS.21 Section 3.6 | Enforces a maximum nesting depth for grouped AVPs. Prevents stack overflow attacks using deeply nested AVP structures. |
module_avp_sanitizer: %{
enabled: true,
action: {:error, 3002},
# Only allow standard vendor AVPs on interconnect (FS.21 Section 3.6)
# 0 = IETF, 10415 = 3GPP, 13019 = ETSI, 5535 = 3GPP2
allowed_vendor_ids: [0, 10415, 13019, 5535],
strip_unknown_vendor_avps: true,
# Prevent stack overflow via deeply nested grouped AVPs (FS.21 Section 3.6)
max_avp_nesting_depth: 10
}
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enabled | Boolean | Yes | false | Enable or disable the AVP Sanitizer module. |
action | Action | No | {:error, 3002} | Action to take when a nesting depth violation is detected. Vendor stripping silently removes offending AVPs without blocking the message. |
allowed_vendor_ids | List | No | [0, 10415, 13019, 5535] | List of vendor IDs permitted on interconnect. AVPs from other vendors are stripped. |
strip_unknown_vendor_avps | Boolean | No | true | Enable stripping of AVPs from vendors not in the allowed_vendor_ids list. |
max_avp_nesting_depth | Integer | No | 10 | Maximum permitted depth of grouped AVP nesting. Messages exceeding this are subject to the configured action. |
Common Vendor IDs:
| Vendor ID | Organisation | Notes |
|---|---|---|
| 0 | IETF | Standard Diameter AVPs defined in RFCs |
| 10415 | 3GPP | All 3GPP-defined AVPs (S6a, Gx, Rx, etc.) |
| 13019 | ETSI | ETSI-defined AVPs |
| 5535 | 3GPP2 | 3GPP2-defined AVPs (CDMA interworking) |
Telemetry Events
| Event | Description |
|---|---|
[:diameter, :avp_sanitizer, :unknown_vendor, :stripped] | One or more AVPs from unknown vendors were stripped |
[:diameter, :avp_sanitizer, :nesting_depth, :violation] | A message exceeded the maximum AVP nesting depth |
[:diameter, :avp_sanitizer, :pass, :count] | Message passed all sanitization checks |
Deployment Recommendations
Recommended Enablement Order
When deploying the security modules for the first time, enable them incrementally to avoid disrupting live traffic:
- Rate Limiter — Start with generous limits and monitor traffic patterns. Tighten limits once baseline rates are understood.
- AVP Sanitizer — Low risk of false positives. Enable stripping and nesting checks.
- Diameter Firewall (Category 1) — Define whitelists based on roaming agreements. Start with known-good Application ID / Command Code combinations.
- Diameter Firewall (Low-Layer) — Enable protocol conformance checks.
- Diameter Firewall (Category 2) — Configure home IMSI/MSISDN prefixes.
- Topology Hiding — Enable Route-Record stripping first, then Origin-Host rewriting.
- Diameter Firewall (Category 3) — Enable with
:log_onlyaction to observe location patterns before enforcing.
Defence in Depth
These modules implement the defence in depth principle described in FS.19 Section 3.4. Each layer addresses a different class of attack:
| Attack Class | Primary Defence | Secondary Defence |
|---|---|---|
| Volumetric DoS | Rate Limiter | Diameter Firewall (all categories) |
| Interface Abuse | Category 1 | AVP Sanitizer |
| Home Subscriber Targeting | Category 2 | Category 1 (interface restriction) |
| Location Spoofing | Category 3 | Category 2 (home sub check) |
| Topology Discovery | Topology Hiding | - |
| AVP Injection | AVP Sanitizer | Low-Layer Filtering |
| Protocol Evasion (AVP Doubling) | Low-Layer Filtering | AVP Sanitizer |
GSMA Document Cross-Reference
| Module / Feature | FS.19 v10.0 | FS.21 v12.0 |
|---|---|---|
| Low-Layer Format Filtering | Section 3.3.4 | Section 7.3.1 |
| Category 1 Filtering | Section 3.3.5, Annex B.3.3 | Section 7.2.1 |
| Category 2 Filtering | Section 3.3.6, Annex B.3.4 | Section 7.2.2, Section 16 |
| Category 3 Filtering | Section 3.3.7, Annex B.3.5 | Section 7.2.3 |
| Topology Hiding | Section 2.4, Section 3.4 | Section 3.6 |
| Rate Limiting | Section 3.4 | - |
| AVP Sanitization | Section 4.8.1, 4.8.2 | Section 3.6 |
| Defence in Depth | Section 3.4 | Section 3.15 |
| Filtering Categories (Protocol-Agnostic) | Annex A | Section 7 |