1 radsecproxy documentation for developers
6 At startup client and server configurations are read. Two lists
7 are created, called clconfs and srvconfs. Both contain clsrvconf
10 For each server config, a client writer thread is created. This
11 takes care of sending requests to a server.
13 Next for each known transport type which has a configured client,
14 we create listeners. Typically there is a default server port
15 that will be listened on, but multiple ports might be configured.
16 For each port there will normally be 1-2 sockets (IPv4 and/or IPv6).
17 For each socket a thread is created with the listener() defined for
20 This is all that happens in the main thread. The threads created
21 above need to take care of the rest.
23 Client writers are generally responsible for sending messages to
24 clients, and if necessary creating and maintaining connections to
25 the client. Client writers create threads for handling replies from
26 servers. If connections are used, one thread is created for reading
27 from each connection. clientwr() will use connecter() and
28 clientconnreader() definitions for the transport.
30 The listeners may receive RADIUS messages directly, which is the
31 case for UDP which is not connection based. Or they may receive
32 connections and create a new thread for handling each incoming
33 connection, where that thread will receive RADIUS messages.
34 The function receiving RADIUS client requests is generally called
35 xxxserverrd, where xxx is the transport name. The server reader
36 is responsible for creating a server writer thread that takes care
37 of sending RADIUS replies to a client.
40 2. RADIUS message processing
41 ============================
43 In 1 we described the threads used and the high level operations.
44 We will now describe how RADIUS messages are processed and flow
47 An incoming RADIUS request from a client is handled by a server
48 reader. The server reader calls newrequest() to obtain a request
49 struct. It sets rq->buf to point to the received message, rq->from
50 to point to the client struct for the client, and might for some
51 transports specify additional data. E.g. for UDP, the source port
52 and socket it was received on. The reader must make sure buf is
53 at least as large as the specified RADIUS message length. The
54 client reader calls radsrv(rq). When that returns it just waits
57 radsrv() is in a way the core part of the proxy. It takes care
58 of validation, processing and routing of incoming requests.
59 It first creates a radmsg struct calling buf2radmsg(). This also
60 takes care of basic validation (e.g. that lengths add up) and
61 checking message authenticators. Unless it receives a valid
62 Access Request, Accounting Request or Status Server, it will
63 drop the request and return.
65 It next calls addclientrq() which adds this request to a request
66 queue for rq->from. Before adding it to the queue, it checks if
67 it is a duplicate of something already in the queue. If a
68 duplicate, radsrv() drops the request and returns.
70 Next radsrv() checks if it received a Status Server message. In
71 that case it responds with Access Accept and returns.
73 Next it applies any rewritein rules and also checks TTL attribute.
74 If TTL expired, it will drop request and return.
76 Next it looks for a User-Name attribute. If not present it will
77 will drop the request. However, if it is an accounting request
78 it will first send an accounting response.
80 Next it calls findserver() to pick a server for sending the
81 message to. For this it will use the user-name attribute and the
82 realm definitions. It also takes into account which servers are
85 If no server is found it will drop the message. However, in
86 certain cases it may send a reject or accounting response message.
88 Next it reencrypts any attributes that are encrypted based on
89 the secrets of clients/servers. And after that, decrements TTL if
90 present, and applies any rewriteout rules.
92 Finally radsrv() calls sendrq(rq) to pass the request to the
95 sendrq() checks the request queue for a server. The request queue
96 is an array holding 256 entries, one for each possible message ID.
97 Normally it will start looking for a free slot at the ID after the
98 last entry inserted in the queue (0 follows 255). However in a
99 special case where sendrq() is called to send a status-server message,
100 it will always use ID 0. If status-server is enabled, ID 0 is not used
101 for other requests. If there are no free slots, the message is
104 When finding a free slot, it does, "to->requests[i].rq = rq" and
105 signals the writer thread : "pthread_cond_signal(&to->newrq_cond)".
106 After that, it returns, and the server reader thread can wait for a
109 We will now consider the client writer thread that takes care of
110 sending this request to a server.
112 clientwr() continually looks for requests in its request buffer
113 and tries to send them to a server. It uses timers so that it can
114 sleep waiting for a new request, or sending status server, or
115 re-sending an existing request. When a new request comes in, it
116 will send it ASAP to the server and set tries to 1. For the
117 server there is a retryinterval timer. retryinterval seconds later
118 clientwr() will resend or remove the request. It is removed if the
119 server's retrycount parameter is exceeded (0 retries if reliable
120 transport). Status server messages are never resent.
122 The handling of the request stops here, unless we get a reply.
123 We will now describe how replies are handled.
125 Each transport has a function called something xxxclientrd() for
126 receiving replies from a server. This is run as a separate thread.
127 All they do is read a RADIUS message and call replyh(server, buf)
128 where server points to the server struct for the server, and buf
129 is a buffer large enough to contain the entire RADIUS message. It
130 will not read another message until replyh() returns.
132 We will now consider replyh(). It will first check if there is an
133 outstanding request matching the id of the reply. This is done by
134 checking the request queue of the server.
136 If it maches a request, it will validate and authenticate the
137 reply by calling buf2radmsg(). If this fails or the message type
138 is not one of Access Accept, Access Reject, Access Challenge or
139 Accounting Response, the reply is ignored.
141 If the request was a status-server message, it simply removes
142 the request and returns.
144 Next it will apply any rewritein rules and check TTL attribute if
145 present. If TTL is exceeded, the reply is ignored.
147 Next it reencrypts some attributes with the secret of the client
148 the original request came from, and which the reply will be sent
149 back to. It also applies any rewriteout rules.
151 Finally to pass the reply back to the client, it does
152 "rqout->rq->msg = msg" to store the reply message in the request,
153 and calls sendreply() with rqout->rq as parameter. When
154 sendreply() returns, we free the request from the server's
155 request queue. This also means that the ID can be used for a new
158 Now about sendreply(). All it does is basically to assemble the
159 reply message, take care of authenticators and set rq->replybuf
160 to point to the result. After that it adds a pointer to rq to
161 the clients reply queue and signals the server writer who is
162 responsible for sending replies to the client.
164 The server writer is a separate thread created by the server reader,
165 typically called something like xxxserverwr. All it does is to send
166 whatever it finds in its replyq to the client and remove it. When
167 the queue is empty it waits for a signal from a sendreply().
169 The above shows the complete flow. It might be worth also looking a
170 bit more at the state created for each request though.
172 As mentioned above, each request received from a client is stored in
173 request queue for the client. The request is stored in a request
174 struct which looks like this:
177 uint8_t *buf, *replybuf;
182 This request will remain in the queue until a new request is
183 received with the same id and which is not a duplicate. The
184 new one then replaces the previous.
186 Initially for a new request, only buf is used (of the above specified
187 fields). Next the message is parsed and validated, and if ok, it is
188 stored in msg. buf with the request is freed.
190 In sendrq() a request that is to be sent to a server, is again
191 reassembled from rq->msg into rq->buf.
193 When a reply is received, it will again be parsed and validated, and
194 if ok, it will free the old rq->msg, and store the new instead.
196 Finally, in sendreply() rq->replybuf is created from rq->msg, and
197 rq->msg is freed. rq->replybuf is kept so that if a duplicate request
198 is received later, we can just return rq->replybuf.
200 rq->buf is removed by freerqoutdata(), because then we will not try
201 to send the request in rq->buf any more.
203 Request structs should perhaps be freed when they "expire", rather
204 than wait until a new request with the same ID comes along.
208 struct protodefs protodefs[] contains definitions of the different
209 transport protocols. We will here describe the different parameters.
213 This should be a textual name for the transport, e.g. "udp". This is
214 used in client/server configurations and for debug/log messages.
217 Some transports like TLS that provides strong encryption, may have a
218 default RADIUS secret, since the RADIUS encryption is not needed.
221 Typically set to SOCK_DGRAM or SOCK_STREAM. This is used when a
222 socket for the transport is created.
225 The default server port for the transport, e.g. 1812.
227 uint8_t retrycountdefault;
228 How many time a client request should be resent. For a reliable
229 transport like TCP/TLS, this should be 0.
231 uint8_t retrycountmax;
232 The maximum allowed configurable value for retrycount. For reliable
233 transport it should probably be 0.
235 uint8_t retryintervaldefault;
236 This is the default for how many seconds there should be between each
237 retry. For a reliable transport with 0 retries, this controls how
238 long it should wait for a reply to the client request.
240 uint8_t retryintervalmax;
241 This is the maximum allowed retryinterval
243 uint8_t duplicateintervaldefault;
244 This is the time period two requests with the same UDP source port
245 and request authenticator are considered duplicates. If a reply has
246 been sent to the first request, then that is resent. If no reply
247 has been sent, the second request is ignored.
249 void *(*listener)(void*);
250 Each transport must define a listener function for the sockets
251 created by the transport. The socket is created by radsecproxy
252 core. If successful, the listener is called.
254 int (*connecter)(struct server *, struct timeval *, int, char *);
255 When creating a new server, a clientwr() thread is created for sending
256 requests to the server. If a connecter() is defined for the transport,
257 clientwr() will call connecter() and exit if connecter() returns 0.
259 void *(*clientconnreader)(void*);
260 If a connecter() is defined, then when that successfully returns,
261 a separate thread is created for reading from the connection. This
262 thread is responsible for handling replies from a server.
264 int (*clientradput)(struct server *, unsigned char *);
265 Used by clientwr() for sending a RADIUS message.
267 void (*addclient)(struct client *);
268 Need only be defined if need to override default client creation.
269 Used by UDP to have a common reply queue, rather than one per client.
271 void (*addserverextra)(struct clsrvconf *);
272 Need only be defined if something needs to be done in addition to
273 the default server creation.
275 uint8_t freesrcprotores;
276 Whether should free the resolver state for source ports and
277 addresses after initial startup.
280 Can be defined it extra initialisation is needed for the transport.
288 A server block can contain the 'dynamicLookupCommand' option, naming
289 an executable file which will be executed (fork, exec) with the realm
290 of the user being authenticated as its only argument. The output from
291 the program (read from stdout) is used as configuration text and
292 parsed as if it was read from a configuration file.
294 [XXX describe what happens when config is read -- set in
295 createsubrealmservers() if the server block has the
296 'dynamicLookupCommand' option]
298 [XXX describe what happens when a packet is handled -- findserver()
299 creating a realm object]
301 [XXX describe the non-staticness of TLS connections to dynamically
302 resolved servers -- tlsclientrd and IDLE_TIMEOUT]
304 Dynamic freeing of resources
305 ----------------------------
307 At the end of clientwr(), if server->dynamiclookuparg != NULL, the
308 removeserversubrealms(list, server) function is called and normally
309 so is freeclsrvconf(server).
311 removeserversubrealms() traverses the list of realms (taking a lock on
312 each realm) and calls _internal_removeserversubrealms(subrealm,
313 server) on each subrealm. If the list of subrealms is empty after
314 this, it's being destroyed.
316 The _internal_removeserversubrealms(realm list, SERVER) function
317 traverses the list of realms and for each realm:
319 - increase the refcount on the realm
321 - take the lock on the realm
323 - for srv in realm->srvconfs: if srv == SERVER: decrease ref on realm
325 - free all servers in realm->srvconfs matching SERVER
326 (list_removedata() TODO: is this function correct?)
328 - same thing for realm->accsrvconfs as for srvconfs
330 - if none of the realm->srvconfs nor the realm->accsrvonfs has a
331 dynamiclookupcommand:
333 - for each srv in realm->srvconfs: free srv and decrease ref on realm
335 - destroy realm->srvconfs
337 - same thing for realm->accsrvconfs as for srvconfs
339 - release the realm lock
341 - decrease the refcount on the realm
343 freeclsrvconf() performs 15 calls to free() and also invokes
344 freegconfmstr(), freehostports(), regfree() and
345 pthread_mutex_destroy().