--
--- Use the following indexes and function if using the stored procedure to
--- find the previously used address.
+-- A stored procedure to reallocate a user's previous address, otherwise
+-- provide a free address.
--
--- You may wish to set the ORDER BY expiry_time to DESC for the first two
--- queries in order to assign the address that the user had most recently,
--- instead of assigning the oldest address the user had used.
+-- 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 sp_allocate_previous_or_new_framedipaddress( \
+-- '%{control:${pool_name}}', \
+-- '%{User-Name}', \
+-- '%{Calling-Station-Id}', \
+-- '%{NAS-IP-Address}', \
+-- '${pool_key}', \
+-- ${lease_duration} \
+-- )"
+-- allocate_update = ""
+-- allocate_commit = ""
--
-CREATE INDEX radippool_pool_name ON radippool USING btree (pool_name);
-CREATE INDEX radippool_username ON radippool USING btree (username);
-CREATE INDEX radippool_callingstationid ON radippool USING btree (callingstationid);
+CREATE INDEX radippool_poolname_username_callingstationid ON radippool(pool_name,username,callingstationid);
-CREATE OR REPLACE FUNCTION find_previous_or_new_framedipaddress (
+CREATE OR REPLACE FUNCTION sp_allocate_previous_or_new_framedipaddress (
v_pool_name VARCHAR(64),
v_username VARCHAR(64),
- v_callingstationid VARCHAR(64)
+ v_callingstationid VARCHAR(64),
+ v_nasipaddress VARCHAR(16),
+ v_pool_key VARCHAR(64),
+ v_lease_duration INT
)
RETURNS inet
LANGUAGE plpgsql
DECLARE
r_address inet;
BEGIN
+
+ -- Reissue an existing IP address lease when re-authenticating a session
+ --
SELECT framedipaddress INTO r_address
FROM radippool
- WHERE radippool.pool_name = v_pool_name
- AND radippool.expiry_time < 'now'::timestamp(0)
- AND radippool.username = v_username
- AND radippool.callingstationid = v_callingstationid
- ORDER BY expiry_time
+ WHERE pool_name = v_pool_name
+ AND expiry_time > NOW()
+ AND username = v_username
+ AND callingstationid = v_callingstationid
LIMIT 1
FOR UPDATE SKIP LOCKED;
- IF r_address IS NOT NULL THEN
- RETURN 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.
+ --
+ -- 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()
+ ORDER BY
+ expiry_time
+ LIMIT 1
+ FOR UPDATE SKIP LOCKED;
END IF;
- SELECT framedipaddress INTO r_address
- FROM radippool
- WHERE radippool.pool_name = v_pool_name
- AND radippool.expiry_time < 'now'::timestamp(0)
- AND radippool.username = v_username
- ORDER BY expiry_time
- LIMIT 1
- FOR UPDATE SKIP LOCKED;
- IF r_address IS NOT NULL THEN
+
+ -- Return nothing if we failed to allocated an address
+ --
+ IF r_address IS NULL THEN
RETURN r_address;
END IF;
- SELECT framedipaddress INTO r_address
- FROM radippool
- WHERE radippool.pool_name = v_pool_name
- AND radippool.expiry_time < 'now'::timestamp(0)
- ORDER BY expiry_time
- LIMIT 1
- FOR UPDATE SKIP LOCKED;
+
+ -- Update the pool having allocated an IP address
+ --
+ UPDATE radippool
+ SET
+ nasipaddress = v_nasipaddress,
+ pool_key = v_pool_key,
+ callingstationid = v_callingstationid,
+ username = v_username,
+ expiry_time = NOW() + v_lease_duration * interval '1 sec'
+ WHERE framedipaddress = r_address;
+
+ -- Return the address that we allocated
RETURN r_address;
+
END
$$;
# LIMIT 1 \
# FOR UPDATE SKIP LOCKED"
-#
-# Use a stored procedure to find the address. This requires PostgreSQL >= 9.5 as
-# SKIP LOCKED is used. See `procedure.sql` in this directory for the
-# indices and stored procedure used by this query.
-#
-# 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_find = "\
-# /*NO LOAD BALANCE*/ \
-# SELECT find_previous_or_new_framedipaddress( \
-# '%{control:${pool_name}}', \
-# '%{SQL-User-Name}', \
-# '%{Calling-Station-Id}' \
-# )"
-
#
# 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.
WHERE nasipaddress = '%{NAS-IP-Address}' \
AND pool_key = '${pool_key}'"
+#
+# 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 sp_allocate_previous_or_new_framedipaddress( \
+# '%{control:${pool_name}}', \
+# '%{SQL-User-Name}', \
+# '%{Calling-Station-Id}', \
+# '%{NAS-IP-Address}', \
+# '${pool_key}', \
+# '${lease_duration}' \
+# )"
+#allocate_update = ""
+#allocate_commit = ""
+
#
# This query extends an IP address lease by "lease_duration" when an accounting
# START record arrives