Lua script context API reference for accessing AAA request data
Script Context API
The context object provides access to AAA request data through sub-contexts
and methods. See scripts for script structure, parameters, and examples.
Root Context
The root context object provides these fields and methods:
Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique request identifier |
instance_id | string | Server instance identifier (for HA deployments) |
cluster_id | string? | Cluster identifier (if configured) |
version | string | Radiator server version |
hostname | string | Server hostname |
root | context? | Root context (for nested contexts) |
parent | context? | Parent context (for nested contexts) |
Nested Contexts
Nested contexts are created automatically during tunneled EAP methods like EAP-TTLS, PEAP, and EAP-TEAP. When inner authentication runs inside the TLS tunnel, a child context is created with:
parent— the outer tunnel's contextroot— the original request context (the outermost context)
For non-tunneled requests, both root and parent are nil.
The vars sub-context is shared between parent and child contexts, allowing
data to be passed between outer and inner authentication phases.
Sub-contexts
| Field | Type | Description |
|---|---|---|
aaa | aaa | AAA processing state |
acct | acct | Accounting data |
auth | auth | Authentication state |
cache | cache | Cache operations |
cert | cert | Certificate information |
eap | eap | EAP protocol data |
eap_teap | eap_teap | EAP-TEAP specific data |
eap_ttls | eap_ttls | EAP-TTLS specific data |
http | http | HTTP request/response |
radius | radius | RADIUS packet data |
radiusproxy | radiusproxy | RADIUS proxy data |
stats | stats | Process/system stats |
tls | tls | TLS connection info |
user | user | User data from backend |
vars | vars | Custom variables |
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
log(name, message) | logger name, message | - | Write to named AAA logger |
backend(name, query?) | backend name, optional query | result | Execute backend query |
challenge(timeout, message?) | seconds, optional message | - | Send challenge and wait for response |
count(name) | counter name string | - | Increment a statistics counter |
map(name, value) | map name, lookup value | result? | Execute a named policy map lookup |
count(name)
Increments the statistics counter identified by name. Counters are created
dynamically on first increment; no pre-declaration in the statistics block is
required.
The name argument controls which namespace the counter is placed in:
- No
::separator — counter is placed under the current pipeline context namespace (e.g.handler::<policy>::<handler>::<name>). ::separator — the parts before the last::form the namespace and the part after is the counter name. Use thecustom::prefix to create counters in the shared custom namespace.
local context, previous = ...
-- Counter in the current handler namespace
context:count("total_requests")
-- Counter in the explicit custom namespace
if context.aaa.result == "accept" then
context:count("custom::successful_logins")
else
context:count("custom::failed_logins")
end
return previous
Counter values are readable via the management API at
/api/v1/statistics/counter/<namespace>/<name> (e.g.
/api/v1/statistics/counter/custom/successful_logins).
Use the statistics block to configure history retention (samples and interval)
for counters; this is optional and separate from counter creation.
log(name, message)
Writes message to the AAA logger identified by name. The logger must be
declared in the server configuration (log-aaa block). If no logger with that
name exists, the script raises a runtime error.
The name parameter is the logger name as configured in the log-aaa block,
not a severity level. AAA loggers write structured records that include the
request identifier and timestamp automatically.
local context, previous = ...
-- Write to an AAA logger named "AUTHLOG"
context:log("AUTHLOG", "Processing " .. tostring(context.aaa.identity))
-- Conditional logging
local framed_ip = context.radius.request:attr("framed-ip-address")
if framed_ip then
context:log("AUTHLOG", "Framed-IP: " .. framed_ip)
end
return previous
Configuring the logger in radconf:
log-aaa "AUTHLOG" {
filename "log/auth.log";
format json;
}
RADIUS Packet Attribute Methods
RADIUS packet attribute access is through the context.radius.request and
context.radius.reply packet objects (see radius sub-context).
The key methods for reading attributes are:
| Method | Parameters | Returns | Description |
|---|---|---|---|
attr(name, tag?) | attribute name, optional tag | value? | Get first value of a named attribute |
attr_last(name, tag?) | attribute name, optional tag | value? | Get last value of a named attribute |
attr_all(name, tag?) | attribute name, optional tag | array? | Get all values as an array |
attrs() | - | table | Get all attributes as a table |
Attribute Name Casing
Attribute lookups use lowercase names regardless of how the dictionary file or packet spells them. Always pass lowercase names to all attribute methods.
-- Correct: lowercase
local ip = context.radius.request:attr("framed-ip-address")
local nas = context.radius.request:attr("nas-identifier")
local mac = context.radius.request:attr("calling-station-id")
-- Wrong: mixed-case silently returns nil
-- local ip = context.radius.request:attr("Framed-IP-Address") -- nil
This applies only to Lua attribute methods. Radconf condition expressions
(radius.request.attr.Framed-IP-Address) and template expansions
(%{radius.request.attr.Framed-IP-Address}) are handled by a separate code
path and are not affected.
attr(name, tag?)
Returns the first value of the named attribute, or nil if the attribute is
not present. The optional tag parameter (integer 1-31) filters tagged
attributes; omit it (or pass nil) for untagged lookups.
local username = context.radius.request:attr("user-name")
local ip = context.radius.request:attr("framed-ip-address")
if not ip then
-- attribute absent; treat as unknown origin
end
attrs()
Returns a Lua table where each key is a lowercase attribute name (string) and each value is an array (1-based) of all occurrences of that attribute. Even attributes that appear only once are wrapped in a one-element array so callers can iterate uniformly.
Attribute values are mapped to Lua types as follows:
| RADIUS type | Lua type |
|---|---|
| Integer / Unsigned | number |
| Text / String | string |
| IP address / network | string (dotted-decimal or CIDR) |
| Bytes / binary | string (raw bytes) |
| Boolean | boolean |
| Missing / unknown | nil |
local all = context.radius.request:attrs()
-- Single-valued attribute
local username = all["user-name"] and all["user-name"][1]
-- Multi-valued attribute
local filters = all["filter-id"] or {}
for _, f in ipairs(filters) do
context:log("AUTHLOG", "Filter: " .. tostring(f))
end
-- Dump every attribute for debugging
for name, values in pairs(all) do
for _, v in ipairs(values) do
context:log("AUTHLOG", name .. " = " .. tostring(v))
end
end