Introduction to RADIUS itself
Objective
RADIUS provides authentication, authorization and accounting (AAA) for network access. This document is a short primer on the RADIUS protocol itself (the players involved, the shared secret, how requests and responses work, and how attributes are encoded) so the rest of the lunar documentation makes sense. It is not a full reference. The RFCs (starting with RFC 2865) are the authority.
The players: server, client and NAS
RADIUS has a few roles you will see throughout this documentation:
RADIUS server is the system that answers requests and makes the AAA decisions (authenticate this user, record this session, change or disconnect this session). Lunar is a RADIUS server.
RADIUS client is the device that sends requests to the server. RFC 2865 uses "client" for exactly this: the peer that talks to the server. A client is identified on the server by its source IP address and has its own shared secret.
NAS (Network Access Server) is the network device the subscriber actually connects through: a BNG / BRAS in a broadband network, or a Wi-Fi controller / hotspot router on wireless. In the common case the NAS is the RADIUS client. It terminates the subscriber and speaks RADIUS to the server, so "client" and "NAS" usually mean the same box.
Two clarifications worth making up front:
A RADIUS proxy is a server that forwards requests on to another server. A proxy is itself a client to the server it forwards to, so from the server's point of view the immediate peer is always "the client", whether that is a NAS directly or a proxy standing in front of the NAS.
Do not confuse the client (the NAS) with the subscriber, the end user or their device (home router, phone, laptop). The subscriber connects to the NAS, and the NAS is the RADIUS client that asks the server whether to allow them.
Transports and ports
RADIUS most commonly runs over UDP, but it can also run over TCP (RFC 6613) or over TLS/RadSec (RFC 6614). The shared secret above applies to all of them.
Each RADIUS function has its own well-known UDP port, and a request must be sent to the port for its type. The defaults below are standardized, but they are configurable, and several RADIUS servers can run on one host on different ports.
The three functions, the requests each accepts and the responses each returns:
Request Type |
Valid Request Types |
Valid Response Types |
Port |
|---|---|---|---|
Authentication |
Access-Request |
Access-Accept, Access-Reject, Access-Challenge |
1812 |
Accounting |
Accounting-Request |
Accounting-Response |
1813 |
Dynamic Authorization |
CoA-Request, Disconnect-Request |
CoA-ACK, CoA-NAK, Disconnect-ACK, Disconnect-NAK |
3799 |
Authentication and accounting flow from the client to the server. Dynamic authorization is the exception: it flows the other way. The server sends a CoA-Request (Change of Authorization) or Disconnect-Request to a NAS to change or terminate a live session, and the NAS answers with an ACK or NAK. This is the only case where a request is sent to a NAS.
Requests and responses
Every RADIUS message is a packet with a fixed header followed by a list of attributes:
The header carries the packet type (a code, e.g. Access-Request), a one-byte Identifier used to match a reply to its request, the total length, and an Authenticator (16 bytes) used for integrity/hiding.
The attributes are the key-value fields carrying the actual data about the user, the session and the NAS.
A response has exactly the same structure as a request (the same header layout and the same kind of attribute list), just a different packet type (and its Authenticator is computed differently, keyed with the shared secret). So once you understand one packet, you understand them all. The difference is the code and which attributes are present.
Attributes (TLVs)
Attributes are essentially TLVs (Type, Length, Value) and can be standard (defined by the RFCs) or vendor specific. You do not need the exact parsing rules to work with lunar, but it helps to know the shape:
Field |
Size |
Meaning |
|---|---|---|
Type |
1 byte |
The attribute number, a dictionary lookup code (e.g. 1 = User-Name). |
Length |
1 byte |
Total length of this attribute in bytes, counting the Type and Length octets plus the Value. |
Value |
Length - 2 bytes |
The data, interpreted according to the attribute's type. |
As a concrete example, the User-Name attribute carrying "alice" is the 7 bytes:
01 07 61 6C 69 63 65
| | \_______________/
| | value = "alice" (5 bytes of ASCII)
| length = 7 (1 type + 1 length + 5 value)
type = 1 (User-Name)
A few standard attributes from RFC 2865 (the Length column is in bytes, including the 2-byte Type and Length header):
Attribute |
Type |
Length |
Value |
|---|---|---|---|
User-Name |
1 |
3+ |
The login name, as text, e.g. "alice". |
User-Password |
2 |
18-130 |
The password, hidden using the shared secret (PAP only). |
NAS-IP-Address |
4 |
6 |
The NAS's IPv4 address, e.g. 192.0.2.1. |
NAS-Port |
5 |
6 |
The port number, a 32-bit integer. |
Service-Type |
6 |
6 |
The kind of service, an enum, e.g. Framed-User. |
The Type is only a number on the wire. A dictionary maps that number to a human name and a data type. Without the dictionary, neither a server nor a client can know what an attribute means or how to read its value.
RADIUS also defines vendor-specific attributes, extended attributes and long-extended attributes (and attributes can be nested or split across several TLVs to carry large values). These extend RADIUS beyond the standard set. Many vendor BNGs, for example, add their own attributes carrying extra information a server can use for policy decisions. The dictionary is again what makes them readable.
Accounting
Accounting requests are sent by the client over the life of a session: a Start when it begins, periodic Interim-Updates, and a Stop when it ends. They carry attributes describing usage (how much data the session has used and how long it has been active), and the server replies with an Accounting-Response to acknowledge receipt.
Why RADIUS is complex
RADIUS is not a simple request/response protocol like HTTP. It is a binary protocol with many rules, edge cases and security considerations, and it is unforgiving of mistakes. A server has to handle several message types and many clients, all correctly and securely. In particular:
Every request carries an Identifier and a Request Authenticator, and the reply must echo the Identifier and carry a Response Authenticator, and the two authenticators are computed differently, both keyed on the shared secret.
Attributes can be encoded in complex ways (nested TLVs, and continuation to concatenate large values), all of which must be parsed and built exactly.
Getting any of this subtly wrong breaks interoperability or security.
How lunar helps
Lunar absorbs this complexity. It handles the RADIUS protocol (the wire format, the crypto and authenticators, the dictionaries and all the edge cases) and exposes a simple JSON API to your backend. The backend does not need to know anything about RADIUS: it just answers the JSON, and can be written in any language over any data store, as long as it can serve HTTP/JSON requests.