diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
commit | af754e596a8dbb05ed8580c342e7fe02e08b28e0 (patch) | |
tree | b2f334c2b55ede42081aa6710a72da784547d8ea /raddb/mods-config/sql/ippool-dhcp | |
parent | Initial commit. (diff) | |
download | freeradius-b95e0cd7685e6bbc2465b1f4efe1884df2f4ef01.tar.xz freeradius-b95e0cd7685e6bbc2465b1f4efe1884df2f4ef01.zip |
Adding upstream version 3.2.3+dfsg.upstream/3.2.3+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
15 files changed, 2141 insertions, 0 deletions
diff --git a/raddb/mods-config/sql/ippool-dhcp/mssql/procedure.sql b/raddb/mods-config/sql/ippool-dhcp/mssql/procedure.sql new file mode 100644 index 0000000..4cfbe1c --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mssql/procedure.sql @@ -0,0 +1,159 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- BEGIN TRAN; "SELECT FOR UPDATE"; UPDATE; COMMIT TRAN; -> EXEC sp +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- EXEC fr_dhcp_allocate_previous_or_new_framedipaddress \ +-- @v_pool_name = '%{control:${pool_name}}', \ +-- @v_gateway = '%{DHCP-Gateway-IP-Address}', \ +-- @v_pool_key = '${pool_key}', \ +-- @v_lease_duration = ${lease_duration}, \ +-- @v_requested_address = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +-- " +-- allocate_update = "" +-- allocate_commit = "" +-- + +CREATE OR ALTER PROCEDURE fr_dhcp_allocate_previous_or_new_framedipaddress + @v_pool_name VARCHAR(64), + @v_gateway VARCHAR(15), + @v_pool_key VARCHAR(64), + @v_lease_duration INT, + @v_requested_address VARCHAR(15) +AS + BEGIN + + -- MS SQL lacks a "SELECT FOR UPDATE" statement, and its table + -- hints do not provide a direct means to implement the row-level + -- read lock needed to guarentee that concurrent queries do not + -- select the same Framed-IP-Address for allocation to distinct + -- users. + -- + -- The "WITH cte AS ( SELECT ... ) UPDATE cte ... OUTPUT INTO" + -- patterns in this procedure body compensate by wrapping + -- the SELECT in a synthetic UPDATE which locks the row. + + DECLARE @r_address_tab TABLE(id VARCHAR(15)); + DECLARE @r_address VARCHAR(15); + + BEGIN TRAN; + + -- Reissue an existing IP address lease when re-authenticating a session + -- + WITH cte AS ( + SELECT TOP(1) FramedIPAddress + FROM dhcpippool WITH (rowlock, readpast) + JOIN dhcpstatus + ON dhcpstatus.status_id = dhcpippool.status_id + WHERE pool_name = @v_pool_name + AND expiry_time > CURRENT_TIMESTAMP + AND pool_key = @v_pool_key + AND dhcpstatus.status IN ('dynamic', 'static') + ) + UPDATE cte + SET FramedIPAddress = FramedIPAddress + OUTPUT INSERTED.FramedIPAddress INTO @r_address_tab; + SELECT @r_address = id FROM @r_address_tab; + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- WITH cte AS ( + -- SELECT TOP(1) FramedIPAddress + -- FROM dhcpippool WITH (rowlock, readpast) + -- JOIN dhcpstatus + -- ON dhcpstatus.status_id = dhcpippool.status_id + -- WHERE pool_name = @v_pool_name + -- AND pool_key = @v_pool_key + -- AND dhcpstatus.status IN ('dynamic', 'static') + -- ) + -- UPDATE cte + -- SET FramedIPAddress = FramedIPAddress + -- OUTPUT INSERTED.FramedIPAddress INTO @r_address_tab; + -- SELECT @r_address = id FROM @r_address_tab; + + -- Issue the requested IP address if it is available + -- + IF @r_address IS NULL AND @v_requested_address <> '0.0.0.0' + BEGIN + WITH cte AS ( + SELECT TOP(1) FramedIPAddress + FROM dhcpippool WITH (rowlock, readpast) + JOIN dhcpstatus + ON dhcpstatus.status_id = dhcpippool.status_id + WHERE pool_name = @v_pool_name + AND framedipaddress = @v_requested_address + AND dhcpstatus.status = 'dynamic' + AND ( pool_key = @v_pool_name OR expiry_time < CURRENT_TIMESTAMP ) + ) + UPDATE cte + SET FramedIPAddress = FramedIPAddress + OUTPUT INSERTED.FramedIPAddress INTO @r_address_tab; + SELECT @r_address = id FROM @r_address_tab; + END + + -- 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 + BEGIN + WITH cte AS ( + SELECT TOP(1) FramedIPAddress + FROM dhcpippool WITH (rowlock, readpast) + JOIN dhcpstatus + ON dhcpstatus.status_id = dhcpippool.status_id + WHERE pool_name = @v_pool_name + AND expiry_time < CURRENT_TIMESTAMP + AND dhcpstatus.status = 'dynamic' + ORDER BY + expiry_time + ) + UPDATE cte + SET FramedIPAddress = FramedIPAddress + OUTPUT INSERTED.FramedIPAddress INTO @r_address_tab; + SELECT @r_address = id FROM @r_address_tab; + END + + -- Return nothing if we failed to allocated an address + -- + IF @r_address IS NULL + BEGIN + COMMIT TRAN; + RETURN; + END + + -- Update the pool having allocated an IP address + -- + UPDATE dhcpippool + SET + gateway = @v_gateway, + pool_key = @v_pool_key, + expiry_time = DATEADD(SECOND,@v_lease_duration,CURRENT_TIMESTAMP) + WHERE framedipaddress = @r_address; + + COMMIT TRAN; + + -- Return the address that we allocated + SELECT @r_address; + + END +GO diff --git a/raddb/mods-config/sql/ippool-dhcp/mssql/queries.conf b/raddb/mods-config/sql/ippool-dhcp/mssql/queries.conf new file mode 100644 index 0000000..c919e2d --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mssql/queries.conf @@ -0,0 +1,257 @@ +# -*- text -*- +# +# ippool-dhcp/mssql/queries.conf -- MSSQL queries for rlm_sqlippool +# +# $Id$ + +# ***************** +# * DHCP DISCOVER * +# ***************** + +# +# This series of queries allocates an IP address +# + +# +# MSSQL-specific syntax - required if finding the address and updating +# it are separate queries +# +#allocate_begin = "BEGIN TRAN" +#allocate_commit = "COMMIT TRAN" + +allocate_begin = "" +allocate_commit = "" + +# +# Attempt to find the most recent existing IP address for the client +# +allocate_existing = "\ + WITH cte AS ( \ + SELECT TOP(1) framedipaddress, expiry_time, gateway \ + FROM ${ippool_table} WITH (xlock rowlock readpast) \ + JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND dhcpstatus.status IN ('dynamic', 'static') \ + ORDER BY expiry_time DESC \ + ) \ + UPDATE cte \ + SET expiry_time = DATEADD(SECOND,${offer_duration},CURRENT_TIMESTAMP), \ + gateway = '%{DHCP-Gateway-IP-Address}' \ + OUTPUT INSERTED.FramedIPAddress \ + FROM ${ippool_table}" + +# +# Determine whether the requested IP address is available +# +allocate_requested = "\ + WITH cte AS ( \ + SELECT TOP(1) framedipaddress, expiry_time, gateway \ + FROM ${ippool_table} WITH (xlock rowlock readpast) \ + JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ + AND dhcpstatus.status = 'dynamic' \ + AND expiry_time < CURRENT_TIMESTAMP \ + ) \ + UPDATE cte \ + SET expiry_time = DATEADD(SECOND,${offer_duration},CURRENT_TIMESTAMP), \ + gateway = '%{DHCP-Gateway-IP-Address}', \ + pool_key = '${pool_key}' \ + OUTPUT INSERTED.FramedIPAddress \ + FROM ${ippool_table}" + +# +# If the existing address can't be found this query will be run to +# find a free address +# +allocate_find = "\ + WITH cte AS ( \ + SELECT TOP(1) framedipaddress, expiry_time, gateway, pool_key \ + FROM ${ippool_table} WITH (xlock rowlock readpast) \ + JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND expiry_time < CURRENT_TIMESTAMP \ + AND dhcpstatus.status = 'dynamic' \ + ORDER BY expiry_time \ + ) \ + UPDATE cte \ + SET expiry_time = DATEADD(SECOND,${offer_duration},CURRENT_TIMESTAMP), \ + gateway = '%{DHCP-Gateway-IP-Address}', \ + pool_key = '${pool_key}' \ + OUTPUT INSERTED.FramedIPAddress \ + FROM ${ippool_table}" + +# +# Alternatively attempt all in one, more complex, query +# +# The ORDER BY clause of this query tries to allocate the same IP-address +# which user had last session. Ensure that pool_key is unique to the user +# within a given pool. +# +#allocate_find = "\ +# UPDATE TOP(1) ${ippool_table} \ +# SET FramedIPAddress = FramedIPAddress, \ +# pool_key = '${pool_key}', \ +# expiry_time = DATEADD(SECOND,${offer_duration},CURRENT_TIMESTAMP), \ +# GatewayIPAddress = '%{DHCP-Gateway-IP-Address}' \ +# OUTPUT INSERTED.FramedIPAddress \ +# FROM ${ippool_table} \ +# WHERE ${ippool_table}.id IN ( \ +# SELECT TOP (1) id FROM ( \ +# (SELECT TOP(1) id, 1 AS o FROM ${ippool_table} WITH (xlock rowlock readpast) \ +# JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND pool_key = '${pool_key}' \ +# AND dhcpstatus.status IN ('dynamic', 'static')) \ +# UNION \ +# (SELECT TOP(1) id, 2 AS o FROM ${ippool_table} WITH (xlock rowlock readpast) \ +# JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# AND dhcpstatus.status = 'dynamic' \ +# AND ( pool_key = '%{pool_key}' OR expiry_time < CURRENT_TIMESTAMP )) \ +# UNION \ +# (SELECT TOP(1) id, 3 AS o FROM ${ippool_table} WITH (xlock rowlock readpast) \ +# JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < CURRENT_TIMESTAMP \ +# AND dhcpstatus.status = 'dynamic' \ +# ORDER BY expiry_time) \ +# ) AS q ORDER BY q.o \ +# )" + +# +# If you prefer to allocate a random IP address every time, use this query instead. +# Note: This is very slow if you have a lot of free IPs. +# +#allocate_find = "\ +# WITH cte AS ( \ +# SELECT TOP(1) FramedIPAddress FROM ${ippool_table} \ +# JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < CURRENT_TIMESTAMP \ +# AND dhcpstatus.status = 'dynamic' \ +# ORDER BY \ +# newid() \ +# ) \ +# UPDATE cte WITH (rowlock, readpast) \ +# SET FramedIPAddress = FramedIPAddress \ +# OUTPUT INSERTED.FramedIPAddress" + +# +# If an IP could not be allocated, check to see if the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be +# commented out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT TOP(1) id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}'" + +# +# This is the final IP Allocation query, which saves the allocated ip details. +# Only needed if the initial "find" query is not storing the allocation. +# +#allocate_update = "\ +# UPDATE ${ippool_table} \ +# SET \ +# gateway = '%{DHCP-Gateway-IP-Address}', pool_key = '${pool_key}', \ +# expiry_time = DATEADD(SECOND,${offer_duration},CURRENT_TIMESTAMP) \ +# WHERE FramedIPAddress = '%I'" + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# +#allocate_begin = "" +#allocate_find = "\ +# EXEC fr_dhcp_allocate_previous_or_new_framedipaddress \ +# @v_pool_name = '%{control:${pool_name}}', \ +# @v_gateway = '%{DHCP-Gateway-IP-Address}', \ +# @v_pool_key = '${pool_key}', \ +# @v_lease_duration = ${offer_duration}, \ +# @v_requested_address = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# " +#allocate_update = "" +#allocate_commit = "" + + +# **************** +# * DHCP REQUEST * +# **************** + +# +# This query revokes any active offers for addresses that a client is not +# requesting when a DHCP REQUEST packet arrives +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = CURRENT_TIMESTAMP \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress <> '%{DHCP-Requested-IP-Address}' \ + AND expiry_time > CURRENT_TIMESTAMP \ + AND ${ippool_table}.status_id IN \ + (SELECT status_id FROM dhcpstatus WHERE status = 'dynamic')" + +# +# This query extends an existing lease (or offer) when a DHCP REQUEST packet +# arrives. This query must update a row when a lease is succesfully requested +# - queries that update no rows will result in a "notfound" response to +# the module which by default will give a DHCP-NAK reply. In this example +# incrementing "counter" is used to achieve this. +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = DATEADD(SECOND,${lease_duration},CURRENT_TIMESTAMP), \ + counter = counter + 1 \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{%{DHCP-Requested-IP-Address}:-%{DHCP-Client-IP-Address}}'" + + +# **************** +# * DHCP RELEASE * +# **************** + +# +# This query frees an IP address when a DHCP RELEASE packet arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = CURRENT_TIMESTAMP \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND FramedIPAddress = '%{DHCP-Client-IP-Address}' \ + AND ${ippool_table}.status_id IN \ + (SELECT status_id FROM dhcpstatus WHERE status = 'dynamic')" + +# +# This query is not applicable to DHCP +# +on_clear = "" + + +# **************** +# * DHCP DECLINE * +# **************** + +# +# This query marks an IP address as declined when a DHCP Decline +# packet arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET status_id = (SELECT status_id FROM dhcpstatus WHERE status = 'declined') \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Requested-IP-Address}'" diff --git a/raddb/mods-config/sql/ippool-dhcp/mssql/schema.sql b/raddb/mods-config/sql/ippool-dhcp/mssql/schema.sql new file mode 100644 index 0000000..dae4eff --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mssql/schema.sql @@ -0,0 +1,40 @@ +-- +-- Table structure for table 'dhcpippool' +-- +-- See also "procedure.sql" in this directory for +-- a stored procedure that gives much faster response. +-- + +CREATE TABLE dhcpstatus ( + status_id int NOT NULL, + status varchar(10) NOT NULL, + PRIMARY KEY (status_id) +) +GO + +INSERT INTO dhcpstatus (status_id, status) VALUES (1, 'dynamic'), (2, 'static'), (3, 'declined'), (4, 'disabled') +GO + +CREATE TABLE dhcpippool ( + id int IDENTITY (1,1) NOT NULL, + pool_name varchar(30) NOT NULL, + FramedIPAddress varchar(15) NOT NULL default '', + pool_key varchar(30) NOT NULL default '', + gateway varchar(15) NOT NULL default '', + expiry_time DATETIME NOT NULL default CURRENT_TIMESTAMP, + status_id int NOT NULL default 1, + counter int NOT NULL default 0, + CONSTRAINT fk_status_id FOREIGN KEY (status_id) REFERENCES dhcpstatus (status_id), + PRIMARY KEY (id) +) +GO + +CREATE INDEX dhcp_poolname_expire ON dhcpippool(pool_name, expiry_time) +GO + +CREATE INDEX dhcp_FramedIPAddress ON dhcpippool(FramedIPAddress) +GO + +CREATE INDEX dhcp_poolname_poolkey_FramedIPAddress ON dhcpippool(pool_name, pool_key, FramedIPAddress) +GO + diff --git a/raddb/mods-config/sql/ippool-dhcp/mysql/procedure-no-skip-locked.sql b/raddb/mods-config/sql/ippool-dhcp/mysql/procedure-no-skip-locked.sql new file mode 100644 index 0000000..bee37de --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mysql/procedure-no-skip-locked.sql @@ -0,0 +1,160 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- NOTE: This version of the SP is intended for MySQL variants that do not +-- support the SKIP LOCKED pragma, i.e. MariaDB and versions of MySQL +-- prior to 8.0. It should be a lot faster than using the default SP +-- without the SKIP LOCKED pragma under highly concurrent workloads +-- and not result in thread starvation. +-- +-- It is however a *useful hack* which should not be used if SKIP +-- LOCKED is available. +-- +-- WARNING: This query uses server-local, "user locks" (GET_LOCK and +-- RELEASE_LOCK), without the need for a transaction, to emulate +-- row locking with locked-row skipping. User locks are not +-- supported on clusters such as Galera and MaxScale. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- START TRANSACTION; SELECT FOR UPDATE; UPDATE; COMMIT; -> CALL sp() +-- +-- The stored procedure is executed within a single round trip which often +-- leads to reduced deadlocking and significant performance improvements. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- CALL fr_dhcp_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{DHCP-Gateway-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration}, \ +-- '%{%{${req_attribute_name}}:-0.0.0.0}' \ +-- )" +-- allocate_update = "" +-- allocate_commit = "" +-- + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS fr_dhcp_allocate_previous_or_new_framedipaddress; +CREATE PROCEDURE fr_allocate_previous_or_new_framedipaddress ( + IN v_pool_name VARCHAR(64), + IN v_gateway VARCHAR(15), + IN v_pool_key VARCHAR(64), + IN v_lease_duration INT, + IN v_requested_address VARCHAR(15) +) +SQL SECURITY INVOKER +proc:BEGIN + DECLARE r_address VARCHAR(15); + + -- Reissue an existing IP address lease when re-authenticating a session + -- + -- Note: In this query we get away without the need for FOR UPDATE + -- becase: + -- + -- (a) Each existing lease only belongs to a single device, so + -- no two devices will be racing over a single address. + -- (b) The set of existing leases (not yet expired) are + -- disjoint from the set of free leases, so not subject to + -- reallocation. + -- + SELECT framedipaddress INTO r_address + FROM dhcpippool + WHERE pool_name = v_pool_name + AND expiry_time > NOW() + AND pool_key = v_pool_key + AND `status` IN ('dynamic', 'static') + LIMIT 1; + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- SELECT framedipaddress INTO r_address + -- FROM dhcpippool + -- WHERE pool_name = v_pool_name + -- AND pool_key = v_pool_key + -- AND `status` IN ('dynamic', 'static') + -- LIMIT 1; + + -- + -- Normally here we would honour an IP address hint if the IP were + -- available, however we cannot do that without taking a lock which + -- defeats the purpose of this version of the stored procedure. + -- + -- It you need to honour an IP address hint then use a database with + -- support for SKIP LOCKED and use the normal stored procedure. + -- + + IF r_address IS NOT NULL THEN + UPDATE dhcpippool + SET + gateway = v_gateway, + pool_key = v_pool_key, + expiry_time = NOW() + INTERVAL v_lease_duration SECOND + WHERE + framedipaddress = r_address; + SELECT r_address; + LEAVE proc; + END IF; + + REPEAT + + -- 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 + -- + SELECT framedipaddress INTO r_address + FROM dhcpippool + WHERE pool_name = v_pool_name + AND expiry_time < NOW() + AND `status` = 'dynamic' + -- + -- WHERE ... GET_LOCK(...,0) = 1 is a poor man's SKIP LOCKED that simulates + -- a row-level lock using a "user lock" that allows the locked "rows" to be + -- skipped. After the user lock is acquired and the SELECT retired it does + -- not mean that the entirety of the WHERE clause is still true: Another + -- thread may have updated the expiry time and released the lock after we + -- checked the expiry_time but before we acquired the lock since SQL is free + -- to reorder the WHERE condition. Therefore we must recheck the condition + -- in the UPDATE statement below to detect this race. + -- + AND GET_LOCK(CONCAT('dhcpippool_', framedipaddress), 0) = 1 + LIMIT 1; + + IF r_address IS NULL THEN + DO RELEASE_LOCK(CONCAT('dhcpippool_', r_address)); + LEAVE proc; + END IF; + + UPDATE dhcpippool + SET + gateway = v_gateway, + pool_key = v_pool_key, + expiry_time = NOW() + INTERVAL v_lease_duration SECOND + WHERE + framedipaddress = r_address + -- + -- Here we re-evaluate the original condition for selecting the address + -- to detect a race, in which case we try again... + -- + AND expiry_time<NOW(); + + UNTIL ROW_COUNT() <> 0 END REPEAT; + + DO RELEASE_LOCK(CONCAT('dhcpippool_', r_address)); + SELECT r_address; + +END$$ + +DELIMITER ; diff --git a/raddb/mods-config/sql/ippool-dhcp/mysql/procedure.sql b/raddb/mods-config/sql/ippool-dhcp/mysql/procedure.sql new file mode 100644 index 0000000..b5dfae0 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mysql/procedure.sql @@ -0,0 +1,144 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- START TRANSACTION; SELECT FOR UPDATE; UPDATE; COMMIT; -> CALL sp() +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- CALL fr_dhcp_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{DHCP-Gateway-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration}, \ +-- '%{%{${req_attribute_name}}:-0.0.0.0}' \ +-- )" +-- allocate_update = "" +-- allocate_commit = "" +-- + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS fr_dhcp_allocate_previous_or_new_framedipaddress; +CREATE PROCEDURE fr_dhcp_allocate_previous_or_new_framedipaddress ( + IN v_pool_name VARCHAR(30), + IN v_gateway VARCHAR(15), + IN v_pool_key VARCHAR(30), + IN v_lease_duration INT, + IN v_requested_address VARCHAR(15) +) +SQL SECURITY INVOKER +proc:BEGIN + DECLARE r_address VARCHAR(15); + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + SET TRANSACTION ISOLATION LEVEL READ COMMITTED; + + START TRANSACTION; + + -- Reissue an existing IP address lease when re-authenticating a session + -- + SELECT framedipaddress INTO r_address + FROM dhcpippool + WHERE pool_name = v_pool_name + AND expiry_time > NOW() + AND pool_key = v_pool_key + AND `status` IN ('dynamic', 'static') + LIMIT 1 + FOR UPDATE; +-- FOR UPDATE SKIP LOCKED; -- Better performance, but limited support + + -- NOTE: You should enable SKIP LOCKED here (as well as any other + -- instances) if your database server supports it. If it is not + -- supported and you are not running a multi-master cluster (e.g. + -- Galera or MaxScale) then you should instead consider using the + -- SP in procedure-no-skip-locked.sql which will be faster and + -- less likely to result in thread starvation under highly + -- concurrent load. + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- SELECT framedipaddress INTO r_address + -- FROM dhcpippool + -- WHERE pool_name = v_pool_name + -- AND pool_key = v_pool_key + -- AND `status` IN ('dynamic', 'static') + -- LIMIT 1 + -- FOR UPDATE; + -- -- FOR UPDATE SKIP LOCKED; -- Better performance, but limited support + + -- Issue the requested IP address if it is available + -- + IF r_address IS NULL AND v_requested_address <> '0.0.0.0' THEN + SELECT framedipaddress INTO r_address + FROM dhcpippool + WHERE pool_name = v_pool_name + AND framedipaddress = v_requested_address + AND `status` = 'dynamic' + AND ( pool_key = v_pool_key OR expiry_time < NOW() ) + FOR UPDATE; +-- FOR UPDATE SKIP LOCKED; -- Better performance, but limited support + END IF; + + -- 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 dhcpippool + WHERE pool_name = v_pool_name + AND expiry_time < NOW() + AND `status` = 'dynamic' + ORDER BY + expiry_time + LIMIT 1 + FOR UPDATE; +-- FOR UPDATE SKIP LOCKED; -- Better performance, but limited support + END IF; + + -- Return nothing if we failed to allocated an address + -- + IF r_address IS NULL THEN + COMMIT; + LEAVE proc; + END IF; + + -- Update the pool having allocated an IP address + -- + UPDATE dhcpippool + SET + gateway = v_gateway, + pool_key = v_pool_key, + expiry_time = NOW() + INTERVAL v_lease_duration SECOND + WHERE framedipaddress = r_address; + + COMMIT; + + -- Return the address that we allocated + SELECT r_address; + +END$$ + +DELIMITER ; diff --git a/raddb/mods-config/sql/ippool-dhcp/mysql/queries.conf b/raddb/mods-config/sql/ippool-dhcp/mysql/queries.conf new file mode 100644 index 0000000..6aaecb1 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mysql/queries.conf @@ -0,0 +1,221 @@ +# -*- text -*- +# +# ippool-dhcp/mysql/queries.conf -- MySQL queries for rlm_sqlippool +# +# $Id$ + +# ***************** +# * DHCP DISCOVER * +# ***************** + +# +# This series of queries allocates an IP address + +# If using MySQL < 8.0.1 then remove SKIP LOCKED +# +# Attempt to find the most recent existing IP address for the client +# +allocate_existing = "\ + SELECT framedipaddress FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND `status` IN ('dynamic', 'static') \ + ORDER BY expiry_time DESC LIMIT 1 FOR UPDATE SKIP LOCKED" + +# +# Determine whether the requested IP address is available +# +allocate_requested = "\ + SELECT framedipaddress FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ + AND `status` = 'dynamic' \ + AND expiry_time < NOW() \ + FOR UPDATE SKIP LOCKED" + +# +# If the existing address can't be found this query will be run to +# find a free address +# +allocate_find = "\ + SELECT framedipaddress FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND expiry_time < NOW() \ + AND `status` = 'dynamic' \ + ORDER BY expiry_time LIMIT 1 FOR UPDATE SKIP LOCKED" + +# +# The ORDER BY clause of this query tries to allocate the same IP-address +# which the user last had. Ensure that pool_key is unique to the user +# within a given pool. +# + +# +# Alternatively do the operations in one query. Depending on transaction +# isolation mode, this can cause deadlocks +# +#allocate_find = "\ +# (SELECT framedipaddress, 1 AS o FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND pool_key = '${pool_key}' \ +# AND `status` IN ('dynamic', 'static') \ +# ORDER BY expiry_time DESC LIMIT 1 FOR UPDATE SKIP LOCKED \ +# ) UNION ( \ +# SELECT framedipaddress, 2 AS o FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# AND `status` = 'dynamic' \ +# AND ( pool_key = '${pool_key}' OR expiry_time < NOW() ) \ +# FOR UPDATE SKIP LOCKED \ +# ) UNION ( \ +# SELECT framedipaddress, 3 AS o FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < NOW() \ +# AND `status` = 'dynamic' \ +# ORDER BY expiry_time LIMIT 1 FOR UPDATE SKIP LOCKED \ +# ) ORDER BY o \ +# LIMIT 1" + +# +# If you prefer to allocate a random IP address every time, use this query instead. +# Note: This is very slow if you have a lot of free IPs. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < NOW() \ +# AND `status` = 'dynamic' \ +# ORDER BY \ +# RAND() \ +# LIMIT 1 \ +# FOR UPDATE" + +# +# The above query again, but with SKIP LOCKED. This requires MySQL >= 8.0.1, +# and InnoDB. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < NOW() \ +# AND `status` = 'dynamic' \ +# ORDER BY \ +# RAND() \ +# LIMIT 1 \ +# FOR UPDATE SKIP LOCKED" + +# +# If an IP could not be allocated, check to see if the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be +# commented out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}' \ + LIMIT 1" + +# +# This is the final IP Allocation query, which saves the allocated ip details. +# +allocate_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '%{DHCP-Gateway-IP-Address}', pool_key = '${pool_key}', \ + expiry_time = NOW() + INTERVAL ${offer_duration} SECOND \ + WHERE framedipaddress = '%I'" + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# +#allocate_begin = "" +#allocate_find = "\ +# CALL fr_dhcp_allocate_previous_or_new_framedipaddress( \ +# '%{control:${pool_name}}', \ +# '%{DHCP-Gateway-IP-Address}', \ +# '${pool_key}', \ +# ${offer_duration}, \ +# '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# )" +#allocate_update = "" +#allocate_commit = "" + + +# **************** +# * DHCP REQUEST * +# **************** + +# +# This query revokes any active offers for addresses that a client is not +# requesting when a DHCP REQUEST packet arrives +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = NOW() \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress <> '%{DHCP-Requested-IP-Address}' \ + AND expiry_time > NOW() \ + AND `status` = 'dynamic'" + +# +# This query extends an existing lease (or offer) when a DHCP REQUEST packet +# arrives. This query must update a row when a lease is succesfully requested +# - queries that update no rows will result in a "notfound" response to +# the module which by default will give a DHCP-NAK reply. In this example +# incrementing "counter" is used to achieve this. +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = NOW() + INTERVAL ${lease_duration} SECOND, \ + counter = counter + 1 \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{%{DHCP-Requested-IP-Address}:-%{DHCP-Client-IP-Address}}'" + + +# **************** +# * DHCP RELEASE * +# **************** + +# +# This query frees an IP address when a DHCP RELEASE packet arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = NOW() \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Client-IP-Address}' \ + AND `status` = 'dynamic'" + + +# +# This query is not applicable to DHCP +# +on_clear = "" + + +# **************** +# * DHCP DECLINE * +# **************** + +# +# This query marks an IP address as declined when a DHCP Decline +# packet arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET status = 'declined' \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Requested-IP-Address}'" diff --git a/raddb/mods-config/sql/ippool-dhcp/mysql/schema.sql b/raddb/mods-config/sql/ippool-dhcp/mysql/schema.sql new file mode 100644 index 0000000..d8b1219 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mysql/schema.sql @@ -0,0 +1,21 @@ +-- +-- Table structure for table 'dhcpippool' +-- +-- See also "procedure.sql" in this directory for a stored procedure +-- that is much faster. +-- + +CREATE TABLE dhcpippool ( + id int unsigned NOT NULL auto_increment, + pool_name varchar(30) NOT NULL, + framedipaddress varchar(15) NOT NULL default '', + pool_key varchar(30) NOT NULL default '', + gateway varchar(15) NOT NULL default '', + expiry_time DATETIME NOT NULL default NOW(), + `status` ENUM('dynamic', 'static', 'declined', 'disabled') DEFAULT 'dynamic', + counter int unsigned NOT NULL default 0, + PRIMARY KEY (id), + KEY dhcpippool_poolname_expire (pool_name, expiry_time), + KEY framedipaddress (framedipaddress), + KEY dhcpippool_poolname_poolkey_ipaddress (pool_name, pool_key, framedipaddress) +) ENGINE=InnoDB; diff --git a/raddb/mods-config/sql/ippool-dhcp/oracle/procedure.sql b/raddb/mods-config/sql/ippool-dhcp/oracle/procedure.sql new file mode 100644 index 0000000..84b4596 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/oracle/procedure.sql @@ -0,0 +1,217 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- BEGIN; SELECT FOR UPDATE; UPDATE; COMMIT; -> SELECT sp() FROM dual +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- SELECT fr_dhcp_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{DHCP-Gateway-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration}, \ +-- '%{%{${req_attribute_name}}:-0.0.0.0}' \ +-- ) FROM dual" +-- allocate_update = "" +-- allocate_commit = "" +-- + +CREATE OR REPLACE FUNCTION fr_dhcp_allocate_previous_or_new_framedipaddress ( + v_pool_name IN VARCHAR2, + v_gateway IN VARCHAR2, + v_pool_key IN VARCHAR2, + v_lease_duration IN INTEGER, + v_requested_address IN VARCHAR2 +) +RETURN varchar2 IS + PRAGMA AUTONOMOUS_TRANSACTION; + r_address varchar2(15); +BEGIN + + -- Reissue an existing IP address lease when re-authenticating a session + -- + BEGIN + SELECT framedipaddress INTO r_address FROM dhcpippool WHERE id IN ( + SELECT id FROM ( + SELECT * + FROM dhcpippool + JOIN dhcpstatus + ON dhcpstatus.status_id = dhcpippool.status_id + WHERE pool_name = v_pool_name + AND expiry_time > current_timestamp + AND pool_key = v_pool_key + AND dhcpstatus.status IN ('dynamic', 'static') + ) WHERE ROWNUM <= 1 + ) FOR UPDATE SKIP LOCKED; + EXCEPTION + WHEN NO_DATA_FOUND THEN + r_address := NULL; + END; + + -- Oracle >= 12c version of the above query + -- + -- BEGIN + -- SELECT framedipaddress INTO r_address FROM dhcpippool WHERE id IN ( + -- SELECT id FROM dhcpippool + -- JOIN dhcpstatus + -- ON dhcpstatus.status_id = dhcpippool.status_id + -- WHERE pool_name = v_pool_name + -- AND expiry_time > current_timestamp + -- AND pool_key = v_pool_key + -- AND dhcpstatus.status IN ('dynamic', 'static') + -- FETCH FIRST 1 ROWS ONLY + -- ) FOR UPDATE SKIP LOCKED; + -- EXCEPTION + -- WHEN NO_DATA_FOUND THEN + -- r_address := NULL; + -- END; + + + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- BEGIN + -- SELECT framedipaddress INTO r_address FROM dhcpippool WHERE id IN ( + -- SELECT id FROM ( + -- SELECT * + -- FROM dhcpippool + -- JOIN dhcpstatus + -- ON dhcpstatus.status_id = dhcpippool.status_id + -- WHERE pool_name = v_pool_name + -- AND pool_key = v_pool_key + -- AND dhcpstatus.status IN ('dynamic', 'static') + -- ) WHERE ROWNUM <= 1 + -- ) FOR UPDATE SKIP LOCKED; + -- EXCEPTION + -- WHEN NO_DATA_FOUND THEN + -- r_address := NULL; + -- END; + + -- Oracle >= 12c version of the above query + -- + -- BEGIN + -- SELECT framedipaddress INTO r_address FROM dhcpippool WHERE id IN ( + -- SELECT id FROM dhcpippool + -- JOIN dhcpstatus + -- ON dhcpstatus.status_id = dhcpippool.status_id + -- WHERE pool_name = v_pool_name + -- AND pool_key = v_pool_key + -- AND dhcpstatus.status IN ('dynamic', 'static') + -- FETCH FIRST 1 ROWS ONLY + -- ) FOR UPDATE SKIP LOCKED; + -- EXCEPTION + -- WHEN NO_DATA_FOUND THEN + -- r_address := NULL; + -- END; + + + + -- Issue the requested IP address if it is available + -- + IF r_address IS NULL AND v_requested_address <> '0.0.0.0' THEN + BEGIN + SELECT framedipaddress INTO r_address FROM dhcpippool WHERE id IN ( + SELECT id FROM ( + SELECT * + FROM dhcpippool + JOIN dhcpstatus + ON dhcpstatus.status_id = dhcpippool.status_id + WHERE pool_name = v_pool_name + AND framedipaddress = v_requested_address + AND dhcpstatus.status = 'dynamic' + AND expiry_time < CURRENT_TIMESTAMP + ) WHERE ROWNUM <= 1 + ) FOR UPDATE SKIP LOCKED; + EXCEPTION + WHEN NO_DATA_FOUND THEN + r_address := NULL; + END; + END IF; + + -- Oracle >= 12c version of the above query + -- + -- IF r_address IS NULL AND v_requested_address <> '0.0.0.0' THEN + -- BEGIN + -- SELECT framedipaddress INTO r_address FROM dhcpippool WHERE id IN ( + -- SELECT id FROM dhcpippool + -- JOIN dhcpstatus + -- ON dhcpstatus.status_id = dhcpippool.status_id + -- WHERE pool_name = v_pool_name + -- AND framedipaddress = v_requested_address + -- AND dhcpstatus.status = 'dynamic' + -- AND expiry_time < CURRENT_TIMESTAMP + -- FETCH FIRST 1 ROWS ONLY + -- ) FOR UPDATE SKIP LOCKED; + -- EXCEPTION + -- WHEN NO_DATA_FOUND THEN + -- r_address := NULL; + -- END; + -- END IF; + + + + -- 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 + DECLARE + l_cursor sys_refcursor; + BEGIN + OPEN l_cursor FOR + SELECT framedipaddress + FROM dhcpippool + JOIN dhcpstatus + ON dhcpstatus.status_id = dhcpippool.status_id + WHERE pool_name = v_pool_name + AND expiry_time < CURRENT_TIMESTAMP + AND dhcpstatus.status = 'dynamic' + ORDER BY expiry_time + FOR UPDATE SKIP LOCKED; + FETCH l_cursor INTO r_address; + CLOSE l_cursor; + EXCEPTION + WHEN NO_DATA_FOUND THEN + r_address := NULL; + END; + END IF; + + -- Return nothing if we failed to allocated an address + -- + IF r_address IS NULL THEN + COMMIT; + RETURN r_address; + END IF; + + -- Update the pool having allocated an IP address + -- + UPDATE dhcpippool + SET + gateway = v_gateway, + pool_key = v_pool_key, + expiry_time = CURRENT_TIMESTAMP + v_lease_duration * INTERVAL '1' SECOND(1) + WHERE framedipaddress = r_address; + + -- Return the address that we allocated + COMMIT; + RETURN r_address; + +END; + diff --git a/raddb/mods-config/sql/ippool-dhcp/oracle/queries.conf b/raddb/mods-config/sql/ippool-dhcp/oracle/queries.conf new file mode 100644 index 0000000..0fcffc3 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/oracle/queries.conf @@ -0,0 +1,200 @@ +# -*- text -*- +# +# ippool-dhcp/oracle/queries.conf -- Oracle queries for rlm_sqlippool +# +# $Id$ + +start_begin = "commit" +alive_begin = "commit" +stop_begin = "commit" +on_begin = "commit" +off_begin = "commit" + + +# ***************** +# * DHCP DISCOVER * +# ***************** + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# Oracle's locking mechanism limitations prevents the use of single queries +# that can either find a client's existing address or the first available one. +# +allocate_begin = "" +allocate_find = "\ + SELECT fr_dhcp_allocate_previous_or_new_framedipaddress( \ + '%{control:${pool_name}}', \ + '%{DHCP-Gateway-IP-Address}', \ + '${pool_key}', \ + '${offer_duration}', \ + '%{%{${req_attribute_name}}:-0.0.0.0}' \ + ) FROM dual" +allocate_update = "" +allocate_commit = "" + + +# +# If you prefer to allocate a random IP address every time, use this query instead +# Note: This is very slow if you have a lot of free IPs. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( \ +# SELECT id FROM ( \ +# SELECT * \ +# FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < current_timestamp \ +# AND dhcpstatus.status = 'dynamic' \ +# ORDER BY DBMS_RANDOM.VALUE \ +# ) WHERE ROWNUM <= 1 \ +# ) FOR UPDATE" + +# +# The above query again, but with SKIP LOCKED. This requires Oracle > 11g. +# It may work in 9i and 10g, but is not documented, so YMMV. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( \ +# SELECT id FROM (\ +# SELECT * \ +# FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < current_timestamp \ +# AND dhcpstatus.status = 'dynamic' \ +# ORDER BY DBMS_RANDOM.VALUE \ +# ) WHERE ROWNUM <= 1 \ +# ) FOR UPDATE SKIP LOCKED" + +# +# A tidier version that needs Oracle >= 12c +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( +# SELECT id FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < current_timestamp \ +# AND dhcpstatus.status = 'dynamic' \ +# ORDER BY DBMS_RANDOM.VALUE \ +# FETCH FIRST 1 ROWS ONLY +# ) FOR UPDATE SKIP LOCKED" + +# +# If an IP could not be allocated, check to see whether the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be commented +# out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT id \ + FROM (\ + SELECT id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}'\ + ) \ + WHERE ROWNUM = 1" + +# +# This query marks the IP address handed out by "allocate-find" as used +# for the period of "offer_duration" after which time it may be reused. +# Only needed if allocate_find is not using the stored procedure and therefore +# not updating the lease +# +#allocate_update = "\ +# UPDATE ${ippool_table} \ +# SET \ +# gateway = '%{DHCP-Gateway-IP-Address}', \ +# pool_key = '${pool_key}', \ +# expiry_time = current_timestamp + INTERVAL '${offer_duration}' second(1) \ +# WHERE framedipaddress = '%I'" + + +# **************** +# * DHCP REQUEST * +# **************** + +# +# This query revokes any active offers for addresses that a client is not +# requesting when a DHCP REQUEST packet arrives +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '0', \ + expiry_time = current_timestamp - INTERVAL '1' second(1) \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress <> '%{DHCP-Requested-IP-Address}' \ + AND expiry_time > current_timestamp \ + AND ${ippool_table}.status_id IN \ + (SELECT status_id FROM dhcpstatus WHERE status = 'dynamic')" +start_commit = "COMMIT" + +# +# This query extends an existing lease (or offer) when a DHCP REQUEST packet +# arrives. This query must update a row when a lease is succesfully requested +# - queries that update no rows will result in a "notfound" response to +# the module which by default will give a DHCP-NAK reply. In this example +# incrementing "counter" is used to achieve this. +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = current_timestamp + INTERVAL '${lease_duration}' second(1), \ + counter = counter + 1 \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{%{DHCP-Requested-IP-Address}:-%{DHCP-Client-IP-Address}}'" +alive_commit = "COMMIT" + + +# **************** +# * DHCP RELEASE * +# **************** + +# +# This query frees an IP address when a DHCP RELEASE packet arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '0', \ + expiry_time = current_timestamp - INTERVAL '1' second(1) \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Client-IP-Address}' \ + AND ${ippool_table}.status_id IN \ + (SELECT status_id FROM dhcpstatus WHERE status = 'dynamic')" +stop_commit = "COMMIT" + + +# +# This query is not applicable to DHCP +# +on_clear = "" + + +# **************** +# * DHCP DECLINE * +# **************** + +# +# This query marks an IP address as declined when a DHCP Decline +# packet arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET status_id = (SELECT status_id FROM dhcpstatus WHERE status = 'declined') \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Requested-IP-Address}'" +off_commit = "COMMIT" + diff --git a/raddb/mods-config/sql/ippool-dhcp/oracle/schema.sql b/raddb/mods-config/sql/ippool-dhcp/oracle/schema.sql new file mode 100644 index 0000000..32d28bb --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/oracle/schema.sql @@ -0,0 +1,28 @@ +CREATE TABLE dhcpstatus ( + status_id INT PRIMARY KEY, + status VARCHAR(10) NOT NULL +); + +INSERT INTO dhcpstatus (status_id, status) VALUES (1, 'dynamic'); +INSERT INTO dhcpstatus (status_id, status) VALUES (2, 'static'); +INSERT INTO dhcpstatus (status_id, status) VALUES (3, 'declined'); +INSERT INTO dhcpstatus (status_id, status) VALUES (4, 'disabled'); + +CREATE SEQUENCE dhcpippool_seq START WITH 1 INCREMENT BY 1; + +CREATE TABLE dhcpippool ( + id INT DEFAULT ON NULL dhcpippool_seq.NEXTVAL PRIMARY KEY, + pool_name VARCHAR(30) NOT NULL, + framedipaddress VARCHAR(15) NOT NULL, + pool_key VARCHAR(30) DEFAULT '', + gateway VARCHAR(15) DEFAULT '', + expiry_time timestamp(0) DEFAULT CURRENT_TIMESTAMP, + status_id INT DEFAULT 1, + counter INT DEFAULT 0, + FOREIGN KEY (status_id) REFERENCES dhcpstatus(status_id) +); + +CREATE INDEX dhcpippool_poolname_expire ON dhcpippool (pool_name, expiry_time); +CREATE INDEX dhcpippool_framedipaddress ON dhcpippool (framedipaddress); +CREATE INDEX dhcpippool_poolname_poolkey_ipaddress ON dhcpippool (pool_name, pool_key, framedipaddress); + diff --git a/raddb/mods-config/sql/ippool-dhcp/postgresql/procedure.sql b/raddb/mods-config/sql/ippool-dhcp/postgresql/procedure.sql new file mode 100644 index 0000000..379a349 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/postgresql/procedure.sql @@ -0,0 +1,119 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- START TRANSACTION; SELECT FOR UPDATE; UPDATE; COMMIT; -> SELECT sp() +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- SELECT fr_dhcp_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{DHCP-Gateway-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration}, \ +-- '%{%{${req_attribute_name}}:-0.0.0.0}' \ +-- )" +-- allocate_update = "" +-- allocate_commit = "" +-- + +CREATE OR REPLACE FUNCTION fr_dhcp_allocate_previous_or_new_framedipaddress ( + v_pool_name VARCHAR(64), + v_gateway VARCHAR(16), + v_pool_key VARCHAR(64), + v_lease_duration INT, + v_requested_address INET +) +RETURNS inet +LANGUAGE plpgsql +AS $$ +DECLARE + r_address INET; +BEGIN + + -- Reissue an existing IP address lease when re-authenticating a session + -- + WITH ips AS ( + SELECT framedipaddress FROM dhcpippool + WHERE pool_name = v_pool_name + AND pool_key = v_pool_key + AND expiry_time > NOW() + AND status IN ('dynamic', 'static') + LIMIT 1 FOR UPDATE SKIP LOCKED ) + UPDATE dhcpippool + SET expiry_time = NOW() + v_lease_duration * interval '1 sec' + FROM ips WHERE dhcpippool.framedipaddress = ips.framedipaddress + RETURNING dhcpippool.framedipaddress INTO r_address; + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- WITH ips AS ( + -- SELECT framedipaddress FROM dhcpippool + -- WHERE pool_name = v_pool_name + -- AND pool_key = v_pool_key + -- AND status IN ('dynamic', 'static') + -- LIMIT 1 FOR UPDATE SKIP LOCKED ) + -- UPDATE dhcpippool + -- SET expiry_time = NOW + v_lease_duration * interval '1 sec' + -- FROM ips WHERE dhcpippool.framedipaddress = ips.framedipaddress + -- RETURNING dhcpippool.framedipaddress INTO r_address; + + -- Issue the requested IP address if it is available + -- + IF r_address IS NULL AND v_requested_address != '0.0.0.0' THEN + WITH ips AS ( + SELECT framedipaddress FROM dhcpippool + WHERE pool_name = v_pool_name + AND framedipaddress = v_requested_address + AND status = 'dynamic' + AND ( pool_key = v_pool_key OR expiry_time < NOW() ) + LIMIT 1 FOR UPDATE SKIP LOCKED ) + UPDATE dhcpippool + SET pool_key = v_pool_key, + expiry_time = NOW() + v_lease_duration * interval '1 sec', + gateway = v_gateway + FROM ips WHERE dhcpippool.framedipaddress = ips.framedipaddress + RETURNING dhcpippool.framedipaddress INTO r_address; + END IF; + + -- 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 + WITH ips AS ( + SELECT framedipaddress FROM dhcpippool + WHERE pool_name = v_pool_name + AND expiry_time < NOW() + AND status = 'dynamic' + ORDER BY expiry_time + LIMIT 1 FOR UPDATE SKIP LOCKED ) + UPDATE dhcpippool + SET pool_key = v_pool_key, + expiry_time = NOW() + v_lease_duration * interval '1 sec', + gateway = v_gateway + FROM ips WHERE dhcpippool.framedipaddress = ips.framedipaddress + RETURNING dhcpippool.framedipaddress INTO r_address; + END IF; + + -- Return the address that we allocated + RETURN r_address; + +END +$$; diff --git a/raddb/mods-config/sql/ippool-dhcp/postgresql/queries.conf b/raddb/mods-config/sql/ippool-dhcp/postgresql/queries.conf new file mode 100644 index 0000000..632fc70 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/postgresql/queries.conf @@ -0,0 +1,291 @@ +# -*- text -*- +# +# ippool-dhcp/postgresql/queries.conf -- PostgreSQL queries for rlm_sqlippool +# +# $Id$ + +# ***************** +# * DHCP DISCOVER * +# ***************** + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# +# This requires PostgreSQL >= 9.5 as SKIP LOCKED is used. +# +# The "NO LOAD BALANCE" comment is included here to indicate to a PgPool +# system that this needs to be a write transaction. PgPool itself cannot +# detect this from the statement alone. If you are using PgPool and do not +# have this comment, the query may go to a read only server, and will fail. +# This has no negative effect if you are not using PgPool. +# +allocate_begin = "" +allocate_find = "\ + /*NO LOAD BALANCE*/ \ + SELECT fr_dhcp_allocate_previous_or_new_framedipaddress( \ + '%{control:${pool_name}}', \ + '%{DHCP-Gateway-IP-Address}', \ + '${pool_key}', \ + '${offer_duration}', \ + '%{%{${req_attribute_name}}:-0.0.0.0}' \ + )" +allocate_update = "" +allocate_commit = "" + +# +# If stored procedures are not able to be used, the following queries can +# be used. +# Comment out all the above queries and choose the appropriate "allocate_find" +# to match the desired outcome and also the version of "allocate_update" below. +# + +# +# This sequence of queries allocates an IP address from the Pool +# +#allocate_begin = "BEGIN" + + +# Attempt to find the most recent existing IP address for the client +# +#allocate_existing = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND pool_key = '${pool_key}' \ +# AND status IN ('dynamic', 'static') \ +# ORDER BY expiry_time DESC \ +# LIMIT 1 \ +# FOR UPDATE" + +# The same query with SKIP LOCKED - requires PostgreSQL >= 9.5 +# allocate_existing = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND pool_key = '${pool_key}' \ +# AND status IN ('dynamic', 'static') \ +# ORDER BY expiry_time DESC \ +# LIMIT 1 \ +# FOR UPDATE SKIP LOCKED" + + +# +# Determine whether the requested IP address is available +# +#allocate_requested = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# AND status = 'dynamic' \ +# AND expiry_time < 'now'::timestamp(0) \ +# FOR UPDATE" + +# The same query with SKIP LOCKED - requires PostgreSQL >= 9.5 +#allocate_requested = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# AND status = 'dynamic' \ +# AND expiry_time < 'now'::timestamp(0) \ +# FOR UPDATE SKIP LOCKED" + + +# +# If the existing address can't be found this query will be run to +# find a free address +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < 'now'::timestamp(0) \ +# AND status = 'dynamic' \ +# ORDER BY expiry_time \ +# LIMIT 1 \ +# FOR UPDATE" + +# The same query with SKIP LOCKED - requires PostgreSQL >= 9.5 +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < 'now'::timestamp(0) \ +# AND status = 'dynamic' \ +# ORDER BY expiry_time \ +# LIMIT 1 \ +# FOR UPDATE SKIP LOCKED" + +# +# If you prefer to allocate a random IP address every time, use this query instead +# Note: This is very slow if you have a lot of free IPs. +# Use of either of these next two queries should have the allocate_begin line commented out +# and allocate_update below un-commented. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' AND expiry_time < 'now'::timestamp(0) \ +# AND status = 'dynamic' \ +# ORDER BY RANDOM() \ +# LIMIT 1 \ +# FOR UPDATE" + +# +# The above query again, but with SKIP LOCKED. This requires PostgreSQL >= 9.5. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' AND expiry_time < 'now'::timestamp(0) \ +# AND status = 'dynamic' \ +# ORDER BY RANDOM() \ +# LIMIT 1 \ +# FOR UPDATE SKIP LOCKED" + +# +# This query marks the IP address handed out by "allocate-find" as used +# for the period of "lease_duration" after which time it may be reused. +# +#allocate_update = "\ +# UPDATE ${ippool_table} \ +# SET \ +# gateway = '%{DHCP-Gateway-IP-Address}', \ +# pool_key = '${pool_key}', \ +# expiry_time = 'now'::timestamp(0) + '${offer_duration} second'::interval \ +# WHERE framedipaddress = '%I'" + + +# +# Alternatively, merge the matching of existing IP and free IP into a single query +# This version does the update as well - so allocate_begin, allocate_update and +# allocate_commit should be blank +# +#allocate_begin = "" +#allocate_find = "\ +# WITH found AS ( \ +# WITH existing AS ( \ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND pool_key = '${pool_key}' \ +# ORDER BY expiry_time DESC \ +# LIMIT 1 \ +# FOR UPDATE SKIP LOCKED \ +# ), requested AS ( \ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# AND status = 'dynamic' \ +# AND ( pool_key = '${pool_key}' OR expiry_time < 'now'::timestamp(0) ) \ +# FOR UPDATE SKIP LOCKED \ +# ), new AS ( \ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < 'now'::timestamp(0) \ +# AND status = 'dynamic' \ +# ORDER BY expiry_time \ +# LIMIT 1 \ +# FOR UPDATE SKIP LOCKED \ +# ) \ +# SELECT framedipaddress, 1 AS o FROM existing \ +# UNION ALL \ +# SELECT framedipaddress, 2 AS o FROM requested \ +# UNION ALL \ +# SELECT framedipaddress, 3 AS o FROM new \ +# ORDER BY o LIMIT 1 \ +# ) \ +# UPDATE ${ippool_table} \ +# SET pool_key = '${pool_key}', \ +# expiry_time = 'now'::timestamp(0) + '${offer_duration} second'::interval, \ +# gateway = '%{DHCP-Gateway-IP-Address}' \ +# FROM found \ +# WHERE found.framedipaddress = ${ippool_table}.framedipaddress \ +# RETURNING found.framedipaddress" +#allocate_update = "" +#allocate_commit = "" + + +# +# If an IP could not be allocated, check to see whether the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be commented +# out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}' \ + LIMIT 1" + + +# **************** +# * DHCP REQUEST * +# **************** + +# +# This query revokes any active offers for addresses that a client is not +# requesting when a DHCP REQUEST packet arrives, i.e, each server (sharing the +# same database) may have simultaneously offered a unique address. +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = 'now'::timestamp(0) - '1 second'::interval \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress <> '%{DHCP-Requested-IP-Address}' \ + AND expiry_time > 'now'::timestamp(0) \ + AND status = 'dynamic'" + +# +# This query extends an existing lease (or offer) when a DHCP REQUEST packet +# arrives. This query must update a row when a lease is succesfully requested +# - queries that update no rows will result in a "notfound" response to +# the module which by default will give a DHCP-NAK reply. In this example +# incrementing "counter" is used to achieve this. +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = 'now'::timestamp(0) + '${lease_duration} second'::interval, \ + counter = counter + 1 \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{%{DHCP-Requested-IP-Address}:-%{DHCP-Client-IP-Address}}'" + + +# **************** +# * DHCP RELEASE * +# **************** + +# +# This query frees an IP address when a DHCP RELEASE packet arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = 'now'::timestamp(0) - '1 second'::interval \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Client-IP-Address}' \ + AND status = 'dynamic'" + + +# +# This query is not applicable to DHCP +# +on_clear = "" + + +# **************** +# * DHCP DECLINE * +# **************** + +# +# This query marks an IP address as declined when a DHCP DECLINE packet +# arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET status = 'declined' \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Requested-IP-Address}'" diff --git a/raddb/mods-config/sql/ippool-dhcp/postgresql/schema.sql b/raddb/mods-config/sql/ippool-dhcp/postgresql/schema.sql new file mode 100644 index 0000000..af86889 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/postgresql/schema.sql @@ -0,0 +1,23 @@ +-- +-- Table structure for table 'dhcpippool' +-- +-- See also "procedure.sql" in this directory for +-- a stored procedure that gives much faster response. +-- + +CREATE TYPE dhcp_status AS ENUM ('dynamic', 'static', 'declined', 'disabled'); + +CREATE TABLE dhcpippool ( + id BIGSERIAL PRIMARY KEY, + pool_name varchar(64) NOT NULL, + FramedIPAddress INET NOT NULL, + pool_key VARCHAR(64) NOT NULL default '0', + gateway VARCHAR(16) NOT NULL default '', + expiry_time TIMESTAMP(0) without time zone NOT NULL default NOW(), + status dhcp_status DEFAULT 'dynamic', + counter INT NOT NULL default 0 +); + +CREATE INDEX dhcpippool_poolname_expire ON dhcpippool USING btree (pool_name, expiry_time); +CREATE INDEX dhcpippool_framedipaddress ON dhcpippool USING btree (framedipaddress); +CREATE INDEX dhcpippool_poolname_poolkey_ipaddress ON dhcpippool USING btree (pool_name, pool_key, framedipaddress); diff --git a/raddb/mods-config/sql/ippool-dhcp/sqlite/queries.conf b/raddb/mods-config/sql/ippool-dhcp/sqlite/queries.conf new file mode 100644 index 0000000..d99e09b --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/sqlite/queries.conf @@ -0,0 +1,236 @@ +# -*- text -*- +# +# ippool-dhcp/sqlite/queries.conf -- SQLite queries for rlm_sqlippool +# +# $Id$ + +# ***************** +# * DHCP DISCOVER * +# ***************** + +# +# SQLite does not implement SELECT FOR UPDATE which is normally used to place +# an exclusive lock over rows to prevent the same address from being +# concurrently selected for allocation to multiple users. +# +# The most granular read-blocking lock that SQLite has is an exclusive lock +# over the database, so that's what we use. All locking in SQLite is performed +# over the entire database and we perform a row update for any IP that we +# allocate, requiring an exclusive lock. Taking the exclusive lock from the +# start of the transaction (even if it were not required to guard the SELECT) +# is actually quicker than if we deferred it causing SQLite to "upgrade" the +# automatic shared lock for the transaction to an exclusive lock for the +# subsequent UPDATE. +# +allocate_begin = "BEGIN EXCLUSIVE" +allocate_commit = "COMMIT" + +# +# Attempt to find the most recent existing IP address for the client +# +allocate_existing = "\ + SELECT framedipaddress \ + FROM ${ippool_table} \ + JOIN dhcpstatus \ + ON ${ippool_table}.status_id = dhcpstatus.status_id \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND status IN ('dynamic', 'static') \ + ORDER BY expiry_time DESC \ + LIMIT 1" + +# +# Determine whether the requested IP address is available +# +allocate_requested = "\ + SELECT framedipaddress \ + FROM ${ippool_table} \ + JOIN dhcpstatus \ + ON ${ippool_table}.status_id = dhcpstatus.status_id \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ + AND status = 'dynamic' \ + AND expiry_time < datetime('now')" + +# +# If the existing address can't be found this query will be run to +# find a free address +# +allocate_find = "\ + SELECT framedipaddress \ + FROM ${ippool_table} \ + JOIN dhcpstatus \ + ON ${ippool_table}.status_id = dhcpstatus.status_id \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND expiry_time < datetime('now') \ + AND status = 'dynamic' \ + ORDER BY expiry_time LIMIT 1" + +# +# This series of queries allocates an IP address +# +# Either pull the most recent allocated IP for this client or the +# oldest expired one. The first sub query returns the most recent +# lease for the client (if there is one), the second returns the +# oldest expired one. +# Sorting the result by expiry_time DESC will return the client specific +# IP if it exists, otherwise an expired one. +# +#allocate_find = "\ +# SELECT framedipaddress, 1 AS o \ +# FROM ( \ +# SELECT framedipaddress \ +# FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND pool_key = '${pool_key}' \ +# AND status IN ('dynamic', 'static') \ +# ORDER BY expiry_time DESC \ +# LIMIT 1 \ +# ) UNION \ +# SELECT framedipaddress, 2 AS o \ +# FROM ( \ +# SELECT framedipaddress \ +# FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# AND status = 'dynamic' \ +# AND ( pool_key = '${pool_key}' OR expiry_time < datetime('now') ) \ +# ) UNION \ +# SELECT framedipaddress, 3 AS o \ +# FROM ( \ +# SELECT framedipaddress \ +# FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < datetime('now') \ +# AND status = 'dynamic' \ +# ORDER BY expiry_time LIMIT 1 \ +# ) \ +# ORDER BY o \ +# LIMIT 1" + +# +# If you prefer to allocate a random IP address every time, i +# use this query instead +# Note: This is very slow if you have a lot of free IPs. +# + +#allocate_find = "\ +# SELECT framedipaddress \ +# FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < datetime('now') \ +# AND status = 'dynamic' \ +# ORDER BY RAND() \ + + +# +# If an IP could not be allocated, check to see if the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be +# commented out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}' \ + LIMIT 1" + +# +# This is the final IP Allocation query, which saves the allocated ip details +# +allocate_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '%{DHCP-Gateway-IP-Address}', \ + pool_key = '${pool_key}', \ + expiry_time = datetime(strftime('%%s', 'now') + ${offer_duration}, 'unixepoch') \ + WHERE framedipaddress = '%I'" + + +# **************** +# * DHCP REQUEST * +# **************** + +# +# This query revokes any active offers for addresses that a client is not +# requesting when a DHCP REQUEST packet arrives +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = datetime('now') \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress <> '%{DHCP-Requested-IP-Address}' \ + AND expiry_time > datetime('now') \ + AND ${ippool_table}.status_id IN \ + (SELECT status_id FROM dhcpstatus WHERE status = 'dynamic')" + +# +# This query extends an existing lease (or offer) when a DHCP REQUEST packet +# arrives. This query must update a row when a lease is succesfully requested +# - queries that update no rows will result in a "notfound" response to +# the module which by default will give a DHCP-NAK reply. In this example +# incrementing "counter" is used to achieve this. +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = datetime(strftime('%%s', 'now') + ${lease_duration}, 'unixepoch'), \ + counter = counter + 1 \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{%{DHCP-Requested-IP-Address}:-%{DHCP-Client-IP-Address}}'" + + +# **************** +# * DHCP RELEASE * +# **************** + +# +# This query frees an IP address when a DHCP RELEASE packet arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = datetime('now') \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Client-IP-Address}' \ + AND ${ippool_table}.status_id IN \ + (SELECT status_id FROM dhcpstatus WHERE status = 'dynamic')" + + +# +# This query is not applicable to DHCP +# +on_clear = "" + + +# **************** +# * DHCP DECLINE * +# **************** + +# +# This query marks an IP address as declined when a DHCP Decline +# packet arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET status_id = (SELECT status_id FROM dhcpstatus WHERE status = 'declined') \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Requested-IP-Address}'" + diff --git a/raddb/mods-config/sql/ippool-dhcp/sqlite/schema.sql b/raddb/mods-config/sql/ippool-dhcp/sqlite/schema.sql new file mode 100644 index 0000000..339d58d --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/sqlite/schema.sql @@ -0,0 +1,25 @@ +-- +-- Table structure for table 'dhcpippool' +-- +CREATE TABLE dhcpstatus ( + status_id int PRIMARY KEY, + status varchar(10) NOT NULL +); + +INSERT INTO dhcpstatus (status_id, status) VALUES (1, 'dynamic'), (2, 'static'), (3, 'declined'), (4, 'disabled'); + +CREATE TABLE dhcpippool ( + id int(11) PRIMARY KEY, + pool_name varchar(30) NOT NULL, + framedipaddress varchar(15) NOT NULL default '', + pool_key varchar(30) NOT NULL default '', + gateway varchar(15) NOT NULL default '', + expiry_time DATETIME NOT NULL default (DATETIME('now')), + status_id int NOT NULL default 1, + counter int NOT NULL default 0, + FOREIGN KEY(status_id) REFERENCES dhcpstatus(status_id) +); + +CREATE INDEX dhcpippool_poolname_expire ON dhcpippool(pool_name, expiry_time); +CREATE INDEX dhcpippool_framedipaddress ON dhcpippool(framedipaddress); +CREATE INDEX dhcpippool_poolname_poolkey_ipaddress ON dhcpippool(pool_name, pool_key, framedipaddress); |