2026-03-02

v10.32.2

Summary
  • Add radius.request.identifier and radius.reply.identifier accessor support for reading and writing RADIUS packet identifiers

  • Add --identifier flag to radiator-client for setting custom packet identifiers

  • Fixed stop action not halting pipeline execution when used inside sub-pipelines such as if-then blocks

  • Add named HTTP status code constants (e.g. http.TOO_MANY_REQUESTS, http.INTERNAL_SERVER_ERROR) for use in pipelines

  • Add machine-readable error codes to caught pipeline errors via aaa.caught_error.code and compile-time-validated errors.* constants

  • Add ability to read all RADIUS attributes as a JSON array using bare radius.request.attr and radius.reply.attr accessors

  • Implement Replace operator for radius proxy attribute modification

  • Add RADIUS attribute bulk copy and numeric packet code setter for proxy queries

  • Use attrs instead of attr for bulk RADIUS attribute operations to avoid confusing syntax

  • Allow handlers to override the RADIUS reply packet code by preserving explicitly set values

  • The verification block in TLS configuration now uses the @verification prefix; the old syntax is deprecated and emits a warning

  • Add select() and remove() filters for filtering multivalue data and RADIUS attribute collections

  • Add nas_identifier configuration option to RADIUS backend server blocks for including NAS-Identifier in Status-Server packets

  • Pipeline blocks inside EAP methods (authentication, post-authentication, pre-authentication, authorization) now use the @ prefix; the old syntax is deprecated and emits a warning

  • Add assert pipeline action for verifying expression values and error conditions during pipeline execution. See assert for full documentation.

  • Simplify the configuration editor syntax highlighting for radconf files to use a generic grammar instead of dynamically loaded keywords

  • Fixed incorrect error-level logging for unauthorized HTTP management API requests

  • Add rate limiting support via Lua scripts and cache-based counters, with GCRA and counter-based algorithms for per-user, per-device, and per-gateway throttling. See Rate Limiting and Rate Limiting Algorithms for full documentation.

  • radiator-client --expect-response-code now accepts packet type names (e.g. Access-Reject) in addition to numeric codes

  • Lua print() now routes output to the structured application log instead of stdout

  • Add conditions pipeline action conditions for full documentation.

  • Add format-packet subcommand and improve packet display in radiator-client

  • Fixed backend server priority field being ignored - servers are now selected by priority (0 = highest) first, then alphabetically

  • The Management UI log view is now faster and more reliable for large datasets, with easier filtering, infinite-scroll loading, streaming logs, and JSON/CSV exports. This makes it easier to find and share relevant logs with less manual work.

  • Fixed light/dark theme detection when following the system theme preference

  • Fixed RADIUS proxy attribute ordering so Message-Authenticator is always the first attribute in proxied requests, even when pre-proxying modifies attributes without a copy block

  • Added a button to clear search highlights in the documentation viewer

  • Improved toolbar layout on small screens by hiding the page title

  • Fix RADIUS attributes being duplicated when using modify in @pre-proxying without a copy block

  • Added syntax highlighting for radconf code fences in Markdown documentation viewed in the Management UI

  • Add Reverse CoA support

RADIUS Packet Identifier Accessor Support

New execution context variables radius.request.identifier and radius.reply.identifier allow reading and writing the RADIUS packet identifier field (0–255) in configuration policies.

The identifier is a byte-sized field in the RADIUS packet header used to match requests with their corresponding responses. The server automatically echoes the request identifier in the reply packet.

The Execution Context reference has been updated with the new radius.request.identifier and radius.reply.identifier variables.

radiator-client --identifier Flag

The radiator-client tool now supports a --identifier option to set a custom RADIUS packet identifier (0–255) on outgoing requests. When not specified, the identifier defaults to 0.

radiator-client --identifier 42 -s 127.0.0.1 --secret mysecret \
    --user mikem --password fred

Named HTTP status code constants

The http namespace now provides named constants for all standard HTTP status codes. Use these instead of raw numeric values for clearer, more maintainable pipeline configurations.

# Before
modify http.status = 429;

# After
modify http.status = http.TOO_MANY_REQUESTS;

All standard 1xx through 5xx status codes are available. Constants are read-only and resolve to their numeric values at runtime.

See Execution Context for the full list of available constants.

Structured error codes for pipeline error handling

Errors caught by the try action now carry an optional machine-readable error code accessible via aaa.caught_error.code. This enables pipelines to branch on specific error conditions without parsing the human-readable error message string.

Error code constants are available in the read-only errors namespace and validated at configuration load time, preventing typos from going unnoticed.

The first supported error code is errors.RADIUS_IDENTIFIERS_EXHAUSTED, which is set when all 256 RADIUS packet identifiers are in use on a connection and a new proxied request cannot be sent.

try backend "REVERSE_RADSEC";

if all {
    aaa.caught_error.code == errors.RADIUS_IDENTIFIERS_EXHAUSTED;
} then {
    modify http.status = http.TOO_MANY_REQUESTS;
    stop;
} else if all {
    aaa.caught_error != none;
} then {
    reject "operation failed: %{aaa.caught_error}";
}

When aaa.caught_error is cleared, aaa.caught_error.code is also cleared automatically.

See Execution Context for the full list of available error code constants.

Log All RADIUS Attributes into a Single Field

The bare radius.request.attr and radius.reply.attr accessors (without a specific attribute name) now return all dictionary attributes as a JSON array of objects with name and value fields. Enum-typed attribute values are automatically resolved to their dictionary names (e.g. "framed-user" instead of 2).

This is useful for logging all RADIUS attributes in a single structured log field:

log "AUTHENTICATION" {
    json {
        "request_attrs" radius.request.attr;
        "reply_attrs" radius.reply.attr;
    }
}

The resulting JSON array looks like:

[{"name": "user-name", "value": "mikem"}, {"name": "service-type", "value": "framed-user"}]

See Execution Context for more details.

The = (replace) operator now works for radiusproxy.request.attr.* and radiusproxy.reply.attr.* in @pre-proxying and @post-proxying blocks. Previously only append (+=) and insert-unless (?=) were functional.

Example

backends {
    radius "UPSTREAM" {
        @pre-proxying {
            copy {
                User-Name;
                User-Password;
            }
        }
    }
}

RADIUS attribute bulk copy in backend queries

The bare radius.request.attr and radius.reply.attr accessors (without a specific attribute name) now copy all attributes in bulk between contexts. Encrypted attributes such as User-Password are automatically re-encrypted with the destination shared secret so they survive proxying across different RADIUS hops. Message-Authenticator is excluded from bulk copies because it must be recomputed per context.

This makes it possible to implement RADIUS proxying through query mode:

backends {
    radius "RADSEC_BACKEND" {
        server "UPSTREAM" {
            # ... server configuration ...
        }

        query "PROXY_REQUEST" {
            bindings {
                radius.request.code = radius.request.code;
                radius.request.attrs = radius.request.attrs;
            }

            mapping {
                radius.reply.code = radius.reply.code;
                radius.reply.attr = radius.reply.attr;
            }
        }
    }
}

See RADIUS backend query mode for details.

Numeric values for radius.*.code

radius.request.code and radius.reply.code now accept numeric integer values in addition to string names (e.g. "access-request"). This is needed for query bindings where the packet code is passed through as an integer, such as radius.request.code = radius.request.code.

Use attrs for bulk attribute operations

Previously bulk attribute copy used the attr path, which led to confusing configuration lines like:

radius.reply.attr = radius.reply.attr;

This looks like a no-op assigning a single attribute to itself. The attrs (plural) path now makes the bulk nature of the operation explicit:

radius.reply.attrs = radius.reply.attrs;

Both attr and attrs still work interchangeably, but attrs is now the recommended form for bulk operations.

Documentation

Preserve handler-set reply packet codes

Previously, the reply packet code was always determined by the handler result (accept, reject, challenge), which made it impossible for handlers to send a custom response code. Now, if a handler explicitly sets radius.reply.code before the result is finalized, that code is sent as-is instead of being replaced by the default code for the handler result.

When proxying RADIUS requests in query mode, the backend's reply code can be mapped back to the reply. Previously, the mapped code was overwritten by the proxy handler's own result. Now the backend's reply code is faithfully relayed to the client:

query "PROXY_REQUEST" {
    bindings {
        radius.request.code = radius.request.code;
        radius.request.attrs = radius.request.attrs;
    }

    mapping {
        radius.reply.code = radius.reply.code;
        radius.reply.attrs = radius.reply.attrs;
    }
}

Documentation

@verification prefix for TLS verification block

The verification block inside tls configuration now uses the @verification prefix, consistent with other block-level statements. The old verification syntax is still accepted but will emit a deprecation warning.

Before

tls {
    verification {
        accept;
    }
}

After

tls {
    @verification {
        accept;
    }
}

Documentation

Add select() and remove() filters

New filters for filtering multivalue data and RADIUS attribute collections. Supports matching by attribute name, attribute number, and regex patterns.

Documentation

NAS-Identifier Support for RADIUS Backend Status-Server

A new optional nas_identifier configuration statement is available in the RADIUS backend server block. When set, Radiator includes the NAS-Identifier attribute in outgoing Status-Server health check packets. On the receiving server side, the value is extracted from incoming Status-Server packets and exposed through the server connections Management API.

@ prefix for pipeline blocks inside EAP methods

Pipeline blocks inside EAP method configurations (eap-tls, eap-ttls, eap-peap, eap-md5, eap-otp, eap-gtc, eap-sim, eap-aka, eap-aka-prime, eap-mschapv2, eap-teap) now use the @ prefix, consistent with handler-level pipeline blocks. The old unprefixed syntax is still accepted but will emit a deprecation warning.

Before

eap-tls {
    tls { ... }
    post-authentication {
        invoke "LOG_AUTHENTICATION";
    }
}

After

eap-tls {
    tls { ... }
    @post-authentication {
        invoke "LOG_AUTHENTICATION";
    }
}

Fixed Log Level for Unauthorized HTTP Management Requests

The HTTP management middleware was logging all API errors at the Error level, including expected 401 Unauthorized responses from unauthenticated requests. This caused noisy "No user context found in the request" error log entries during normal operation whenever an unauthorized request was received.

The log level for client errors (4xx) has been downgraded to Debug, while server errors (5xx) continue to be logged at the Error level.

radiator-client: --expect-response-code accepts packet type names

The --expect-response-code option in radiator-client now accepts case-insensitive packet type names in addition to numeric codes.

For example, instead of:

radiator-client --expect-response-code 3 ...

You can now write:

radiator-client --expect-response-code Access-Reject ...

Valid names: Access-Request, Access-Accept, Access-Reject, Accounting-Request, Accounting-Response, Access-Challenge, Status-Server, Disconnect-Request, Disconnect-Ack, Disconnect-Nak, CoA-Request, CoA-Ack, CoA-Nak.

Error messages for unexpected response codes now also display the packet type name alongside the numeric code for improved readability.

Lua print() routed to application log

The Lua print() function is now overridden in all Radiator script contexts. Instead of writing to stdout (which is not accessible in production deployments), print() emits a structured DEBUG-level log entry so output is always captured alongside other application events.

Each call produces a log entry with "message": "Lua print output" and fields:

  • msg — the printed value (plain string for a single argument; compact JSON array for multiple arguments; tables serialized to JSON)
  • script — script identifier: "name" for inline scripts, "name(file.lua)" for file-based scripts
  • line — the line number within the Lua source
-- auth_enrichment.lua
local context, previous = ...
print("identity: " .. context.aaa.identity)
print(context.radius.request)  -- table → compact JSON
return previous

Example log output (JSON format):

{
  "level": "DEBUG",
  "message": "Lua print output",
  "fields": {
    "msg": "identity: alice@example.com",
    "script": "auth_enrichment(auth_enrichment.lua)",
    "line": "3"
  }
}

Enable DEBUG logging to see print() output (--debug flag or loglevel debug; in a logging block).

Backend Server Priority Selection Fixed

The priority field in RADIUS, LDAP, and SQL backend server configurations was being parsed but not respected at runtime. Servers were always selected in alphabetical order by name, regardless of their configured priority.

This has been fixed. Backend servers are now selected according to their configured priority value (0 = highest priority, 255 = lowest), with alphabetical ordering (by server name) used as a tie-breaker when multiple servers share the same priority.

What Changed

  • RADIUS backends: Server selection now respects the priority field
  • LDAP backends: Server selection now respects the priority field
  • SQL backends (MySQL, PostgreSQL, MSSQL): Priority field added and server selection now respects it
  • Default behavior: Servers with no priority field default to priority 0 (highest)
  • Tie-breaking: When servers have the same priority, they are ordered alphabetically by name

See Backend Load Balancing for details on server selection algorithms and the priority field.

Fixed an issue where parts of the management UI would always render with the dark theme when the user had not explicitly set a theme preference, regardless of the operating system's light/dark mode setting. This affected the sidebar logo, Monaco code editors, canvas charts, and Mermaid diagrams.

Documentation Search Improvements

A clear button now appears next to the search bar when search highlights are active on a documentation page. Clicking it removes the highlights, same as pressing Escape.

The toolbar page title and instance info are now hidden on smaller screens to give more space to the search bar and other controls.

Fix pre-proxying attribute duplication

When using modify in @pre-proxying without a copy block, attributes set by modify would be duplicated in the proxied request. For example, setting radiusproxy.request.attr.User-Name would result in both the modified and original User-Name appearing in the outgoing request.

The proxy request is now populated from the original request before running the pre-proxying pipeline, so modify operations correctly replace existing attributes.

Reverse CoA support

Add support for sending RADIUS "reverse" requests to NAS devices over their existing connections and receiving them into a local RADIUS server via a backend for NAS or AP simulation.

See the Reverse CoA Messaging article for details on this new feature.

Known Issues

When the /var/lib/radiator directory has 15_certificates.radconf file, and the file has not been edited once, the rpm package upgrade will show a warning warning: file /var/lib/radiator/15_certificates.radconf: remove failed: No such file or directory. The warning is harmless and can be ignored, as the 15_certificates.radconf file has been renamed.