Radcli library 2.0.0
A simple radius library -- new API reference
Loading...
Searching...
No Matches
RFC 5176 dynamic authorization (CoA/Disconnect)

Macros

#define RADCLI_DAE_NEW   0
#define RADCLI_DAE_DUPLICATE   1
#define RADCLI_CTX_MAX_POLLFDS   2

Typedefs

typedef struct radcli_dae_st radcli_dae
typedef struct radcli_dae_request_st radcli_dae_request
typedef void(* radcli_dae_handler) (radcli_dae_request *req, void *user)
 Application callback invoked by radcli_ctx_dispatch() for a validated request.
typedef enum radcli_dae_flags radcli_dae_flags

Enumerations

enum  radcli_dae_flags { RADCLI_DAE_NO_NAS_CHECK = 1 << 0 }

Functions

radcli_code radcli_dae_req_code (const radcli_dae_request *req)
 Return the received packet's RADIUS code.
const radcli_avp_list * radcli_dae_req_attrs (const radcli_dae_request *req)
 Return the request's decoded attributes.
const char * radcli_dae_req_session_id (const radcli_dae_request *req)
 Return the request's Acct-Session-Id, if it carried one.
const char * radcli_dae_req_user_name (const radcli_dae_request *req)
 Return the request's User-Name, if it carried one.
int radcli_dae_req_framed_ip (const radcli_dae_request *req, struct sockaddr_storage *out)
 Return the request's Framed-IP-Address or Framed-IPv6-Address.
int radcli_dae_req_nas_port (const radcli_dae_request *req, uint32_t *out)
 Return the request's NAS-Port.
int radcli_dae_reply (radcli_dae_request *req, int ack)
 Answer a request with an ACK or NAK, selecting 41/42 or 44/45 from the request's own code, mirroring its Proxy-State attributes, and computing the Response Authenticator over the request's Authenticator.
int radcli_dae_reply_error (radcli_dae_request *req, uint32_t error_cause)
 Answer a request with a NAK carrying the given Error-Cause.
int radcli_dae_reply_to_buffer (radcli_dae_request *req, int ack, uint32_t error_cause, void *buf, size_t *len)
 Produce a reply as bytes instead of sending it – the L0 counterpart of radcli_dae_reply()/radcli_dae_reply_error(), for a request that came from radcli_dae_process().
void radcli_dae_request_free (radcli_dae_request *req)
 Release a request.
int radcli_dae_process (radcli_dae *dae, const void *buf, size_t len, const struct sockaddr *from, socklen_t fromlen, radcli_dae_request **req)
 Validate a caller-supplied packet, without a radcli-owned socket – the L0 counterpart of radcli_ctx_dispatch(), running the identical validation pipeline (REQ-DAE-NET-003) on a buffer and source address the caller supplies instead of reading them from radcli_dae_start()'s socket. A request this produces is otherwise indistinguishable from one radcli_ctx_dispatch() would have delivered to a handler.
radcli_daeradcli_dae_new (radcli_ctx *ctx, unsigned flags)
 Validate dae-* configuration and build a dynamic-authorization listener. Opens no socket – see radcli_dae_start().
void radcli_dae_set_handler (radcli_dae *dae, radcli_dae_handler cb, void *user)
 Register the callback radcli_ctx_dispatch() invokes for each validated request. May be called before or after radcli_dae_start().
int radcli_dae_start (radcli_dae *dae)
 Start receiving: binds the socket described by dae-listen.
void radcli_dae_free (radcli_dae *dae)
 Release a listener, closing its socket if radcli_dae_start() opened one.
int radcli_ctx_get_poll (radcli_ctx *ctx, struct pollfd *pfds, size_t max_pfds, size_t *nfds, int *timeout_ms)
 Report what to wait for on ctx's behalf, for the caller's own event loop – radcli never calls poll()/select()/epoll_wait() itself.
int radcli_ctx_dispatch (radcli_ctx *ctx)
 Read what is ready on ctx's descriptor(s), validate it, and invoke the registered handler for anything that passes – see radcli_dae_set_handler(). Also: drains any in-flight RADCLI_REQUEST_SENDONLY exchange's reply and services its retransmit/ timeout deadline (net2.md's REQ-NET2-SEND-013/016), and, once due, sends the RFC 5997 watchdog on an established RadSec session (watchdog.md's REQ-WATCHDOG-NET-001) – all unconditionally, every call, regardless of whether an active radcli_dae exists at all: this is now the single entry point radcli_ctx_get_poll() drives, for everything ctx owns, not a DAE-only call.

Detailed Description

Receive-only support for RFC 5176: radcli answers CoA-Request and Disconnect-Request packets from a Dynamic Authorization Client (DAC); there is no exported function to send them (radcli implements the server/receiving role only – see doc/requirements/dae.md).

radcli never exposes the listener's own descriptor and never offers a blocking read call: instead, the application registers a handler with radcli_dae_set_handler() and polls radcli_ctx_get_poll()/dispatches with radcli_ctx_dispatch(), both at the radcli_ctx level rather than the radcli_dae level. That is deliberate, not incidental: a future dynamic authorization transport carried over the same session as ordinary requests (rather than a separate UDP/3799 listener) shares one descriptor between the two, and an accessor on radcli_dae alone would let an application watch a descriptor that silently stops meaning what it thinks – see radcli_ctx_dispatch()'s doc comment.

radcli_dae_process()/radcli_dae_reply_to_buffer() are the L0 entry point: the same validation and reply machinery, without a radcli-owned socket, for an application that owns its own transport – e.g. a privileged listener process handing validated requests to workers over IPC.

radcli itself never forks and never installs an atfork handler, so it cannot protect an application that binds the listener and then forks (e.g. a worker/main/privilege-separated process shape): every child inherits a working copy of the same UDP socket, races the others for datagrams, and – since the duplicate-suppression table is per-process – can end up acting on the same Disconnect-Request more than once. A process that forks after radcli_dae_start() must call radcli_dae_free() in every child that does not itself own the listener before running its own event loop.

When the nas-identifier config option is set, every request's own NAS-Identifier (if it carries one) is checked against it automatically, before radcli_dae_handler ever sees the request: a mismatch is NAKed with RADCLI_ERROR_NAS_IDENTIFICATION_MISMATCH and the handler is not invoked. Pass RADCLI_DAE_NO_NAS_CHECK to radcli_dae_new() to disable this check. NAS-IP-Address/NAS-IPv6-Address are never compared: a value a DAC observed for a NAS routinely differs from what the NAS itself is configured with (NAT, containers, a proxy/load balancer in front of the NAS), unlike NAS-Identifier, which both sides are explicitly, statically configured with.

Macro Definition Documentation

◆ RADCLI_CTX_MAX_POLLFDS

#define RADCLI_CTX_MAX_POLLFDS   2

The maximum number of descriptors radcli_ctx_get_poll() ever reports in one call: the request-registry socket/session (REQ-NET2-SEND-016) and, for UDP with an active radcli_dae, the separate DAE listener socket – genuinely different local sockets that cannot be merged into one without changing the wire protocol. Every other case (TLS/DTLS regardless of radcli_dae; UDP with no radcli_dae) reports at most 1.

Examples
radexample-async-dae.c.

Definition at line 764 of file radcli2.h.

◆ RADCLI_DAE_DUPLICATE

#define RADCLI_DAE_DUPLICATE   1

radcli_dae_process() succeeded, producing a request that is a retransmission of one already answered: no new decision to make, and radcli_dae_reply_to_buffer() on it reproduces that cached answer regardless of the ack/error_cause passed to it.

Definition at line 733 of file radcli2.h.

◆ RADCLI_DAE_NEW

#define RADCLI_DAE_NEW   0

radcli_dae_process() succeeded, producing a newly validated request that needs an application decision.

Definition at line 728 of file radcli2.h.

Typedef Documentation

◆ radcli_dae

typedef struct radcli_dae_st radcli_dae

Opaque RFC 5176 dynamic-authorization listener.

Examples
radexample-async-dae.c.

Definition at line 678 of file radcli2.h.

◆ radcli_dae_handler

typedef void(* radcli_dae_handler) (radcli_dae_request *req, void *user)

Application callback invoked by radcli_ctx_dispatch() for a validated request.

Called synchronously, from inside radcli_ctx_dispatch(): only radcli_dae_req_*(), radcli_dae_reply(), radcli_dae_reply_error(), and radcli_dae_request_free() may be called from within it. Calling radcli_ctx_dispatch(), radcli_dae_start(), or radcli_dae_free() from a handler is undefined.

Parameters
reqthe validated request; owned by radcli until freed with radcli_dae_request_free(), or may be freed here if not needed after.
userthe pointer passed to radcli_dae_set_handler().

Definition at line 703 of file radcli2.h.

◆ radcli_dae_request

typedef struct radcli_dae_request_st radcli_dae_request

Opaque validated CoA-Request or Disconnect-Request, passed to a radcli_dae_handler. By the time an application sees one, it has already passed source-address authorization, Request Authenticator verification, Message-Authenticator verification (when present, or unconditionally under dae-require-message-authenticator), Event-Timestamp freshness, duplicate suppression, and (unless RADCLI_DAE_NO_NAS_CHECK was passed to radcli_dae_new()) the NAS-Identifier check – see radcli_ctx_dispatch()'s doc comment.

Examples
radexample-async-dae.c.

Definition at line 688 of file radcli2.h.

Enumeration Type Documentation

◆ radcli_dae_flags

Flags for radcli_dae_new().

A bitwise OR of these is passed as radcli_dae_new()'s flags parameter; 0 is the common case.

Enumerator
RADCLI_DAE_NO_NAS_CHECK 

Skip the automatic check of a request's NAS-Identifier against the nas-identifier config option.

Definition at line 744 of file radcli2.h.

Function Documentation

◆ radcli_ctx_dispatch()

int radcli_ctx_dispatch ( radcli_ctx * ctx)

Read what is ready on ctx's descriptor(s), validate it, and invoke the registered handler for anything that passes – see radcli_dae_set_handler(). Also: drains any in-flight RADCLI_REQUEST_SENDONLY exchange's reply and services its retransmit/ timeout deadline (net2.md's REQ-NET2-SEND-013/016), and, once due, sends the RFC 5997 watchdog on an established RadSec session (watchdog.md's REQ-WATCHDOG-NET-001) – all unconditionally, every call, regardless of whether an active radcli_dae exists at all: this is now the single entry point radcli_ctx_get_poll() drives, for everything ctx owns, not a DAE-only call.

On the DAE side specifically, reads exactly one datagram per call (non-blocking), so a burst of requests re-arms the caller's loop rather than starving it, and runs it through process_packet()'s validation pipeline before ever invoking the registered handler. Every rejection short of "authorized sender" is silent: no reply, no Error-Cause, no log of the secret or either authenticator (REQ-DAE-SEC-009).

Not reentrant: calling this, radcli_dae_start(), or radcli_dae_free() from within a handler radcli_ctx_dispatch() itself invoked is undefined.

Parameters
ctxa context previously reported ready by radcli_ctx_get_poll().
Returns
0 on success (including "nothing was ready"), -1 on failure (e.g. ctx is NULL, or called reentrantly).

Definition at line 2324 of file dae.c.

◆ radcli_ctx_get_poll()

int radcli_ctx_get_poll ( radcli_ctx * ctx,
struct pollfd * pfds,
size_t max_pfds,
size_t * nfds,
int * timeout_ms )

Report what to wait for on ctx's behalf, for the caller's own event loop – radcli never calls poll()/select()/epoll_wait() itself.

There is no per-object descriptor accessor (e.g. no radcli_dae_fd(), no per-radcli_request one either – net2.md's REQ-NET2-SEND-013): every descriptor this reports belongs to ctx, not to any one radcli_dae or radcli_request, so that a transport sharing one descriptor between dynamic authorization and ordinary requests (already true for TLS/DTLS) never leaves an application holding a watcher on a descriptor that has quietly started meaning something else.

For TLS/DTLS, or for a UDP ctx with no active radcli_dae, this is always exactly one descriptor: the session fd (TLS/DTLS, also carrying any in-flight RADCLI_REQUEST_SENDONLY traffic) or the request-registry socket (UDP, REQ-NET2-SEND-016). A UDP ctx with an active radcli_dae reports a second, independent descriptor for the DAE listener alongside it – the two are genuinely different local sockets/ports and cannot be merged into one without changing the wire protocol; RADCLI_CTX_MAX_POLLFDS (2) is the maximum this API ever needs. *timeout_ms folds together every deadline source that applies (DAE queued-work, RadSec watchdog, and any in-flight RADCLI_REQUEST_SENDONLY exchange's own retransmit/timeout) into one caller-facing value, so the caller never computes a min() itself.

A descriptor is closed or replaced only during a call the application itself makes (radcli_ctx_dispatch(), radcli_dae_free(), radcli_request_perform(), rc_destroy()), never asynchronously – but re-query after any of those regardless, since one may replace it.

Parameters
ctxa context, with or without an active radcli_dae.
[out]pfdsfilled with up to RADCLI_CTX_MAX_POLLFDS entries (fd/events; revents is left for the caller's poll() to fill in).
max_pfdspfds's capacity; MUST be at least RADCLI_CTX_MAX_POLLFDS.
[out]nfdsset to how many of pfds were filled in (0 if there is nothing to watch yet).
[out]timeout_msmilliseconds after which to call radcli_ctx_dispatch() even without I/O readiness; -1 for "no timeout needed".
Returns
0 on success, -1 if ctx or an out-parameter is NULL, or max_pfds is too small.

Definition at line 1091 of file dae.c.

◆ radcli_dae_free()

void radcli_dae_free ( radcli_dae * dae)

Release a listener, closing its socket if radcli_dae_start() opened one.

Parameters
daea listener from radcli_dae_new(); NULL is accepted and ignored.

Definition at line 978 of file dae.c.

◆ radcli_dae_new()

radcli_dae * radcli_dae_new ( radcli_ctx * ctx,
unsigned flags )

Validate dae-* configuration and build a dynamic-authorization listener. Opens no socket – see radcli_dae_start().

Fails (returns NULL) unless dae-accept is "yes" or "udp", so that a library upgrade never silently exposes a session-terminating channel in an application that did not opt in. When enabled, also fails unless both dae-server and dae-secret are set, and unless every dae-server entry resolves and carries no network prefix.

At most one radcli_dae may be active on a given ctx at a time, since radcli_ctx_get_poll()/radcli_ctx_dispatch() operate on ctx and need a single descriptor to report.

Parameters
ctxa configured context (rc_read_config()/rc_apply_config() already called).
flagsa bitwise OR of radcli_dae_flags, or 0 for the common case.
Returns
a new listener, or NULL on invalid configuration or an unknown flags bit.

Definition at line 638 of file dae.c.

◆ radcli_dae_process()

int radcli_dae_process ( radcli_dae * dae,
const void * buf,
size_t len,
const struct sockaddr * from,
socklen_t fromlen,
radcli_dae_request ** req )

Validate a caller-supplied packet, without a radcli-owned socket – the L0 counterpart of radcli_ctx_dispatch(), running the identical validation pipeline (REQ-DAE-NET-003) on a buffer and source address the caller supplies instead of reading them from radcli_dae_start()'s socket. A request this produces is otherwise indistinguishable from one radcli_ctx_dispatch() would have delivered to a handler.

Parameters
daea listener from radcli_dae_new() (radcli_dae_start() need never have been called: this function reads no socket). Rejected outright (-1) if dae is following serv-type=tls/dtls (RadSec): that transport trusts the record's origin entirely to the already-verified TLS/DTLS session (REQ-DAE-SEC-015), which this buffer/address pair is not, and has no source-address check of its own to apply to it.
bufthe received packet, header included.
lenbuf's length.
fromthe packet's source address, for the dae-server authorization check.
fromlenfrom's length. Must be at least sizeof(struct sockaddr_in) and at most sizeof(struct sockaddr_storage), and consistent with the family from->sa_family declares (AF_INET or AF_INET6 only, matching what dae-server can authorize) – an application relaying from and fromlen from an untrusted producer (e.g. over IPC from a privileged listener) must not assume this function corrects a mismatch; it only rejects one.
[out]reqset to the validated request on success (RADCLI_DAE_NEW or RADCLI_DAE_DUPLICATE), left NULL on failure.
Returns
RADCLI_DAE_NEW or RADCLI_DAE_DUPLICATE on success, -1 if the packet failed validation (discarded silently, exactly as radcli_ctx_dispatch() would), dae is a RadSec listener, or any argument is invalid.

Definition at line 2487 of file dae.c.

◆ radcli_dae_reply()

int radcli_dae_reply ( radcli_dae_request * req,
int ack )

Answer a request with an ACK or NAK, selecting 41/42 or 44/45 from the request's own code, mirroring its Proxy-State attributes, and computing the Response Authenticator over the request's Authenticator.

Parameters
reqa request passed to a radcli_dae_handler, not yet replied to.
acknon-zero for an ACK (Disconnect-ACK/CoA-ACK), zero for a bare NAK with no Error-Cause attribute – most callers rejecting a request should use radcli_dae_reply_error() instead, which also states why.
Returns
0 once the reply is handed to the network, -1 on failure (req is NULL, already replied to, or the reply could not be sent).

Definition at line 1806 of file dae.c.

◆ radcli_dae_reply_error()

int radcli_dae_reply_error ( radcli_dae_request * req,
uint32_t error_cause )

Answer a request with a NAK carrying the given Error-Cause.

Parameters
reqa request passed to a radcli_dae_handler, not yet replied to.
error_causea radcli_error_cause value (e.g. RADCLI_ERROR_SESSION_CONTEXT_NOT_FOUND), encoded as attribute 101.
Returns
0 once the reply is handed to the network, -1 on failure (req is NULL, already replied to, or the reply could not be sent).

Definition at line 1821 of file dae.c.

◆ radcli_dae_reply_to_buffer()

int radcli_dae_reply_to_buffer ( radcli_dae_request * req,
int ack,
uint32_t error_cause,
void * buf,
size_t * len )

Produce a reply as bytes instead of sending it – the L0 counterpart of radcli_dae_reply()/radcli_dae_reply_error(), for a request that came from radcli_dae_process().

For a request radcli_dae_process() returned RADCLI_DAE_DUPLICATE for, ack and error_cause are ignored: a genuine retransmission always gets the same answer it originally got (RFC 5176 SS2.3), never a fresh one, so the bytes produced are always that cached decision's.

Parameters
reqa request from radcli_dae_process(), not yet replied to (unless RADCLI_DAE_DUPLICATE, which may be called any number of times).
acknon-zero for an ACK, zero for a NAK – ignored if req is a RADCLI_DAE_DUPLICATE.
error_causea radcli_error_cause value for a NAK, or 0 for a bare one – ignored if req is a RADCLI_DAE_DUPLICATE.
[out]buffilled with the reply's bytes on success.
[in,out]lenbuf's capacity on entry; the reply's actual length on success.
Returns
0 on success, -1 on failure (any argument NULL, req already replied to and not a RADCLI_DAE_DUPLICATE, or buf too small).

Definition at line 1852 of file dae.c.

◆ radcli_dae_req_attrs()

const radcli_avp_list * radcli_dae_req_attrs ( const radcli_dae_request * req)

Return the request's decoded attributes.

Parameters
reqa request passed to a radcli_dae_handler.
Returns
the attribute list, owned by req and valid until it is freed; NULL if req is NULL. Never NULL for a request the handler actually received: an attribute-free Disconnect-Request/CoA-Request still yields a valid, empty list.

Definition at line 1608 of file dae.c.

◆ radcli_dae_req_code()

radcli_code radcli_dae_req_code ( const radcli_dae_request * req)

Return the received packet's RADIUS code.

Parameters
reqa request passed to a radcli_dae_handler.
Returns
RADCLI_DISCONNECT_REQUEST or RADCLI_COA_REQUEST (no other code ever reaches a handler – see radcli_ctx_dispatch()'s doc comment), or 0 if req is NULL.

Definition at line 1593 of file dae.c.

◆ radcli_dae_req_framed_ip()

int radcli_dae_req_framed_ip ( const radcli_dae_request * req,
struct sockaddr_storage * out )

Return the request's Framed-IP-Address or Framed-IPv6-Address.

Parameters
reqa request passed to a radcli_dae_handler.
[out]outfilled with an AF_INET or AF_INET6 address on success; untouched on failure.
Returns
0 on success, -1 if req or out is NULL, or the request carried neither attribute.

Definition at line 1649 of file dae.c.

◆ radcli_dae_req_nas_port()

int radcli_dae_req_nas_port ( const radcli_dae_request * req,
uint32_t * out )

Return the request's NAS-Port.

Parameters
reqa request passed to a radcli_dae_handler.
[out]outfilled with the value on success; may be NULL to just check presence.
Returns
0 on success, -1 if req is NULL or the request carried no NAS-Port.

Definition at line 1704 of file dae.c.

◆ radcli_dae_req_session_id()

const char * radcli_dae_req_session_id ( const radcli_dae_request * req)

Return the request's Acct-Session-Id, if it carried one.

Parameters
reqa request passed to a radcli_dae_handler.
Returns
a NUL-terminated string owned by req and valid until it is freed, or NULL if req is NULL or carried no Acct-Session-Id.

Definition at line 1621 of file dae.c.

◆ radcli_dae_req_user_name()

const char * radcli_dae_req_user_name ( const radcli_dae_request * req)

Return the request's User-Name, if it carried one.

Parameters
reqa request passed to a radcli_dae_handler.
Returns
a NUL-terminated string owned by req and valid until it is freed, or NULL if req is NULL or carried no User-Name.

Definition at line 1634 of file dae.c.

◆ radcli_dae_request_free()

void radcli_dae_request_free ( radcli_dae_request * req)

Release a request.

Parameters
reqa request passed to a radcli_dae_handler, or from radcli_dae_process(); NULL is accepted and ignored. Replying is optional before freeing: an unanswered request simply gets no reply.

Definition at line 1890 of file dae.c.

◆ radcli_dae_set_handler()

void radcli_dae_set_handler ( radcli_dae * dae,
radcli_dae_handler cb,
void * user )

Register the callback radcli_ctx_dispatch() invokes for each validated request. May be called before or after radcli_dae_start().

Parameters
daea listener from radcli_dae_new().
cbthe callback; NULL clears a previously registered one.
userpassed back to cb unchanged.

Definition at line 852 of file dae.c.

◆ radcli_dae_start()

int radcli_dae_start ( radcli_dae * dae)

Start receiving: binds the socket described by dae-listen.

Parameters
daea listener from radcli_dae_new().
Returns
0 on success, -1 on failure (e.g. the address is already in use).

Definition at line 896 of file dae.c.