subscriber.auth.radius v0.5.0
Authentication and accounting client for RADIUS servers. Sends Access-Request packets, translates Access-Accept AVPs into internal subscriber attributes, and handles accounting (Start/Interim-Update/Stop).
Supports ordered server failover with dead server detection and a three-tier attribute mapping system.
| Field | Type | Description | Default |
|---|---|---|---|
servers |
Server[] | Ordered list of RADIUS servers | required |
auth_port |
int | Authentication port | 1812 |
acct_port |
int | Accounting port | 1813 |
timeout |
duration | Per-attempt timeout | 3s |
retries |
int | Per-server retry count | 3 |
nas_identifier |
string | NAS-Identifier AVP. Falls back to aaa.nas_identifier |
|
nas_ip |
string | NAS-IP-Address AVP. Falls back to aaa.nas_ip |
|
nas_port_type |
string | NAS-Port-Type AVP value | Virtual |
nas_port_id_format |
string | NAS-Port-Id template. See placeholders | {interface}:{svlan}.{cvlan} |
dead_time |
duration | How long to skip a dead server | 30s |
dead_threshold |
int | Consecutive failures before marking dead | 3 |
vrf |
string | Default VRF for outbound auth/accounting traffic. Per-server vrf overrides this. |
|
source_ip |
string | Default IPv4 source address for outbound auth/accounting. Per-server source_ip overrides this. |
|
source_ipv6 |
string | Default IPv6 source address for outbound auth/accounting. Per-server source_ipv6 overrides this. |
|
coa_listener |
CoAListener | CoA/DM UDP listener settings (port, VRF, bind address). | |
coa_clients |
CoAClient[] | Authorized CoA senders. | |
coa_replay_window |
int | CoA Event-Timestamp replay window in seconds. Set to 0 to disable. | 300 |
response_mappings |
ResponseMapping[] | Custom Tier 3 attribute mappings | |
request_mappings |
RequestMapping[] | Custom attributes added to Access-Request | |
accounting_mappings |
RequestMapping[] | Custom attributes added to Accounting-Request |
VRF and Source Address Cascade
vrf / source_ip / source_ipv6 at the plugin level are the default control-plane binding for outbound traffic to every server. A per-Server entry may override any of those fields individually. Fields are merged field-by-field, so setting source_ip on one server does not blank out an inherited vrf.
The CoA listener has its own binding (CoAListener) and does not inherit from the plugin-level defaults. Incoming CoA arrives from RADIUS, not to it, so the cascade does not apply.
Server
| Field | Type | Description | Example |
|---|---|---|---|
host |
string | RADIUS server hostname or IP | 10.1.1.1 |
secret |
string | Shared secret | ${RADIUS_SECRET} |
vrf |
string | Override plugin-level vrf for this server. |
mgmt-vrf |
source_ip |
string | Override plugin-level source_ip for this server (IPv4). |
10.0.0.1 |
source_ipv6 |
string | Override plugin-level source_ipv6 for this server (IPv6). |
2001:db8::1 |
Servers are tried in order. On timeout or error, the next server is attempted. After dead_threshold consecutive failures, a server is marked dead and skipped for dead_time.
The binding fields are merged with the plugin-level defaults field by field: a server can override a single field while inheriting the rest. An IPv6 host resolves source binding through source_ipv6; an IPv4 host through source_ip.
Attribute Mapping
The provider uses a three-tier attribute mapping system. All tiers are evaluated on every Access-Accept response.
Tier 1 — RFC Standard (always active)
| RADIUS AVP | Type | Internal Attribute |
|---|---|---|
| Framed-IP-Address | 8 | ipv4_address |
| Framed-IP-Netmask | 9 | ipv4_netmask |
| Framed-Route | 22 | routed_prefix |
| Session-Timeout | 27 | session_timeout |
| Idle-Timeout | 28 | idle_timeout |
| Acct-Interim-Interval | 85 | acct_interim_interval |
| Framed-Pool | 88 | pool |
| Framed-IPv6-Prefix | 97 | ipv6_wan_prefix |
| Framed-IPv6-Pool | 100 | iana_pool |
| Delegated-IPv6-Prefix | 123 | ipv6_prefix |
| Framed-IPv6-Address | 168 | ipv6_address |
| Delegated-IPv6-Prefix-Pool | 171 | pd_pool |
Tier 2 — Common Vendor Defaults (always active)
| Vendor | Vendor ID | Attribute | Type | Internal Attribute |
|---|---|---|---|---|
| Microsoft | 311 | MS-Primary-DNS-Server | 28 | dns_primary |
| Microsoft | 311 | MS-Secondary-DNS-Server | 29 | dns_secondary |
Additional vendor attributes will be added over time based on deployment feedback. Use Tier 3 custom mappings for vendor-specific attributes not yet covered.
Tier 3 — Custom Mappings (config-driven)
For vendor-specific attributes not covered by Tier 1 or 2, define custom mappings using raw vendor_id and vendor_type integers.
Response Mappings
| Field | Type | Description | Example |
|---|---|---|---|
vendor_id |
int | RADIUS vendor ID | 9 (Cisco) |
vendor_type |
int | Vendor sub-attribute type | 1 |
internal |
string | Internal attribute name to set | vrf |
extract |
string | Optional regex with capture group | ip:vrf-id=(.+) |
When extract is set, the VSA string value is matched against the regex and the first capture group is used as the attribute value. Without extract, the full VSA string value is used.
Finding vendor IDs and types
Use FreeRADIUS dictionary files as a reference for vendor ID and attribute type numbers. The layeh/radius project provides a radius-dict-gen tool for converting dictionaries to Go code if needed for development.
Access-Request AVPs
The following AVPs are included in every Access-Request:
| AVP | Type | Source |
|---|---|---|
| User-Name | 1 | AAA policy username |
| User-Password / CHAP-Password | 2/3 | Subscriber credentials |
| NAS-IP-Address | 4 | nas_ip config |
| Service-Type | 6 | 2 (Framed) for PPPoE/IPoE/L2TP, 5 (Outbound) otherwise |
| Calling-Station-Id | 31 | Subscriber MAC |
| NAS-Identifier | 32 | nas_identifier config |
| Called-Station-Id | 30 | Circuit ID (if available) |
| Acct-Session-Id | 44 | Session accounting ID |
| Event-Timestamp | 55 | Current time |
| NAS-Port | 5 | Access sub-interface sw_if_index |
| NAS-Port-Type | 61 | nas_port_type config |
| NAS-Port-Id | 87 | Formatted per nas_port_id_format |
NAS-Port-Id format
nas_port_id_format builds NAS-Port-Id (attribute 87) from these placeholders. The same format is used for Access-Request and Accounting.
| Placeholder | Value |
|---|---|
{interface} |
Access sub-interface name, e.g. eth1.100 (the .100 is the sub-interface index, not the S-VLAN) |
{svlan} |
Outer (S-)VLAN tag |
{cvlan} |
Inner (C-)VLAN tag |
{subscriber_index} |
Subscriber dataplane sw_if_index. Accounting only; 0 in Access-Request (the subscriber interface is created after authentication) |
Default: {interface}:{svlan}.{cvlan} → e.g. eth1.100:100.2.
Request Mappings
request_mappings adds attributes to the Access-Request, sourced from the subscriber's internal attributes. Each entry takes internal plus either a radius_attr name or a vendor_id/vendor_type pair. A mapping only emits when its internal attribute is set for that session.
Internal attributes available as sources:
| Internal | Source | Access types |
|---|---|---|
circuit_id |
Agent Circuit ID — DHCP Option 82 sub-option 1 / PPPoE TR-101 tag | IPoE, PPPoE |
remote_id |
Agent Remote ID — DHCP Option 82 sub-option 2 / PPPoE TR-101 tag | IPoE, PPPoE |
hostname |
Client hostname — DHCP Option 12 | IPoE |
request_mappings:
- internal: remote_id
vendor_id: 3561
vendor_type: 2
Accounting
Accounting is automatic when the RADIUS provider is the active auth_provider. The AAA component calls Start/Interim-Update/Stop on session lifecycle events.
Accounting-Request packets include:
| AVP | Type | Description |
|---|---|---|
| Acct-Status-Type | 40 | Start (1), Interim-Update (3), Stop (2) |
| Acct-Authentic | 45 | RADIUS (1) |
| Acct-Delay-Time | 41 | Seconds the record has been delayed (0) |
| Acct-Session-Id | 44 | Session accounting ID |
| User-Name | 1 | Subscriber username |
| Calling-Station-Id | 31 | Subscriber MAC |
| NAS-Identifier | 32 | NAS identifier |
| NAS-IP-Address | 4 | NAS IP |
| Called-Station-Id | 30 | Circuit ID (if available) |
| Service-Type | 6 | Framed (2) for subscriber access types |
| NAS-Port | 5 | Access sub-interface sw_if_index |
| NAS-Port-Type | 61 | From nas_port_type |
| NAS-Port-Id | 87 | Formatted per nas_port_id_format |
| Acct-Session-Time | 46 | Session duration (seconds) |
| Acct-Input-Octets | 42 | RX bytes (lower 32 bits) |
| Acct-Output-Octets | 43 | TX bytes (lower 32 bits) |
| Acct-Input-Gigawords | 52 | RX bytes (upper 32 bits), when non-zero |
| Acct-Output-Gigawords | 53 | TX bytes (upper 32 bits), when non-zero |
| Acct-Input-Packets | 47 | RX packets |
| Acct-Output-Packets | 48 | TX packets |
| Framed-IP-Address | 8 | Assigned IPv4 address |
| Framed-IPv6-Prefix | 97 | Assigned IPv6 WAN prefix |
| Delegated-IPv6-Prefix | 123 | Delegated IPv6 prefix |
| Event-Timestamp | 55 | Current time |
Accounting Mappings
accounting_mappings adds vendor or standard attributes to Accounting-Request packets, sourced from the subscriber's internal attributes. Each entry takes internal plus either a radius_attr name or a vendor_id/vendor_type pair, the same as request_mappings. A mapping only emits when its internal attribute is set on the session.
accounting_mappings:
- internal: circuit_id
vendor_id: 3561
vendor_type: 1
- internal: vrf
vendor_id: 4874
vendor_type: 1
CoA / Disconnect-Message (RFC 5176)
osvbng can receive RADIUS Change of Authorization (CoA) and Disconnect-Message (DM) requests from authorized RADIUS servers. CoA changes subscriber attributes mid-session; Disconnect tears down a session.
CoA is implemented as a plugin component (subscriber.auth.radius.coa) that starts automatically when coa_clients is configured. It uses the event bus to communicate with subscriber components - no direct coupling.
If coa_clients is empty or absent, the CoA listener is not started. Top-level fields are documented in the main config table above; this section describes the CoA Listener and CoA Client sub-blocks.
CoA Listener
coa_listener configures the UDP socket osvbngd binds to for incoming CoA/DM requests. It does not inherit the plugin-level auth/accounting binding, so the listener can live in a different VRF and on a different source address than outbound auth traffic.
| Field | Type | Description | Default |
|---|---|---|---|
port |
int | UDP port to listen for CoA/DM requests | 3799 |
vrf |
string | Linux VRF master to bind the listener to. | |
source_ip |
string | IPv4 address to bind the listener to. Empty = wildcard (0.0.0.0). |
CoA-ACK / CoA-NAK / Disconnect-ACK responses are sent back over this same socket, so the reply path follows the listener's binding. There is no separate per-client reply binding.
CoA Client
| Field | Type | Description | Example |
|---|---|---|---|
host |
string | IP address or CIDR range of the CoA sender | 10.1.1.1 or 10.244.0.0/16 |
secret |
string | Shared secret for this client | ${COA_SECRET} |
The host field accepts bare IP addresses (matched as /32) or CIDR ranges. CIDR support is useful for Kubernetes deployments where CoA sources have ephemeral pod IPs within a known network:
coa_clients:
- host: "10.244.0.0/16" # K8s pod CIDR
secret: "${COA_SECRET}"
For environments where source IP filtering is not practical, use 0.0.0.0/0 to accept from any source. Message-Authenticator (HMAC-MD5) remains the authentication boundary:
coa_clients:
- host: "0.0.0.0/0"
secret: "${COA_SECRET}"
Supported Operations
CoA-Request: Changes subscriber session attributes. The request must contain at least one session identifier (Acct-Session-Id, User-Name, Framed-IP-Address, or Framed-IPv6-Address) and one or more mutable attributes. Attributes that require session teardown (IP addresses, VRF, pools) are rejected.
Disconnect-Request: Tears down a subscriber session. The request must contain only session identification attributes. The session is fully deprovisioned from the VPP dataplane.
Error-Cause Values
| Code | Name | When |
|---|---|---|
| 201 | Residual Session Context Removed | Disconnect-ACK |
| 401 | Unsupported Attribute | CoA attribute not in the mutable set |
| 402 | Missing Attribute | No session identifier or no mutable attributes |
| 403 | NAS Identification Mismatch | NAS-Identifier doesn't match config |
| 404 | Invalid Request | Disconnect-Request contains non-identification attributes |
| 503 | Session Context Not Found | Target session not found |
| 506 | Resources Unavailable | Worker pool overflow or internal error |
| 507 | Request Initiated | Service-Type = Authorize Only (not supported) |
Show Commands
| Path | API Endpoint | Description |
|---|---|---|
aaa.radius.servers |
/api/show/aaa/radius/servers |
Per-server auth/acct statistics |
aaa.radius.coa |
/api/show/aaa/radius/coa |
Per-client CoA/DM statistics |
Example
Minimal
aaa:
auth_provider: radius
nas_identifier: osvbng
plugins:
subscriber.auth.radius:
servers:
- host: 10.1.1.1
secret: testing123
nas_ip: 10.0.0.1
Full (with CoA)
aaa:
auth_provider: radius
nas_identifier: osvbng
plugins:
subscriber.auth.radius:
servers:
- host: 10.1.1.1
secret: "${RADIUS_SECRET_PRIMARY}"
- host: 10.1.1.2
secret: "${RADIUS_SECRET_SECONDARY}"
auth_port: 1812
acct_port: 1813
timeout: 3s
retries: 3
nas_ip: 10.0.0.1
nas_port_type: Virtual
dead_time: 30s
dead_threshold: 3
coa_listener:
port: 3799
coa_clients:
- host: 10.1.1.1
secret: "${COA_SECRET}"
response_mappings:
- vendor_id: 9
vendor_type: 1
internal: vrf
extract: "ip:vrf-id=(.+)"
- vendor_id: 9
vendor_type: 1
internal: qos.egress-policy
extract: "sub-qos-policy-out=(.+)"
- vendor_id: 9
vendor_type: 1
internal: qos.ingress-policy
extract: "sub-qos-policy-in=(.+)"
Per-VRF Servers
A common deployment: auth/accounting reach two RADIUS servers in different VRFs, and CoA is received on a separate management VRF. The plugin-level vrf and source_ip set the default for outbound traffic; each server overrides as needed. coa_listener is bound independently.
plugins:
subscriber.auth.radius:
vrf: aaa-vrf
source_ip: 10.0.0.1
servers:
- host: 10.1.1.1
secret: "${RADIUS_SECRET_PRIMARY}"
- host: 10.2.2.2
secret: "${RADIUS_SECRET_SECONDARY}"
vrf: aaa-vrf-backup
source_ip: 10.0.1.1
coa_listener:
port: 3799
vrf: mgmt-vrf
source_ip: 192.0.2.10
coa_clients:
- host: 10.1.1.1
secret: "${COA_SECRET}"