summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--develdoc.txt281
1 files changed, 281 insertions, 0 deletions
diff --git a/develdoc.txt b/develdoc.txt
new file mode 100644
index 0000000..d3ee6f7
--- /dev/null
+++ b/develdoc.txt
@@ -0,0 +1,281 @@
+radsecproxy documentation for developers
+
+1. Overall design
+
+At startup client and server configurations are read. Two lists
+are created, called clconfs and srvconfs. Both contain clsrvconf
+structs.
+
+For each server config, a client writer thread is created. This
+takes care of sending requests to a server.
+
+Next for each known transport type which has a configured client,
+we create listeners. Typically there is a default server port
+that will be listened on, but multiple ports might be configured.
+For each port there will normally be 1-2 sockets (IPv4 and/or IPv6).
+For each socket a thread is created with the listener() defined for
+the transport.
+
+This is all that happens in the main thread. The threads created
+above need to take care of the rest.
+
+Client writers are generally responsible for sending messages to
+clients, and if necessary creating and maintaining connections to
+the client. Client writers create threads for handling replies from
+servers. If connections are used, one thread is created for reading
+from each connection. clientwr() will use connecter() and
+clientconnreader() definitions for the transport.
+
+The listeners may receive RADIUS messages directly, which is the
+case for UDP which is not connection based. Or they may receive
+connections and create a new thread for handling each incoming
+connection, where that thread will receive RADIUS messages.
+The function receiving RADIUS client requests is generally called
+xxxserverrd, where xxx is the transport name. The server reader
+is responsible for creating a server writer thread that takes care
+of sending RADIUS replies to a client.
+
+2. RADIUS message processing
+
+In 1 we described the threads used and the high level operations.
+We will now describe how RADIUS messages are processed and flow
+through the system.
+
+An incoming RADIUS request from a client is handled by a server
+reader. The server reader calls newrequest() to obtain a request
+struct. It sets rq->buf to point to the received message, rq->from
+to point to the client struct for the client, and might for some
+transports specify additional data. E.g. for UDP, the source port
+and socket it was received on. The reader must make sure buf is
+at least as large as the specified RADIUS message length. The
+client reader calls radsrv(rq). When that returns it just waits
+for the next message.
+
+radsrv() is in a way the core part of the proxy. It takes care
+of validation, processing and routing of incoming requests.
+It first creates a radmsg struct calling buf2radmsg(). This also
+takes care of basic validation (e.g. that lengths add up) and
+checking message authenticators. Unless it receives a valid
+Access Request, Accounting Request or Status Server, it will
+drop the request and return.
+
+It next calls addclientrq() which adds this request to a request
+queue for rq->from. Before adding it to the queue, it checks if
+it is a duplicate of something already in the queue. If a
+duplicate, radsrv() drops the request and returns.
+
+Next radsrv() checks if it received a Status Server message. In
+that case it responds with Access Accept and returns.
+
+Next it applies any rewritein rules and also checks TTL attribute.
+If TTL expired, it will drop request and return.
+
+Next it looks for a User-Name attribute. If not present it will
+will drop the request. However, if it is an accounting request
+it will first send an accounting response.
+
+Next it calls findserver() to pick a server for sending the
+message to. For this it will use the user-name attribute and the
+realm definitions. It also takes into account which servers are
+alive.
+
+If no server is found it will drop the message. However, in
+certain cases it may send a reject or accounting response message.
+
+Next it reencrypts any attributes that are encrypted based on
+the secrets of clients/servers. And after that, decrements TTL if
+present, and applies any rewriteout rules.
+
+Finally radsrv() calls sendrq(rq) to pass the request to the
+chosen server.
+
+sendrq() checks the request queue for a server. The request queue
+is an array holding 256 entries, one for each possible message ID.
+Normally it will start looking for a free slot at the ID after the
+last entry inserted in the queue (0 follows 255). However in a
+special case where sendrq() is called to send a status-server message,
+it will always use ID 0. If status-server is enabled, ID 0 is not used
+for other requests. If there are no free slots, the message is
+discarded.
+
+When finding a free slot, it does, "to->requests[i].rq = rq" and
+signals the writer thread : "pthread_cond_signal(&to->newrq_cond)".
+After that, it returns, and the server reader thread can wait for a
+new client request.
+
+We will now consider the client writer thread that takes care of
+sending this request to a server.
+
+clientwr() continually looks for requests in its request buffer
+and tries to send them to a server. It uses timers so that it can
+sleep waiting for a new request, or sending status server, or
+re-sending an existing request. When a new request comes in, it
+will send it ASAP to the server and set tries to 1. For the
+server there is a retryinterval timer. retryinterval seconds later
+clientwr() will resend or remove the request. It is removed if the
+server's retrycount parameter is exceeded (0 retries if reliable
+transport). Status server messages are never resent.
+
+The handling of the request stops here, unless we get a reply.
+We will now describe how replies are handled.
+
+Each transport has a function called something xxxclientrd() for
+receiving replies from a server. This is run as a separate thread.
+All they do is read a RADIUS message and call replyh(server, buf)
+where server points to the server struct for the server, and buf
+is a buffer large enough to contain the entire RADIUS message. It
+will not read another message until replyh() returns.
+
+We will now consider replyh(). It will first check if there is an
+outstanding request matching the id of the reply. This is done by
+checking the request queue of the server.
+
+If it maches a request, it will validate and authenticate the
+reply by calling buf2radmsg(). If this fails or the message type
+is not one of Access Accept, Access Reject, Access Challenge or
+Accounting Response, the reply is ignored.
+
+If the request was a status-server message, it is simply removes
+the request and returns.
+
+Next it will apply any rewritein rules and check TTL attribute if
+present. If TTL is exceeded, the reply is ignored.
+
+Next it reencrypts some attributes with the secret of the client
+the original request came from, and which the reply will be sent
+back to. It also applies any rewriteout rules.
+
+Finally to pass the reply back to the client, it does
+"rqout->rq->msg = msg" to store the reply message in the request,
+and calls sendreply() with rqout->rq as parameter. When
+sendreply() returns, we free the request from the server's
+request queue. This also means that the ID can be used for a new
+request.
+
+Now about sendreply(). All it does, is basically to assemble the
+reply message, take care of authenticators and set rq->replybuf
+to point to the result. After that it adds a pointer to rq to
+the clients reply queue, and signals the server writer who is
+responsible for sending replies to the client.
+
+The server writer is a separate thread created by the server reader,
+typically called something like xxxserverwr. All it does is to send
+whatever it finds in its replyq to the client and removes it. When
+the queue is empty it waits for a signal from a sendreply().
+
+The above shows the complete flow. It might be worth also looking a
+bit more at the state created for each request though.
+
+As mentioned above, each request received from a client is stored in
+request queue for the client. The request is stored in a request
+struct which looks like this:
+
+struct request {
+ uint8_t *buf, *replybuf;
+ struct radmsg *msg;
+ ...
+};
+
+This request will remain in the queue until a new request is
+received with the same id and which is not a duplicate. The
+new one then replaces the previous.
+
+Initially for a new request, only buf is used (of the above specified
+fields). Next the message is parsed and validated, and if ok, it is
+stored in msg. buf with the request is freed.
+
+In sendrq() a request that is to be sent to a server, is again
+reassembled from rq->msg into rq->buf.
+
+When a reply is received, it will again be parsed and validated, and
+if ok, it will free the old rq->msg, and store the new instead.
+
+Finally, in sendreply() rq->replybuf is created from rq->msg, and
+rq->msg is freed. rq->replybuf is kept so that if a duplicate request
+is received later, we can just return rq->replybuf.
+
+It appears that rq->buf created by sendrq() is stored until the
+request is replaced, it should be freed when replyh replaces rq->msg
+or in freerqoutdata().
+
+Request structs should perhaps be freed when they "expire", rather
+than wait until a new request with the same ID comes along.
+
+
+x. Transports
+
+struct protodefs protodefs[] contains definitions of the different
+transport protocols. We will here describe the different parameters.
+
+struct protodefs {
+ char *name;
+This should be a textual name for the transport, e.g. "udp". This is
+used in client/server configurations and for debug/log messages.
+
+ char *secretdefault;
+Some transports like TLS that provides strong encryption, may have a
+default RADIUS secret, since the RADIUS encryption is not needed.
+
+ uint8_t socktype;
+Typically set to SOCK_DGRAM or SOCK_STREAM. This is used when a
+socket for the transport is created.
+
+ char *portdefault;
+The default server port for the transport, e.g. 1812.
+
+ uint8_t retrycountdefault;
+How many time a client request should be resent. For a reliable
+transport like TCP/TLS, this should be 0.
+
+ uint8_t retrycountmax;
+The maximum allowed configurable value for retrycount. For reliable
+transport it should probably be 0.
+
+ uint8_t retryintervaldefault;
+This is the default for how many seconds there should be between each
+retry. For a reliable transport with 0 retries, this controls how
+long it should wait for a reply to the client request.
+
+ uint8_t retryintervalmax;
+This is the maximum allowed retryinterval
+
+ uint8_t duplicateintervaldefault;
+This is the time period two requests with the same UDP source port
+and request authenticator are considered duplicates. If a reply has
+been sent to the first request, then that is resent. If no reply
+has been sent, the second request is ignored.
+
+ void *(*listener)(void*);
+Each transport must define a listener function for the sockets
+created by the transport. The socket is created by radsecproxy
+core. If successful, the listener is called.
+
+ int (*connecter)(struct server *, struct timeval *, int, char *);
+When creating a new server, a clientwr() thread is created for sending
+requests to the server. If a connecter() is defined for the transport,
+clientwr() will call connecter() and exit if connecter() returns 0.
+
+ void *(*clientconnreader)(void*);
+If a connecter() is defined, then when that successfully returns,
+a separate thread is created for reading from the connection. This
+thread is responsible for handling replies from a server.
+
+ int (*clientradput)(struct server *, unsigned char *);
+Used by clientwr() for sending a RADIUS message.
+
+ void (*addclient)(struct client *);
+Need only be defined if need to override default client creation.
+Used by UDP to have a common reply queue, rather than one per client.
+
+ void (*addserverextra)(struct clsrvconf *);
+Need only be defined if something needs to be done in addition to
+the default server creation.
+
+ uint8_t freesrcprotores;
+Whether should free the resolver state for source ports and
+addresses after initial startup.
+
+ void (*initextra)();
+Can be defined it extra initialisation is needed for the transport.
+
+};