radsecproxy-1.6.5.
[radsecproxy.git] / develdoc.txt
1 radsecproxy documentation for developers
2
3 1. Overall design
4 =================
5
6 At startup client and server configurations are read. Two lists
7 are created, called clconfs and srvconfs. Both contain clsrvconf
8 structs.
9
10 For each server config, a client writer thread is created. This
11 takes care of sending requests to a server.
12
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
18 the transport.
19
20 This is all that happens in the main thread. The threads created
21 above need to take care of the rest.
22
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.
29
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.
38
39
40 2. RADIUS message processing
41 ============================
42
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
45 through the system.
46
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
55 for the next message.
56
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.
64
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.
69
70 Next radsrv() checks if it received a Status Server message. In
71 that case it responds with Access Accept and returns.
72
73 Next it applies any rewritein rules and also checks TTL attribute.
74 If TTL expired, it will drop request and return.
75
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.
79
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
83 alive.
84
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.
87
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.
91
92 Finally radsrv() calls sendrq(rq) to pass the request to the
93 chosen server.
94
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
102 discarded.
103
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
107 new client request.
108
109 We will now consider the client writer thread that takes care of
110 sending this request to a server.
111
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.
121
122 The handling of the request stops here, unless we get a reply.
123 We will now describe how replies are handled.
124
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.
131
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.
135
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.
140
141 If the request was a status-server message, it simply removes
142 the request and returns.
143
144 Next it will apply any rewritein rules and check TTL attribute if
145 present. If TTL is exceeded, the reply is ignored.
146
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.
150
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
156 request.
157
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.
163
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().
168
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.
171
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:
175
176 struct request {
177     uint8_t *buf, *replybuf;
178     struct radmsg *msg;
179     ...
180 };
181
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.
185
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.
189
190 In sendrq() a request that is to be sent to a server, is again
191 reassembled from rq->msg into rq->buf.
192
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.
195
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.
199
200 rq->buf is removed by freerqoutdata(), because then we will not try
201 to send the request in rq->buf any more.
202
203 Request structs should perhaps be freed when they "expire", rather
204 than wait until a new request with the same ID comes along.
205
206 x. Transports
207
208 struct protodefs protodefs[] contains definitions of the different
209 transport protocols. We will here describe the different parameters.
210
211 struct protodefs {
212     char *name;
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.
215     
216     char *secretdefault;
217 Some transports like TLS that provides strong encryption, may have a
218 default RADIUS secret, since the RADIUS encryption is not needed.
219     
220     uint8_t socktype;
221 Typically set to SOCK_DGRAM or SOCK_STREAM. This is used when a
222 socket for the transport is created.
223     
224     char *portdefault;
225 The default server port for the transport, e.g. 1812.
226     
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.
230     
231     uint8_t retrycountmax;
232 The maximum allowed configurable value for retrycount. For reliable
233 transport it should probably be 0.
234     
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.
239     
240     uint8_t retryintervalmax;
241 This is the maximum allowed retryinterval
242     
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.
248     
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.
253     
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.
258
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.
263
264     int (*clientradput)(struct server *, unsigned char *);
265 Used by clientwr() for sending a RADIUS message.
266
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.
270
271     void (*addserverextra)(struct clsrvconf *);
272 Need only be defined if something needs to be done in addition to
273 the default server creation.
274     
275     uint8_t freesrcprotores;
276 Whether should free the resolver state for source ports and
277 addresses after initial startup.
278
279     void (*initextra)();
280 Can be defined it extra initialisation is needed for the transport.
281
282 };
283
284
285 3. Dynamic servers
286 ==================
287
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.
293
294 [XXX describe what happens when config is read -- set in
295 createsubrealmservers() if the server block has the
296 'dynamicLookupCommand' option]
297
298 [XXX describe what happens when a packet is handled -- findserver()
299 creating a realm object]
300
301 [XXX describe the non-staticness of TLS connections to dynamically
302 resolved servers -- tlsclientrd and IDLE_TIMEOUT]
303
304 Dynamic freeing of resources
305 ----------------------------
306
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).
310
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.
315
316 The _internal_removeserversubrealms(realm list, SERVER) function
317 traverses the list of realms and for each realm:
318
319   - increase the refcount on the realm
320
321   - take the lock on the realm
322
323   - for srv in realm->srvconfs: if srv == SERVER: decrease ref on realm
324
325   - free all servers in realm->srvconfs matching SERVER
326     (list_removedata() TODO: is this function correct?)
327
328   - same thing for realm->accsrvconfs as for srvconfs
329
330   - if none of the realm->srvconfs nor the realm->accsrvonfs has a
331     dynamiclookupcommand:
332
333     - for each srv in realm->srvconfs: free srv and decrease ref on realm
334
335     - destroy realm->srvconfs
336
337     - same thing for realm->accsrvconfs as for srvconfs
338
339     - release the realm lock
340
341     - decrease the refcount on the realm
342
343 freeclsrvconf() performs 15 calls to free() and also invokes
344 freegconfmstr(), freehostports(), regfree() and
345 pthread_mutex_destroy().