How FreeRADIUS handles Access-Request packets

FreeRADIUS is the most popular and the most widely deployed RADIUS server in the world. Remote Authentication Dial-In User Service (RADIUS) is a client-server networking protocol that runs in the application layer. The RADIUS protocol uses a RADIUS Server and RADIUS Clients.
A RADIUS Client (or Network Access Server) is a networking device (like a VPN concentrator, router, switch) that is used to authenticate users.
A RADIUS server utilizes a central database to authenticate remote users. RADIUS servers are well known for their AAA capabilities — Authentication, Authorization, and Accounting. The main advantage of the centralized AAA capabilities of a RADIUS server are heightened security and better efficiency.

Overview of how RADIUS servers work

  1. First, the user initiates authentication to the network access server (NAS).
  2. The network access server then requests either a username and password or a challenge (CHAP).
  3. The user replies.
  4. Upon receiving the user’s reply, the RADIUS client sends the username and the uniquely encrypted password to the RADIUS server.
  5. The RADIUS server accepts or rejects the user.

I am using FreeRADIUS v3. Here I have tried to explain how Access-Request packets are handled by RADIUS Server.


  • First, define admin as a FreeRADIUS test user. Add the following lines at the top of the users file. Make sure the second line is indented by a single tab character.
    "admin" Cleartext-Password := "password"
          Reply-Message = "Hello, %{User-Name}"
  • Now start freeradius service in debug mode.
    radiusd -X
  • Authenticate user admin using the following command.
    radtest admin password 100 testing123
  • Now, The debug output of FreeRADIUS will show how the Access-Request packet arrives and how the FreeRADIUS server responds to this request.

Access-Request packet

When the packet arrives at the FreeRADIUS server it is indicated by the following part:

(0) Received Access-Request Id 199 from to length 75
(0)   User-Name = "admin"
(0)   User-Password = "password"
(0)   NAS-IP-Address =
(0)   NAS-Port = 100
(0)   Message-Authenticator = 0xae52e27288e459e134d7b86273fb6

We see that the incoming request contains five AVPs.
Although the AVP User-Password is shown here in clear text, it was not transmitted to the server in clear text. FreeRADIUS uses the shared secret to encrypt and decrypt the value of the User-Password AVP

AVPs are the workhorse of the RADIUS protocol. AVPs can be categorized as either check or reply attributes. Check attributes are sent from the client to the server. Reply attributes are sent from the server to the client.


After the request is received, the authorize section takes care of the request:

(0) # Executing section authorize from file /etc/raddb/sites-enabled/default
(0)   authorize {
(0)     policy filter_username {
(0)       if (&User-Name) {
(0)       if (&User-Name)  -> TRUE
(0)       if (&User-Name)  {
(0)         if (&User-Name =~ / /) {
(0)         if (&User-Name =~ / /)  -> FALSE
(0)         if (&User-Name =~ /@[^@]*@/ ) {
(0)         if (&User-Name =~ /@[^@]*@/ )  -> FALSE
(0)         if (&User-Name =~ /\.\./ ) {
(0)         if (&User-Name =~ /\.\./ )  -> FALSE
(0)         if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/))  {
(0)         if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/))   -> FALSE
(0)         if (&User-Name =~ /\.$/)  {
(0)         if (&User-Name =~ /\.$/)   -> FALSE
(0)         if (&User-Name =~ /@\./)  {
(0)         if (&User-Name =~ /@\./)   -> FALSE
(0)       } # if (&User-Name)  = notfound
(0)     } # policy filter_username = notfound
(0)     [preprocess] = ok
(0)     [chap] = noop
(0)     [mschap] = noop
(0)     [digest] = noop
(0) suffix: Checking for suffix after "@"
(0) suffix: No '@' in User-Name = "admin", looking up realm NULL
(0) suffix: No such realm "NULL"
(0)     [suffix] = noop
(0) eap: No EAP-Message, not doing EAP
(0)     [eap] = noop
(0) files: users: Matched entry admin at line 60
(0) files: EXPAND Hello, %{User-Name}
(0) files:    --> Hello, admin
(0)     [files] = ok
(0)     [ldap] = notfound
(0)     [expiration] = noop
(0)     [logintime] = noop
(0)     [pap] = updated
(0)   } # authorize = updated
(0) Found Auth-Type = PAP

Module return codes
If you look at the debug output from FreeRADIUS, you will see how each module returns a code. Here is a list of available return codes and what they mean:

Module return code Description
notfound Information was not found
noop The module did nothing
ok The module succeeded
updated The module updated the request. E.g it set the Auth-Type internal AVP
fail The module failed
reject The module rejected the request
userlock The user was locked out
invalid The configuration was invalid
handled The module has handled the request itself

Back to authorization section
The authorize section is defined inside a virtual server. Let’s first look at some points about virtual servers in FreeRADIUS:

  • Virtual servers are defined under the sites-available directory, which resides under the configuration directory of FreeRADIUS.
  • Each virtual server is represented by a single text file.
  • Virtual servers are activated by creating a soft link from the file in the sites-available directory to a file in the sites-enabled directory with the same name.
  • The virtual server named default handles all the typical requests.
  • Virtual servers are basically like having several RADIUS servers. One virtual server can even forward a request to another virtual server. This makes a FreeRADIUS installation extremely versatile and powerful.
  • Each virtual server, including default, has various sections. A virtual server can contain the following sections nested inside the virtual server definition: listen, client, authorize, authenticate, post-auth, pre-proxy, post-proxy, preacct, accounting, and session.
  • The Access-Request is first handled by the authorize section.
Authorize set Auth-Type

When the request is handled by the authorize section, various FreeRADIUS modules look at the AVPs contained in the Access-Request. These modules try to determine the mechanism and module to be used for authenticating the user. In our example authorize sets the Auth-Type to PAP.
If the Access-Request contained MS-CHAP attributes instead of the User-Password for instance, the mschap module would have detected this and set Auth-Type = MS-CHAP.

Authorization in action

The authorize section may decide to reject a request outright based on a decision on the presence or the value of a specified AVP. This will result in an Access-Reject packet returned to the client. There would then be no need for authentication.


After the value of Auth-Type is set, the request is passed to the authenticate section:

(0) # Executing group from file /etc/raddb/sites-enabled/default
(0)   Auth-Type PAP {
(0) pap: Login attempt with password
(0) pap: Comparing with "known good" Cleartext-Password
(0) pap: User authenticated successfully
(0)     [pap] = ok
(0)   } # Auth-Type PAP = ok

Here we see that the pap subsection in the authenticate section is taking care of this request and returns ok.


The post-auth section is done after authentication. You may use it to execute something:

(0) # Executing section post-auth from file /etc/raddb/sites-enabled/default
(0)   post-auth {
(0)   } # post-auth = noop


The result is now sent back to the client:

(0) Login OK: [admin] (from client localhost port 100)
(0) Sent Access-Accept Id 1 from to length 0
(0)   Reply-Message = "Hello, admin"
(0) Finished request
Waking up in 4.9 seconds.


Remember the following points when looking at the debug output: ‹

  1. Main sections like authorize, authenticate and post-auth start with a # Executing. ‹
  2. These sections also indicate in which virtual server they reside. ‹
  3. The authorize section sets the value of Auth-Type. This in turns determines which module inside the authenticate section will be used. ‹
  4. The debug output of FreeRADIUS modules can be divided in two types. They are debug messages and return values. ‹
  5. Debug messages are preceded by the module name, for example [files] users: Matched entry alice at line 137.
  6. Return values are preceded by ++[module_name] for example ++[files] returns ok.