Before the walk-throughs, two ideas make everything below obvious:
Virtual RADIUS servers. Lunar hosts one or more virtuals. Each virtual is a
RADIUS server on its own IPv4 address that binds exactly three UDP ports, one
per RADIUS function:
auth (Access-Request), default 1812.
acct (Accounting-Request), default 1813.
CoA / Disconnect (RFC 5176), default 3799.
Each virtual has a set of clients, the NAS devices allowed to talk to it, each
matched by source IP and carrying a shared secret.
The backend is the brain. Lunar holds no config or policy of its own. It
fetches its virtuals from your backend API over HTTP, and it POSTs every RADIUS
request it receives to that backend for a decision. Lunar also runs a small
HTTP API of its own for configuration and health probes. So there are two
separate HTTP directions: lunar -> backend (config + decisions) and
you -> lunar (management).
Testing mode is the one exception: it wires up a built-in virtual that answers
locally, so you can test lunar with no backend at all.
Lunar starts a built-in TEST virtual on 127.0.0.1 with a
client-test client and shared secret testing123, and mirrors
authentication back so a request succeeds without any backend. Test it with
radtest (see Tools for all options):
A run ending in radtest: Success after an Access-Accept confirms the
install, the listener and the codec are all healthy. Never use testing mode in
production.
2. See it work with the backend simulator
Testing mode proves lunar runs, but hides the backend conversation. To see that
conversation (the config fetch, the decoded packet lunar posts, and the reply)
run the backend simulator in verbose mode next to lunar. This is the best way
to understand how a real backend integration behaves.
Turn testing mode off for this (so lunar talks to the simulator instead of
answering itself), and point lunar's subscriber backend endpoint at the
simulator (127.0.0.1:5555 by default).
In -V mode it pretty-prints every HTTP request lunar makes and every reply
it sends back. It serves one fixed virtual (TEST / client-test /
testing123, auth 1812 / acct 1813 / CoA 3799) and always
accepts auth, mirroring the request attributes onto the reply. See
Tools for the full behavior.
Step 2: start lunar
Start (or restart) lunar with the subscriber service enabled and its backend
endpoint pointing at the simulator. As lunar comes up it performs its
config fetch: it calls the backend for its virtuals:
GET /v1/lunar/<server_id>/virtuals
The simulator returns the one TEST virtual. Lunar binds that
virtual's three UDP ports and is now serving. In the simulator's verbose output
you will see this GET and the virtual JSON it returned. This is exactly the
same fetch lunar does against a real backend. The simulator just always answers
with the test virtual.
Lunar builds the Access-Accept on the RADIUS wire back to radtest, which
prints radtest: Success. You have now seen the whole path end to end:
UDP in -> decode -> backend POST -> decision -> encode -> UDP out.
Step 4: try accounting and a disconnect
The same flow drives the other functions. An accounting Start POSTs to
.../acct/TEST and gets an accounting-response. An outbound Disconnect
is a single API call to lunar that relays a disconnect-request to the NAS
(see the coapod route below and in the RADIUS Integration guide). Watch each in the
simulator's verbose log to see the request/response shapes.
3. Against your own backend
To make it real you replace the simulator with your backend. Your backend
implements the outbound calls lunar makes (lunar -> backend), and calls lunar's
management API when it needs to change or inspect config (backend -> lunar).
The RADIUS Integration guide specifies all of these
precisely. Here is the shortlist.
Endpoints your backend must serve (lunar -> backend)
These are the calls lunar originates. Implement them and lunar works. Each
route's full request and response spec is shown inline below it.
Return this server's virtuals (with their clients). Lunar calls this on startup
and on reload. This is the config fetch you saw the simulator answer.
GET
/v1/lunar/{server_id}/virtuals
(backend)
Fetch All Virtuals for This Server.
Service backend
Request Parameters
Name
In
Type
Description
MUST include
x-lunar-version
header
string
The major version of lunar.
X
x-secret
header
string
The shared secret (config lunar.secret), the same value your backend uses to authenticate lunar.
X
x-server-id
header
string
This lunar instance's id (config lunar.server_id). Your backend returns the virtuals assigned to THIS server.
X
server_id
url
string
This lunar's id.
X
Response Status Codes
Code
Meaning
200
OK
500, 501, 502, 503, 504
Service Unavailable
Response Properties
Name
In
Type
Description
clients
body
array
The authorized RADIUS clients (optional). Each client carries id, ip4_address and secret (REQUIRED), plus optional name, type, profile, coa_port, req_message_auth and message_auth_reply. Absent or empty means the virtual accepts no clients yet.
domain
body
string
Descriptive label for logging (optional, default "").
id
body
string
The virtual id (REQUIRED). Compared case-insensitively.
ip4_address
body
string
The single IPv4 the virtual listens on (REQUIRED).
name
body
string
Descriptive label for logging (optional, default "").
Return one virtual's current config (used when a single virtual is reloaded).
GET
/v1/lunar/{server_id}/virtual/{virtual_id}
(backend)
Fetch One Virtual's Config.
Service backend
Request Parameters
Name
In
Type
Description
MUST include
x-lunar-version
header
string
The major version of lunar.
X
x-secret
header
string
The shared secret (config lunar.secret).
X
x-server-id
header
string
This lunar instance's id (config lunar.server_id).
X
server_id
url
string
This lunar's id.
X
virtual_id
url
string
The single virtual to return. Return 404 (authoritative) to make lunar STOP a running virtual that no longer belongs to this server. Any other failure is treated as transient.
X
Response Status Codes
Code
Meaning
200
OK
404
Not Found
500, 501, 502, 503, 504
Service Unavailable
Response Properties
Name
In
Type
Description
clients
body
array
The authorized RADIUS clients (optional). Same schema as the startup fetch.
id
body
string
The virtual id (REQUIRED).
ip4_address
body
string
The single IPv4 the virtual listens on (REQUIRED).
Reply attributes to send (class, session-timeout, rate limits). Do NOT include id, authenticator, message-authenticator or proxy-state, lunar owns those.
code
body
string
The decision. Return access-accept or access-reject for an access-request, access-challenge to continue a multi-round exchange, or access-accept to answer a status-server. Must be returned within the response deadline (default 2s, capped 5s).
Lunar fast-ACKs accounting to the NAS itself, so the response body is IGNORED. Just record the session (make the write idempotent on acct-session-id plus NAS identity) and return 200 OK. The canonical body, if you send one, is accounting-response.
Response Example
{"code":"accounting-response"}
Decide an inbound CoA / Disconnect from a NAS.
POST
/v1/lunar/{server_id}/coa/{virtual_id}
(backend)
Inbound CoA / Disconnect Decision (coa).
Service backend
Request Parameters
Name
In
Type
Description
MUST include
x-client-id
header
string
The client's id.
X
x-client-ip
header
string
The client's source IP.
X
x-lunar-version
header
string
The major version of lunar.
X
x-nas-id
header
string
The NAS id.
X
x-nas-profile
header
string
The client's profile, only sent when it is set.
x-secret
header
string
The shared secret (config lunar.secret).
X
x-server-id
header
string
This lunar instance's id (config lunar.server_id).
X
server_id
url
string
This lunar's id.
X
virtual_id
url
string
The virtual that received the inbound CoA / Disconnect. Do not confuse this with the outbound coapod relay (backend to lunar to NAS).
Delivery is best-effort and asynchronous, and never blocks RADIUS processing. Simply accept the POST and return 200 OK. message is the log text, level is one of debug, info, warning, error or critical, and time is Unix seconds. Every meta pair is also emitted as a top-level field.
Health probe, answer 200 when your backend is ready.
GET
/v1/lunar/ping
(backend)
Health Ping.
Service backend
Request Parameters
Name
In
Type
Description
MUST include
x-lunar-version
header
string
The major version of lunar.
X
x-secret
header
string
The shared secret (config lunar.secret).
X
x-server-id
header
string
This lunar instance's id (config lunar.server_id).
X
Response Status Codes
Code
Meaning
200
OK
500, 501, 502, 503, 504
Service Unavailable
Response Properties
Name
In
Type
Description
description
body
string
The backend MUST answer 200 OK for the subscriber service to report healthy on GET /v1/status. Probed with a 5 second timeout. No body is required.
The request and response bodies use lunar's packet/attribute JSON, a top-level
code and an attributes object keyed by attribute name. That format,
including data types, enums and credential handling, is documented once in
The JSON request format, and the emitted
(lower-case) form is shown throughout Kafka when using
Kafka event streaming.
Endpoints on lunar you can call (backend -> lunar)
These are the calls you make on lunar, all under /v1 and gated by the
x-secret header. Each route's full spec is shown inline below it.
Lunar's health and vitals (also checks the backend).
GET
/v1/status
(lunar)
Instance Health and Vitals.
Service lunar
Request Parameters
Name
In
Type
Description
MUST include
x-secret
header
string
Lunar shared secret (config lunar.secret). Compared in constant time. A missing or wrong value returns 401 with no body.
X
Response Status Codes
Code
Meaning
200
OK
401
Unauthorized / Require Authentication
500, 501, 502, 503, 504
Service Unavailable
Response Properties
Name
In
Type
Description
disks
body
array
One entry per real mounted filesystem, mirroring df but in MiB. Each entry carries filesystem, mount, fstype, size_mib, used_mib, available_mib and use_percent.
errors
body
array
Present only when status is bad. One human-readable string per failure, either a failed backend probe or a license problem.
free_mem_mib
body
integer
Memory usable by new workloads without swapping, in MiB (the kernel MemAvailable estimate).
hostname
body
string
The host's fully-qualified domain name.
kernel
body
string
Full kernel release string.
load_avg_15m
body
decimal
Fifteen minute load average.
load_avg_1m
body
decimal
One minute load average.
load_avg_5m
body
decimal
Five minute load average.
lunar_running_seconds
body
integer
How long this lunar instance has been running, in seconds.
lunar_started_time
body
integer
Unix epoch second (UTC) at which this lunar instance started.
os
body
string
Human OS description.
status
body
string
Overall verdict. ok when every health check passed and the license is healthy, otherwise bad. This is the single field to alert on. The HTTP status is 200 either way, so poll this field, not the status code.
(Re)load a virtual. Lunar re-fetches that virtual from your backend and applies
it in place, with no restart and no disruption to live traffic.
POST
/v1/subscriber/radius/virtual/{virtual_id}
(lunar)
Update or Start a Virtual.
Service lunar
Request Parameters
Name
In
Type
Description
MUST include
x-secret
header
string
Lunar shared secret (config lunar.secret).
X
virtual_id
url
string
The virtual to (re)load. This is the only input. There is no request body.
X
Response Status Codes
Code
Meaning
204
OK - No Content
401
Unauthorized / Require Authentication
500, 501, 502, 503, 504
Service Unavailable
Response Properties
Name
In
Type
Description
description
body
string
No body. Lunar returns 204 immediately, then calls back to your backend (GET /v1/lunar//virtual/) to fetch and apply the config asynchronously, starting the virtual if it was not running.
Stop a virtual.
DELETE
/v1/subscriber/radius/virtual/{virtual_id}
(lunar)
Stop a Virtual.
Service lunar
Request Parameters
Name
In
Type
Description
MUST include
x-secret
header
string
Lunar shared secret (config lunar.secret).
X
virtual_id
url
string
The virtual to stop. Legacy equivalent POST /v1/virtual/stop/ is identical.
X
Response Status Codes
Code
Meaning
204
OK - No Content
401
Unauthorized / Require Authentication
500, 501, 502, 503, 504
Service Unavailable
Relay a CoA / Disconnect to a NAS in one call, returning the decoded ACK / NAK
(see the Overview and the
RADIUS Integration guide).
The reply packet code, a coa-ack / coa-nak or disconnect-ack / disconnect-nak. On 400 the body is instead an error object with an error string and, when the failure came from sending, a retries count.
The backend's decision, passed straight through (for example access-accept). The whole backend status code and body are returned verbatim. 502 (covered by the 5xx row) means the backend could not be reached.
A full-path liveness probe that builds a Status-Server, sends it through the
whole engine and back, and returns 200{"status": "ok"} when the entire
chain is alive.
GET
/v1/subscriber/radius/{virtual_id}/ping
(lunar)
Virtual Liveness Probe.
Service lunar
Request Parameters
Name
In
Type
Description
MUST include
x-secret
header
string
Lunar shared secret (config lunar.secret).
X
virtual_id
url
string
The virtual to probe end to end. 404 if it is not running.
X
Response Status Codes
Code
Meaning
200
OK
401
Unauthorized / Require Authentication
404
Not Found
500, 501, 502, 503, 504
Service Unavailable
Response Properties
Name
In
Type
Description
status
body
string
ok when the Status-Server probe made the full round-trip and the reply verified. On failure the response is 502 (covered by the 5xx row) with status bad and an error reason.
Response Example
{"status":"ok"}
The typical lifecycle: you create a virtual and its clients in your backend,
then call POST /v1/subscriber/radius/virtual/<virtual_id> on lunar so it
fetches and starts that virtual. From then on lunar serves the wire and posts
every decision to your backend.
Where to go next
RADIUS Integration is the complete integration
reference: every route, the full packet/attribute JSON format, credential
handling and the exact outbound calls. Read this to build your backend.
Authentication explains how PAP / CHAP /
MS-CHAP reach your backend, and the single-call design behind it.
Dictionaries covers the attribute
dictionaries and how to extend them.