Radclient Data Structures
-------------------------

How the RADIUS client handles asynchronous operation and retransmission across
multiple servers will not be immediately obvious from reading the source. Some
pointers:


1. Queries
----------

A query is the set of request attributes that are received through stdin,
for which eventually a single answer will be given.

Information associated with queries are:

- the request attribute/value pairs received through stdin

- the set of servers to be tried for the query, and for each server
  the request slot that was last used for transmitting to the server

- the pairs that need to be added when creating the request

- the number of retransmissions to go for this query

- list of attributes that need to be added by a fixup routine


Queries are kept on a doubly linked list. New queries are added to the
tail end. The following routines work primarily on queries.

stdin_handle_read():

When a full set of A/V pairs is received through stdin, this routine
first calls add_new_query() to build a query from the list of pairs, and
then calls send_qry_req() to send a request for this query.

add_new_query():

Allocates a new query structure, fills it with the request information,
calls fixup_query() to add whatever missing attributes need to be added, and
adds the new query at the tail of the doubly linked list of queries.

fixup_query():

First finds out which attributes are missing; at first 'all' are, and as the
list is travelled, specific attributes or sets thereof are crossed from the
missing bitmap int.

Also establishes whether we're authenticating or accounting based on
defaults and possibly a supplied RAD-Code pair. Can add RAD-Code,
NAS-IP-Address and CHAP-Challenge on its own. 

RAD-Authenticator is likely to be different across requests so is not fixed
up for queries. However, if encountered, it is cleared from the missing
bitmap that is stored in the query.

query_reply():

Replies to stdout with received answer for query and frees query using
query_del().

query_del():

Removes query from linked list and frees memory occupied by query. Also
marks request slots that were occupied by the last requests we sent for this
query (to one or more servers) as free.


2. Requests
-----------
 
A request is information about a RADIUS request we sent to a certain server for
a certain query. It contains the ID, authenticator, timestamp and a cached
packet.

We need to find requests in various ways:

- requests are answered on a certain port and using a certain ID, so we need
  to keep an array of requests indexed on ID

- requests time out a certain time after they are sent, so we need to keep
  a ring of requests in time order

- somewhat different, but because we want a fixed maximum number of outstanding
  requests, we need to be able to find a free slot in the structure that keeps
  them.

As said, we need to be able to match an incoming RADIUS response to a query.
So, for each unique source port / RADIUS ID combination, we need a specific
request. When we're sending a RADIUS request, we build the ID from the request
slot, padding on the left with low bits from the PID if the ring has less than
256 slots. (If the ring has more slots, the bits that do not fit in the ID will
have to be encoded in the source port).

When a request times out, we can either reuse the request or create a new one
for a query, depending on various factors.

New RADIUS IDs are only needed if any of the attributes are different. That
means that requests to the same server can usually be reused, unless we're
accounting, in which case the attribute Acct-Delay-Time will change during the
timeout interval.

-

The most important thing to consider when thinking about the lifecycle of
requests is what happens during retransmission.

When a request times out and we send the same or a different reqest to the
same or a different server (orthogonal), we may get a late answer for the old
request, which may answer the query.

So, even after a timeout, we need to keep the old request if it was in any way
different from the new one. The timer ring will generally be smaller than the
request ring.

That means that a different request means a new ID, because that's the only way
we can identify a request when we receive an answer for it.

So, what happens when we

* retransmit the same packet to the same server? (always ok unless acct)

  - new position in timer ring, same ID -> same slot in request ring

* retransmit the same packet to a different server? (not if pap and diff. secr)

  - new position in timer ring, same ID -> same slot in request ring;
    query->servers[query->cursrv]->reqslot points to same slot as
    query->servers[query->oldsrv]->reqslot.

* retransmit a new packet to the same server? (needed if acct)

  - new position in timer ring, new ID -> new slot in request ring

  Note that in this case, the old slot must be freed later somehow when we're
  done with the query. [1]

* retransmit a new packet to a new server (needed if acct or pap and diff secr)

  - new position in timer ring, new ID -> new slot in request ring

* retransmit a new packet with a same ID because it was forced upon us?

  - old answers will cause authenticator mismatches, unless it was also
    forced.


[1] Say that we have 2 servers and have done 4 transmits, all different because
we are accounting. We may get an answer for any of the 4, so we must keep 4
request slots occupied until we're done with the query.

The best way to solve this is to put a slot id in each request structure, that
points to the previous retransmission slot. If a query ends, the most recent
requests can be found on a per-server basis from the query, and the older
request slots based on the chain of slot ids in the request.

This is only needed (wanted) if we already have a request for the server we're
transmitting to.

