SQL-Based IP Pool module.
Introduction
The sqlippool module allocates IP addresses from one or more pools
of addresses. It tracks addrwss to that it does not issue the same
leased IP address to multiple devices.
Using FreeRADIUS for IP address allocation has some advantages. Centralized pool management means that the pools remains available when one or more of the NASs becomes unavailable. When pools are managed on a NAS, the pools are down when the NAS is down.
Networks may have organisation-specific policies for the way that IP addresses are assigned. These policies are difficult to implement on a NAS, but can be implemented with FreeRADIUS. For example, FreeRADIUS can assign pools of IP addresses to specific NASs or user groups. FreeRADIUS also control reallocation, in order to move users from one pool to another, without disconnecting them. This move allows address ranges to be retired or repurposed without forcibly disconnecting the users.
As with most FreeRADIUS modules, the sqlippool module ships with a
default configuration that "just works". However, the module can also
be configured to use sophisticated IP allocation policies, via custom
SQL queries.
All of the SQL queries used by the module are configurable. The module simply provides for a few "hooks", in order to allocate an IP, clear an IP, expire IPs, etc. The result is that the IP pool allocations works with all possible SQL databases, without requiring code changes. In addition, anyone familiar with SQL can customize the allocation policies. Minimal knowledge of FreeRADIUS is required.
Operation
The SQL IP Pools module only perform an action when FreeRADIUS
receives a packet. The entire state of the pools is held in the
radippool table. The benefit of this approach is that the RADIUS
server can be restarted, replaced, or debugged without losing track of
any IP addresses.
One downside is that there is no method to perform actions at a particular time. We suggust using "cron" for periodic cleanups, analysis, etc.
Another downside is that FreeRADIUS does not "know" the state of a
particular IP address. The sqlippool module simply runs SQL
queries, which either return an IP address, or return "failed to
allocate IP". Since the entire state of the pool is in the SQL
database, we recommend using standard SQL queries to find the state of
an individual IP address, MAC address, etc.
Access-Request: A device connects to the NAS with correct credentials
-
FreeRADIUS receives an
Access-Requestfrom the NAS. After some processing (modules.unlang, etc.), theIP-Pool.Nameattribute is added to thecontrollist. This attribute indicates that the IP address should be allocated from the named pool. This attribute can be added from any module, database,unlang, etc. -
When the
sqlippoolmodule is eventually run, it looks for theIP-Pool.Nameattribute, and runs the various SQL queries in order to perform IP address allocation. If the module is configured to place address into theFramed-IP-Addressattribute (as it is by default), and that attribute already exists, the module does nothing, and returnsnoop. -
The module then runs the
alloc_existingquery, which looks for the IP address last assigned to the device from the pool indicated byIP-Pool.Name. The device is identified using thepool_keyconfiguration item (typicallyNAS-Port) and a NAS identifier (theNAS-IP-Address). -
If no address was found using
alloc_existing, the module then runs thealloc_findquery, which chooses a free IP address from the pool indicated byIP-Pool.Name. Ifalloc_finddoes not return an IP address, thepool_checkquery is run in order to determine why the allocation failed. For example, either the requested pool is empty (i.e. no free addresses), or it is non-existent. This information is returned in thesqlippoolmodule return code, asnotfound, or one of the other return codes. -
If an IP address has been found, the module runs the
alloc_updatequery. This query assigns the IP address to the device by updating theradippoolrow for the IP address with information about the lease. The default information includesexpiry timebased on the configuredlease_duration, a unique identifier for the device as specified by thepool_keyconfiguration item (typicallyNAS-Port) and a NAS identifier (theNAS-IP-Address). Additionally, the default schema and queries record the user identifier (User-Name) and device identifier (Calling-Station-Id). This information can be used to assign the same IP to the same user or device, on subsequent allocation requests. With some database backends (PostgreSQL and MS SQLServer) the update can be incorporated into thealloc_existingandalloc_findqueries, reducing round trips to the database and improving performance. -
The module returns
updatedto indicate that it was successfull in allocating an IP address. -
After some additional processing (modules,
unlang, etc.), FreeRADIUS sends an Access-Accept containing aFramed-IP-Addressattribute holding the allocated IP address to the NAS.
Accounting Start: A device and a NAS successfully establish a session
-
The NAS sends an
Accounting Startrequest to FreeRADIUS. After some processing (modules.unlang, etc.), thesqlippoolmodule is run. -
The
sqlippolmodule runs theupdate_updatequery. This query uses the configuredpool_keyand NAS identifier in order to identify which entry in theradippooltable to update. Theexpiry_timeis updated based on the configuredlease_duration. This update extends the initial lease to the configuredlease_durationin case the event that the Accounting Start request was delayed. -
The module returns
updatedto indicate that it was successfull in updating the state of the IP address. -
After some additional processing (modules,
unlang, etc.), FreeRADIUS sends an Accounting-Response to the NAS.
Accounting Interim-Update: A NAS sends an Interim-Update notification for a device’s session
-
The NAS sends an
Accounting Interim-Updaterequest to FreeRADIUS. After some processing (modules.unlang, etc.), thesqlippoolmodule is run. -
The
sqlippolmodule runs theupdate_updatequery. This query uses the configuredpool_keyand NAS identifier in order to identify which entry in theradippooltable to update. Theexpiry_timeis updated based on the configuredlease_duration. -
The module returns
updatedto indicate that it was successfull in updating the state of the IP address. -
After some additional processing (modules,
unlang, etc.), FreeRADIUS sends an Accounting-Response to the NAS.
Accounting Stop: A device disconnects or its session times out
-
The NAS sends an
Accounting Stoprequest to FreeRADIUS. After some processing (modules.unlang, etc.), thesqlippoolmodule is run. -
The
sqlippolmodule runs therelease_clearquery. This query uses the configuredpool_keyand NAS identifier in order to identify which entry in theradippooltable to update. The update "clears" the IP address, and marks it as free for later allocation. Note that by default, this "clear" does not remove information about which user or device was associated with that address. The result is that on subsequent allocations, it is possible to re-allocate the same IP address to the same user or device. -
The module returns
updatedto indicate that it was successfull in updating the state of the IP address. -
After some additional processing (modules,
unlang, etc.), FreeRADIUS sends an Accounting-Response to the NAS.
Accounting On/Off: A NAS indicates that all of its sessions have been cleared
-
The NAS sends an
Accounting OnorAccounting Offrequest to FreeRADIUS. After some processing (modules.unlang, etc.), thesqlippoolmodule is run. -
The
sqlippolmodule runs thebulk_release_clearquery. This query uses theNAS-IP-Addressin order to identify all leases in theradippooltable which belong to the NAS in question. The leases are cleared, and the IP addresses are immediately released for further allocation. This proces effectively returns all IP address occupied by the dropped sessions back into the pool. -
The module returns
updatedto indicate that it was successfull in updating the state of the IP addresses. -
After some additional processing (modules,
unlang, etc.), FreeRADIUS sends an Accounting-Response to the NAS.
Step by step setup instructions
As with any FreeRADIUS configuration you are strongly recommended to
start with the default configuration for sqlippools, as it is known
to work.
Change the configuration, one thing at a time, testing after each
step, until you have successfully implemented your intended policy.
We also recommend using a revision control system such as git. When
configuration changes are tracked, it becomes trivial to fix issues by
checking out a "known working" version of the configuration. The
alternative is to try to track changes manually, or to manually
recreate what is believed to be a "working" configuration.
This guide provides essential step by step instructions for
configuring the sqlippool module. These instructions begin with a
"default" configuration. The actions described here are
straightforward, and will arrive at a working configuration.
However, the reasons for setting particular module configuration items are complex, and require an understanding of the NAS and wider access network. We therefore discuss these reasons in a separate section, so that the guide is simpler to follow.
| If at any time you break your system then follow the guide to debugging FreeRADIUS. If anything goes wrong, it should be trivial to revert any recent changes. Then, consider what went wrong by examining the debug output. If the issues are still clear, then ask a question on the freeradius-users mailing list. Please also read the list help instructions, for what information we need in order to help you. This information is typically an explantion of what you are trying to achieve, what exactly isn’t working and provide the full debugging output for a relevant test run. |
1. Enable the module and call it during request processing
Firstly enable the module by creating its symlink.
cd /etc/raddb/mods-enabled
ln -s ../mods-available/sqlippool
Ensure that the module is invoked during authentication and accounting request
processing by uncommenting any lines containing sqlippool in the send
Access-Accept and Accounting-Response sections of the default site.
send Access-Accept {
...
sqlippool
...
}
...
send Accounting-Response {
...
sqlippool
...
}
In the event that sqlippool is unable to allocate an IP address you may want
to record the reason for the failure in a Reply-Message attribute of an
Access-Reject response.
Reply-Messagesend Access-Accept {
...
group {
sqlippool {
ok = return
noop = return
}
# On failure for any reason, reject
&reply += {
Reply-Message = Module-Failure-Message
}
reject
}
...
}
You must now configure the module configuration items in
[raddb]/mods-enabled/sqlippool as described below.
2. Configure a database server
The performance requirements for a system with a many IP allocations is likely to determine your choice of database software and architecture.
Read and understand the considerations for choosing a database server.
Consider how these relate to your solution, then select an appropriate backend database for the SQL IP Pools. If a database server is already deployed in your environment, then any solution fit within the existing limitations. High load IP address allocation can severely stress an SQL database. Depending on many factos, performance can very from dozens of IP allocations per second to thousands of allocations per second.
The sqlippools module requires a connection to the database to be
provided, using an instance of the sql module. Configure this as
described in the SQL module guide.
Set the sql_module_instance configuration item to the name of the sql
instance that you have configured and set the dialect configuration item to
the same as that of the sql module. This determines which version of the
queries.conf file is used.
sqlippool {
...
sql_module_instance = "sql"
dialect = "mysql"
...
}
If the SQL IP Pools database is a multi-master cluster (such as
Galera) then you will have multiple instances of the sql module, one per
node. For redundancy, you should configure a sqlipool instance corresponding
to each sql instance for each master node in your cluster. However, you must
not use a load-balancing policy such as load-balance or
redundant-load-balance to spread SQL IP Pool load between cluster nodes for
the reasons explained here. Do not ignore this advice
unless you are certain that you have understood the implications. Instead you
should use the basic redundant policy which will direct all queries to the
first available instance and then be careful to ensure that you do not run out
of connections or that the module might routinely fail in a way that directs IP
allocation queries to multiple database instances simultaneously during normal
operation.
|
Load the SQL IP Pool schema into the database. Methods vary between databases, but a typical example would be:
mysql radius < /etc/raddb/mods-config/sql/ippool/mysql/schema.sql
The above command assumes that passwordless login has been configured via
the user’s ~/.my.cnf file, or otherwise.
|
For performance reasons, if you are using a database that supports SELECT …
FOR UPDATE SKIP LOCKED then you should edit the
[raddb]/mods-config/sql/ippools/<dialect>/queries.conf file corresponding to
your database dialect to select the SKIP LOCKED variant of the
alloc_find query. This will allow the database to remain responsive under
concurrent load.
3. Configure a unique device identifier
Read and understand the considerations for choosing a device identifier.
Set the pool_key configuration item to the chosen unique device identifier attribute or
combination of attributes.
sqlippool {
...
pool_key = "%{NAS-Port-Id}:%{Calling-Station-Id}"
...
}
4. Configure the lease duration
Read and understand the considerations for choosing a lease duration.
Set the lease_duration configuration item to the chosen lease duration in seconds.
sqlippool {
...
lease_duration = 3600
...
}
Remember to enable Interim-Updates on the NAS and configure the update
interval with respect to the chosen lease_duration, typically to less than
half the lease duration.
|
In some access networks there may be circumstances where the device and NAS do
not always finish establishing a connection after FreeRADIUS has issued an IP
address and sent it in an Access-Accept, i.e. the session never really
starts and FreeRADIUS does not receive an Accounting Start request. This may
be due to a network fault, the device or NAS rejecting the data sent in the
Access-Accept response, or some additional policy implemented elsewhere. In
the default configuration, when a successfully authenticated session does not
become fully established the IP address will have been allocated for the full
lease_duration despite the device not having actually connected. It will remain
like this since there will be no Accounting Stop request generated for a
session that does not become fully established.
This waste of an address may be undesirable if the capacity of the pool is
a concern, especially if sticky IPs are not enabled and a device that is
repeatedly failing to establish a connection is able to continue to consume IP
addresses. With some consideration, this initial lease could be amended to a
short, fixed interval rather than the full lease duration. Replace
lease_duration with a fixed value in seconds in the alloc_update query
in queries.conf or in the stored procedure in procedure.sql, whichever is
in use. This fixed interval should be greater than the maximum time it could
take for an Accounting Start to be received for successful connections.
|
FreeRADIUS errs on the side of caution and initially allocates IP addresses during authentication for the full lease duration for two reasons: (1) We do not actually know how long it takes for a session to become established and for the Accounting Start request to actually be sent by the NAS. (2) We do not know that Accounting Start requests will always be reliably generated by the NAS even when a device has established a connection. These should be considered if you decide to amend the default policy.
5. Configure the IP address attribute
Set the attribute_name configuration item to whatever RADIUS attribute is
required by the NAS, for example:
sqlippool {
...
attribute_name = radius.Framed-IP-Address
...
}
Consult your NAS documentation to determine what attribute is used to
indicate the assigned IP address and any additional attributes that are
required for the RADIUS reply, e.g. Framed-IP-Netmask.
|
If your NAS is allocating IPv6 prefixes to devices on the basis of a
Framed-IPv6-Prefix RADIUS attribute then you can put the IPv6 prefixes into the
radippool table and set attribute_name = Framed-IPv6-Prefix.
|
6. Populate the pool
Populate the pool either manually using a text editor or database tool, or via a script.
See Generating IPs for the pools for instructions on how to create lists of IPs for a pool. And then Inserting IPs into SQL
radippool tablefor i in `seq 10 250`; do
echo "INSERT INTO radippool ( \
pool_name, \
framedipaddress, \
pool_key, \
nasipaddress, \
calledstationid, \
callingstationid, \
expiry_time \
) VALUES ( \
'internet', \
'192.0.2.$i', \
'0', \
'', \
'', \
'', \
CURRENT_TIMESTAMP \
);"
done | mysql radius
| If at any time you should accidentally remove an IP address from the pool that is in use then to avoid issuing duplicate IP addresses you must either wait for the existing session to expire (or terminate it manually. e.g. CoA/Disconnect) or reintroduce the IP address with a future expiry date beyond which any existing session will have closed (or at least beyond the accounting interval). |
7. Enable a pool user
For a request to acquire an address from the pool you must set a IP-Pool.Name
check attribute, either directly in the virtual server configuration or via a
data source such as sql (for example using the radcheck table).
radcheck tableecho "INSERT INTO radcheck (username,attribute,op,value) \
VALUES ('bob','IP-Pool.Name',':=','internet');" \
| mysql radius
send Access-Accept {
...
&control.IP-Pool.Name := 'internet'
sqlippool
...
}
Recall that if sqlippool is invoked without a IP-Pool.Name check
attribute then no action is taken. Also recall that if sqlippool is invoked
whilst the attribute configured in the attribute_name configuration item
(e.g. Framed-IP-Address) already exists then no action is taken.
|
8. Test the basic setup
Start by creating some text files holding the test request data based on the actual form of the contents of packets originating from the NAS.
User-Name = bob
User-Password = testing123
Calling-Station-Id = 00:53:00:11:22:33
NAS-Port = 12345
NAS-IP-Address = 192.0.2.5
Acct-Session-Id = 1000
Acct-Status-Id = Start
User-Name = bob
User-Password = testing123
Calling-Station-Id = 00:53:00:11:22:33
NAS-Port = 12345
NAS-IP-Address = 192.0.2.5
Framed-IP-Address = ???.???.???.???
Acct-Session-Id = 1000
Acct-Status-Id = Interim-Update
User-Name = bob
User-Password = testing123
Calling-Station-Id = 00:53:00:11:22:33
NAS-Port = 12345
NAS-IP-Address = 192.0.2.5
Framed-IP-Address = ???.???.???.???
Acct-Session-Id = 1000
Acct-Status-Id = Interim-Update
User-Name = bob
User-Password = testing123
Calling-Station-Id = 00:53:00:11:22:33
NAS-Port = 12345
NAS-IP-Address = 192.0.2.5
Framed-IP-Address = ???.???.???.???
Acct-Status-Id = On
NAS-IP-Address = 192.0.2.5
Acct-Status-Id = Off
NAS-IP-Address = 192.0.2.5
Now run through a series of tests examining the effect on the radippool
tables at each stage to ensure that it matches the expected behaviour as
described in the Operation section.
Testing initial authentication
Send the Access Request to FreeRADIUS.
cat access-request.rad | radclient -x 127.0.0.1 auth testing123
Sent Access-Request Id 1 from 0.0.0.0:2000 to 127.0.0.1:1812 length 81
Password.Cleartext = "testing123"
User-Name = "bob"
User-Password = "testing123"
Calling-Station-Id = "00:53:00:11:22:33"
NAS-Port = 12345
NAS-IP-Address = 192.0.2.5
Received Access-Accept Id 1 from 127.0.0.1:1812 to 0.0.0.0:2000 length 31
User-Name = "bob"
Framed-IP-Address = 192.0.2.10
Note that an IP address has been issued in the Framed-IP-Address of the
reply.
| If you do no receive an IP address then start FreeRADIUS in debugging mode to determine where the process if failing as described in the guide to debugging FreeRADIUS. Do not proceed until you have resolved the IP allocation issue. |
Check the status of the radippool table.
echo "SELECT * FROM radippool WHERE username <> ''" | mysql radius
+----+-----------+-----------------+--------------+----------+-------------------+---------------------+
| id | pool_name | framedipaddress | nasipaddress | username | callingstationid | expiry_time |
+----+-----------+-----------------+--------------+----------+-------------------+---------------------+
| 1 | internet | 192.0.2.10 | 192.0.2.5 | bob | 00:53:00:11:22:33 | 2020-01-01 10:10:10 |
+----+-----------+-----------------+--------------+----------+-------------------+---------------------+
1 rows in set (0.0030 sec)
For the entry matching the given IP address ensure that the IP allocation has been recorded correctly. Check that:
-
The
pool_keymatches the expected value of the unique identifier that you chose. Double check that values of this form will be unique across all of your devices. -
The
expiry_timeislease_durationseconds ahead of the time of the request (or some fixed value that you chose for the initial lease if you updated the default policy.) -
The
User-NameandCalling-Station-Idattributes are populated correctly where this information is given in the request. -
The
NAS-IP-Addresshas been provided. If not then you may need to reconfigure your NAS to provide this or instantiate this attribute fromNet.Src.IPusing an unlang policy in FreeRADIUS. Otherwise when the NAS reboots you will not be able to match the affected IP addresses to the device.
Update your sample text files containing the accounting requests to include the allocated IP Address before proceeding with accounting packet testing.
Testing Accounting Start
cat accounting-start.rad | radclient -x 127.0.0.1 acct testing123
Check the status of the radippool table.
For the entry matching the given IP address ensure that initial lease
extension is occurring by verifying that the expiry_time is in the future by
lease_duration seconds from the time of the request.
Testing Accounting Interim-Update
cat accounting-alive.rad | radclient -x 127.0.0.1 acct testing123
Check the status of the radippool table.
For the entry matching the given IP address ensure that IP address renewal is
occurring by verifying that the expiry_time is in the future by
lease_duration seconds from the time of the request.
Testing Accounting Stop
cat accounting-stop.rad | radclient -x 127.0.0.1 acct testing123
Check the status of the radippool table.
For the entry matching the given IP address ensure that IP address release is
occurring by verifying that the expiry_time is set prior to the current time
or is null.
Additional tests
-
Repeat this authentication test with the same user to ensure that the same IP address is allocated for a re-authentication of an ongoing session.
-
Repeat the authentication test with multiple users to ensure that each user is assigned a unique IP address.
-
Test that
Accounting On/Offpackets clear all sessions owned by the NAS. -
If you have already enabled a sticky IP policy then ensure that user and device data is not removed when an
Accounting Stop(andAccounting On/Off) request is received. Ensure that users receive their previous IP address when they authenticate using a device whose recent session is disconnected.
Recommended additional configuration
Once the initial configuration is working there are some additional recommended configuration steps to consider.
The basic configuration arrived at in the previous section will be functional but it is likely to exhibit poor performance under concurrent load. It also implements a simple, dynamic IP allocation policy that you may want to change.
Enable the IP allocation stored procedure
The stored procedure is provided as an efficient means to both find and allocate an IP address with a single SQL call so that no locks are held across rounds trips between FreeRADIUS and the database. Depending on the database software and configuration the stored procedure may increase the IP allocation performance by as much as 100x and ensure that the system doesn’t fail due to excessive locking under high load.
Load the IP allocation stored procedure in the procedure.sql file
corresponding to the database dialect.
Methods vary between databases, but a typical example would be:
mysql radius < /etc/raddb/mods-config/sql/ippool/mysql/procedure.sql
The above command assumes that passwordless login has been configured via
the user’s ~/.my.cnf file, or otherwise.
|
Read the comments in the procedure.sql file which explain how to use
the stored procedure, then amend the alloc_find, and
allocate_update queries (as well as the corresponding start/end
transaction query-parts) in the dialect’s queries.conf file,
exactly as described.
[raddb]/mods-config/sql/ipool/<dialect>/queries.conf
...
allocate_begin = ""
alloc_find = "\
CALL fr_allocate_previous_or_new_framedipaddress( \
'%{control.${pool_name}}', \
'%{User-Name}', \
'%{Calling-Station-Id}', \
'%{NAS-IP-Address}', \
'${pool_key}', \
${lease_duration} \
)"
allocate_update = ""
allocate_commit = ""
...
Now that the IP allocation stored procedure has been enabled you should re-test your configuration.
Customise the IP allocation policy
The IP allocation policy is mainly configured by modifying the alloc_find
query, however it is likely that the other queries will also need to be
modified to get the results you want.
By default each user is likely to get a different IP address each time they authenticate unless they re-authenticate before their existing session has terminated (as is the case with many EAP-based implementations during "fast re-authentication").
Often it is required that upon reconnection a device obtains its previous IP address, sometimes referred to as a "sticky IPs" policy.
If you are using the base queries (rather than the recommended stored
procedure) then the queries.conf file for your database dialect contains
several example alloc_find queries for choosing either a dynamic or sticky
IP policy.
If you are using the recommended stored procedure then the procedure.sql file
which contains the definition of the stored procedure for your database dialect
has comments that explain how to amend the procedure to choose either a dynamic
or sticky IP policy.
With a sticky IP policy it is necessary to amend the default actions
of the release_clear and bulk_release_clear queries. By default,
each of these queries clobbers the User-Name and
Calling-Station-Id attributes when sessions expire therefore erasing
the affinity information.
| A performance benefit of sticky IP addresses derived from the fact that reallocation of an existing IP address is generally much quicker than allocating a new IP address since it generally involves an indexed lookup of a single IP address based on the device’s request attributes rather than a walk of the table’s indexes. |
The default radippool table contains attributes for User-Name and
Calling-Station that can be used to set the affinity for a sticky IP policy.
With care, these can be modified and the queries adjusted accordingly so that
any RADIUS attribute that is available during authentication can be used.
|
...
alloc_begin = ""
alloc_find = "\
CALL fr_allocate_previous_or_new_framedipaddress( \
'%{control.${pool_name}}', \
'%{User-Name}', \
'%{Calling-Station-Id}', \
'%{NAS-IP-Address}', \
'${pool_key}', \
${lease_duration} \
)"
alloc_update = ""
alloc_commit = ""
...
release_clear = "\
UPDATE ${ippool_table} \
SET \
nasipaddress = '', \
pool_key = 0, \
expiry_time = NOW() \
WHERE nasipaddress = '%{&NAS-IP-Address || &NAS-IPv6-Address}' \
AND pool_key = '${pool_key}' \
AND username = '%{User-Name}' \
AND callingstationid = '%{Calling-Station-Id}' \
AND framedipaddress = '%{${attribute_name}}'"
bulk_release_clear = "\
UPDATE ${ippool_table} \
SET \
nasipaddress = '', \
pool_key = 0, \
expiry_time = NOW() \
WHERE nasipaddress = '%{&NAS-IP-Address || &NAS-IPv6-Address}'"
...
...
-- Reissue an user's previous IP address, provided that the lease
-- is available (i.e. enable sticky IPs)
--
SELECT framedipaddress INTO r_address
FROM radippool
WHERE pool_name = v_pool_name
AND username = v_username
AND callingstationid = v_callingstationid
LIMIT 1
FOR UPDATE SKIP LOCKED;
-- If we didn't reallocate a previous address then pick the least
-- recently used address from the pool which maximises the
-- likelihood of re-assigning the other addresses to their recent
-- user
--
IF r_address IS NULL THEN
SELECT framedipaddress INTO r_address
FROM radippool
WHERE pool_name = v_pool_name
AND ( expiry_time < NOW() OR expiry_time IS NULL )
ORDER BY
expiry_time
LIMIT 1
FOR UPDATE SKIP LOCKED;
END IF;
...
Now that you have amended your IP allocation policy you should re-test your configuration carefully to validate each of the amended queries and exercise all of the features of your policy.
If your testing passes then FreeRADIUS should now be ready to allocate IP addresses.
Detailed considerations
Choice of database server
The performance of the sqlippool module is much more sensitive to the choice
of backend database than is typical for other FreeRADIUS database workloads.
The sqlippool module allocates IP addresses by using a relational database to
implement the "accept" part of a "worker queue" pattern, where multiple workers
attempt to select a single, unique work item from an unordered queue. In our
case the queue is the IP pool and each worker is an authentication request
attempting to reserve a unique IP address.
The overall performance largely depends on how directly and optimally the database server software supports this pattern.
In particular, the SQL standard introduces two pragmas that facilitate an optimised implementation of a worker queue:
SELECT FOR UPDATE …-
Selects rows whilst exclusively locking them to prevent simultaneous locking reads and writes of those same rows. When connections are simultaneously selecting an IP address from a pool this pragma is what provides the guarantee of uniqueness of the IP selected for each authentication request.
… SKIP LOCKED-
This is a modifier for
SELECT FOR UPDATEthat optimises the select by allowing each reader to disregard any locked rows in order to avoid blocking. This pragma avoids the "thundering herd" problem that can cripple performance during highly concurrent IP allocation: Several authentication requests wait a lock to be release on a single IP address that is in the process of being allocated to a device. When the lock is release all but one of these previously blocked connections block waiting for the next free IP address to be allocated. And so on until they have all been serviced or their query times out… In the absence of this pragma concurrent authentication requests are unnecessarily serialised whilst their IP addresses are allocated resulting in poor performance.
Here are some comments regarding the main database engines in relation to this issue:
- PostgreSQL
-
Since version 9.5 provides a direct implementation of the
SELECT … FOR UPDATE SKIP LOCKEDstatement which means that it is a very efficient backend forsqlippool. - MySQL
-
Ensure that you use a storage engine what supports transactions such as InnoDB. Do not use MyISAM. Since version 8 the InnoDB storage engine provides a direct implementation of the
SELECT … FOR UPDATE SKIP LOCKEDstatement which means that it is a very efficient backend forsqlippool. Previous versions do not implement theSKIP LOCKEDpragma and will therefore have poor performance under concurrent load, but see the tip concerning the alternative stored procedure below. - MariaDB
-
Ensure that you use a storage engine what supports transactions such as InnoDB. Do not use MyISAM or Aria. All versions of MariaDB lack
SKIP LOCKEDand therefore will have poor performance under concurrent load, but see the tip concerning the alternative stored procedure below. Given the choice between MariaDB and a recent version of MySQL you should use MySQL for SQL IP Pools. - MS SQL Server
-
Does not provide a
SELECT … FOR UPDATEimplementation nor a direct means to provide a row-level, exclusive lock that ensures uniqueness of the selected row. However FreeRADIUS is able to use an artificialUPDATEof a selected row along with non-standard "hints" to obtain the requiredSELECT … FOR UPDATE SKIP LOCKEDsemantics, but we pay a performance penalty by doing so. The overall performance is moderate but it does not collapse under concurreent load. - Oracle
-
Provides a direct implementation of the
SELECT … FOR UPDATE SKIP LOCKEDstatement. It should be very efficient but limitations of the query syntax result in the need to use multiple subqueries for which pay a performance penalty. The overall performance is moderate but it does not collapse under concurreent load. - SQLite
-
Does not provide a
SELECT … FOR UPDATEimplementation due to its simplified locking characteristics. The only locking that is available for this purpose is an exclusive lock taken over the entire database. This makes it unsuitable for anything other than managing a small number of devices.
|
Alternative stored procedure for MariaDB and MySQL < 8.0
The file It should be a lot faster than using the default stored procedure under highly
concurrent workloads and should not result in thread starvation. It can best be
described as a useful hack that should not be used except when the
better-performing This procedure works by using "user locks" to provide skippable, row-level locking that facilitates non-blocking selection of unique IP addresses from the pool. We pay a performance penalty for managing the locks, however it is nowhere near as bad as serialising all concurrent pool access. Since user locks are local to the database server instance this procedure is not suitable for a multi-master cluster. |
|
Database clusters (e.g. MaxScale, Galera)
It is usually the case that row-level locks issued by Ingress nodes (relays, proxies, etc.) typically balance reads across all
cluster nodes (master and slave) whilst sending writes to one or more masters.
For many workloads this guarantees an appropriate level of data coherency,
however for an IP pool allocation implementation this is insufficient since we
must guarantee that no two nodes may concurrently With clusters the behaviour is normally that when multiple connections initially select the same IP address, one of them will succeed and when the remaining transactions attempt to finalise the lease of the already-allocated IP address only then will the conflict be detected. When this occurs the sane and correct behaviour of most clusters is for
deadlocks to be generated for all but the first transaction and for them to be
rolled back. However this will result in IP allocation failures, which is
likely to be reported as an
Incorrectly configured clusters are likely to have generally good performance under high volumes of serialised load but may exhibit a performance cliff-edge once a moderate level of concurrency is reached. The main symptom of this would be the sudden onset of deadlock. Test your cluster under conditions that represent the maximum load on the system, such as all devices simultaneously re-authenticating after some form of failure.
|
Choice of unique device identifier
Prior to issuing IP addresses to devices it is necessary to determine how to uniquely identify devices to which IP addresses will be assigned on the basis of the available RADIUS attributes.
In many fixed-line configurations a unique identifier for a device may be the
NAS-Port or NAS-Port-Id. In other scenarios identifiers based on the NAS
port may be shared by several devices and are unsuitable for uniquely
identifying devices. In some cases a circuit-specific identifier such as
Vendor-Specific.ADSL-Forum.Agent-Circuit-ID may be present.
| However, a circuit-specific identifier is unsuitable if there are multiple hosts on the circuit with which you issue individual addresses (for example when using PPPoE passthrough to provide unique sessions to firewalls behind a seperate CPE modem.) In this case the "device" must be thought of not as the circuit but the individual hosts behind it. |
The Calling-Station-Id attribute may contain host specific information (such
as a hardware address). You should consider the provenance of the data and
whether you trust it, e.g. does it report a MAC address that is under an
untrusted user’s control. Sometimes a combination of attributes including one
that you trust (such as "%{Vendor-Specific.ADSL-Forum.Agent-Circuit-ID}:%{Calling-Station-Id}") may
be appropriate.
The pool_key is used to issue indexed lookups into the radippool table so for
performance reasons it should have differentiation in the leading characters.
You need to understand your network configuration and the available RADIUS
attributes to determine what is an acceptable pool_key. You may need to
reconfigure your NAS to provide a suitable unique device identifier.
Most NASs allow the content of the Calling-Station-Id to the customised
so that in includes device specific information and network-related information
(such as SSID in the case of wireless networks).
|
When accounting is enabled it is essential that any pool_key attribute
is included in both authentication and accounting requests. Otherwise lease
renewal will fail and IP addresses will be prematurely returned to the pool
before sessions have disconnected, resulting in duplicate IP allocations.
|
Choice of lease duration
sqlippools manages the status of the IP addresses (either leased or free)
using the expiry_time of each entry in the radippool table. If it is in the
future then the IP address is considered to be actively leased (in use by a
device), otherwise it is free to be re-allocated to a device.
The lease_duration configuration item is used to set the expiry_time for an
IP address lease both when it is allocated at the start of a session and renewed
during an ongoing session. The expiry_time is set to lease_duration
seconds after the current time. expiry_time and lease_duration are used as
a means to manage the IP address pools. They are not communicated to the NAS or
device.
In access networks where the IP address pool is plentiful and sessions have a
defined maximum length (for example when Session-Timeout RADIUS attribute is
being honoured), setting a lease_duration that is a little longer than the
maximum permitted session length is acceptable.
Often either the session duration is not restricted or the capacity of the pool is small (relative to the number of devices) so that it needs careful management to continuously free IP addresses that are no longer in active use.
Normally accounting must be enabled on the NAS and an accounting interim interval set for two reasons:
-
Accounting Interim-Updaterequests indicate that an IP address is still in use by an ongoing session. This causes the lease to be periodically extended before it expires and prevents an IP address from being prematurely freed as this would result in active sessions having duplicate IP addresses. -
Accounting Stop(andAccounting On/Off) requests indicate that an IP address is no longer in use so that it can be immediately released. This ensures that a IP address is not leases longer than necessary and avoids wasted pool space when a group of devices reconnect after their sessions are interrupted.
| It is essential that Accounting Interim-Update requests are received reliably. Many NASs will repeat accounting requests that are not acknowledged by a RADIUS server or forward them elsewhere. You should determine whether accounting requests are delivered reliably in your network. |
If accounting requests are received reliably then lease_duration could be set a
little longer than twice the accounting interim interval to handle the
occasional lost interim-update. Otherwise the lease duration might need to be
set several times longer.