The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
rlm_redis_ippool.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: fe09d366ede2ac29afa925986e19b050a548add7 $
19 * @file rlm_redis_ippool.c
20 * @brief IP Allocation module with a redis backend.
21 *
22 * @author Arran Cudbard-Bell
23 *
24 * Performs lease management using a Redis backed.
25 *
26 *
27 * Creates three types of objects:
28 * - @verbatim {<pool name>:<pool type>}:pool @endverbatim (zset) contains IP addresses
29 * with priority set by expiry time.
30 * - @verbatim {<pool name>:<pool type>}:ip:<address> @endverbatim (hash) contains four keys
31 * * range - Range identifier, used to lookup attributes associated with a range within a pool.
32 * * device - Lease owner identifier for the device which last bound this address.
33 * * gateway - Gateway of device which last bound this address.
34 * * counter - How many times this IP address has been bound.
35 * - @verbatim {<pool name>:<pool type>}:device:<client id> @endverbatim (string) contains last
36 * IP address bound by this client.
37 *
38 * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
39 * @copyright 2015 The FreeRADIUS server project
40 */
41RCSID("$Id: fe09d366ede2ac29afa925986e19b050a548add7 $")
42
43#include <freeradius-devel/server/base.h>
44#include <freeradius-devel/server/module_rlm.h>
45#include <freeradius-devel/server/modpriv.h>
46
47#include <freeradius-devel/unlang/xlat_func.h>
48
49#include <freeradius-devel/util/debug.h>
50#include <freeradius-devel/util/base16.h>
51#include <freeradius-devel/util/token.h>
52
53#include <freeradius-devel/redis/base.h>
54#include <freeradius-devel/redis/cluster_async.h>
55
56#include "redis_ippool.h"
57
58static fr_dict_t const *dict_redis;
60
63 { .out = &dict_redis, .proto = "redis" },
64 { .out = &dict_freeradius, .proto = "freeradius" },
66};
67
92
95 { .out = &attr_ippool_name, .name = "IP-Pool.Name", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
96 { .out = &attr_ippool_lease, .name = "IP-Pool.Lease", .type = FR_TYPE_TLV, .dict = &dict_freeradius },
97 { .out = &attr_ippool_lease_address, .name = "IP-Pool.Lease.Address", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
98 { .out = &attr_ippool_lease_active, .name = "IP-Pool.Lease.Active", .type = FR_TYPE_BOOL, .dict = &dict_freeradius },
99 { .out = &attr_ippool_lease_expires, .name = "IP-Pool.Lease.Expires", .type = FR_TYPE_DATE, .dict = &dict_freeradius },
100 { .out = &attr_ippool_lease_device, .name = "IP-Pool.Lease.Device", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
101 { .out = &attr_ippool_lease_gateway, .name = "IP-Pool.Lease.Gateway", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
102 { .out = &attr_ippool_lease_range, .name = "IP-Pool.Lease.Range", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
103 { .out = &attr_ippool_stats, .name = "IP-Pool.Stats", .type = FR_TYPE_TLV, .dict = &dict_freeradius },
104 { .out = &attr_ippool_stats_total, .name = "IP-Pool.Stats.Total", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
105 { .out = &attr_ippool_stats_dynamic, .name = "IP-Pool.Stats.Dynamic", .type = FR_TYPE_TLV, .dict = &dict_freeradius },
106 { .out = &attr_ippool_stats_dynamic_total, .name = "IP-Pool.Stats.Dynamic.Total", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
107 { .out = &attr_ippool_stats_dynamic_free, .name = "IP-Pool.Stats.Dynamic.Free", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
108 { .out = &attr_ippool_stats_dynamic_expire1m, .name = "IP-Pool.Stats.Dynamic.Expire-1m", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
109 { .out = &attr_ippool_stats_dynamic_expire30m, .name = "IP-Pool.Stats.Dynamic.Expire-30m", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
110 { .out = &attr_ippool_stats_dynamic_expire1h, .name = "IP-Pool.Stats.Dynamic.Expire-1h", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
111 { .out = &attr_ippool_stats_dynamic_expire1d, .name = "IP-Pool.Stats.Dynamic.Expire-1d", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
112 { .out = &attr_ippool_stats_static, .name = "IP-Pool.Stats.Static", .type = FR_TYPE_TLV, .dict = &dict_freeradius },
113 { .out = &attr_ippool_stats_static_total, .name = "IP-Pool.Stats.Static.Total", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
114 { .out = &attr_ippool_stats_static_free, .name = "IP-Pool.Stats.Static.Free", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
115 { .out = &attr_ippool_stats_static_renew1m, .name = "IP-Pool.Stats.Static.Renew-1m", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
116 { .out = &attr_ippool_stats_static_renew30m, .name = "IP-Pool.Stats.Static.Renew-30m", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
117 { .out = &attr_ippool_stats_static_renew1h, .name = "IP-Pool.Stats.Static.Renew-1h", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
118 { .out = &attr_ippool_stats_static_renew1d, .name = "IP-Pool.Stats.Static.Renew-1d", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
120};
121
122/** rlm_redis module instance
123 *
124 */
125typedef struct {
126 fr_redis_conf_t conf; //!< Connection parameters for the Redis server.
127 //!< Must be first field in this struct.
128
129 CONF_SECTION *tls_conf; //!< TLS CONF_SECTION
130
131 char const *name; //!< Instance name.
132
133 uint32_t wait_num; //!< How many slaves we want to acknowledge allocations
134 //!< or updates.
135
136 fr_time_delta_t wait_timeout; //!< How long we wait for slaves to acknowledge writing.
137
138 char *wait_cmd; //!< Preformatted redis "WAIT" command.
139 int wait_cmd_len; //!< Length of wait_cmd
140
141 bool ipv4_integer; //!< Whether IPv4 addresses should be cast to integers,
142 //!< for renew operations.
143
144 bool copy_on_update; //!< Copy the address provided by ip_address to the
145 //!< allocated_address_attr if updates are successful.
146
147 fr_coord_reg_t *coord_reg; //!< Coordinator registration.
148 fr_coord_pair_reg_t *coord_pair_reg; //!< Coord pair registration.
150
151typedef struct {
152 rlm_redis_ippool_t *inst; //!< Module instance.
153 fr_redis_ct_t *rtcluster; //!< Per thread Redis cluster.
154 fr_coord_worker_t *cw; //!< Coord-worker for fetching cluster map.
156
161
163 { FR_CONF_OFFSET("wait_num", rlm_redis_ippool_t, wait_num) },
164 { FR_CONF_OFFSET("wait_timeout", rlm_redis_ippool_t, wait_timeout), .dflt = "1s" },
165
166 { FR_CONF_DEPRECATED("ip_address", rlm_redis_ippool_t, NULL) },
167
168 { FR_CONF_DEPRECATED("reply_attr", rlm_redis_ippool_t, NULL) },
169
170 { FR_CONF_OFFSET("ipv4_integer", rlm_redis_ippool_t, ipv4_integer) },
171 { FR_CONF_OFFSET("copy_on_update", rlm_redis_ippool_t, copy_on_update), .dflt = "yes", .quote = T_BARE_WORD },
172
173 /*
174 * Split out to allow conversion to universal ippool module with
175 * minimum of config changes.
176 */
177 { FR_CONF_POINTER("redis", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = redis_config },
179};
180
181/** Call environment used when calling redis_ippool allocate method.
182 *
183 */
184typedef struct {
185 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
186
187 fr_value_box_t offer_time; //!< How long we should reserve a lease for during
188 ///< the pre-allocation stage (typically responding
189 ///< to DHCP discover).
190
191 fr_value_box_t lease_time; //!< How long an IP address should be allocated for.
192
193 fr_value_box_t association_time; //!< How log should a device be associated with an IP address.
194 ///< This allows for "sticky" addressing, where the device -> IP
195 ///< association lasts longer than the lease time.
196
197 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
198 ///< or a combination of User-Name and something
199 ///< unique to the device.
200
201 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
202 ///< Option 82 gateway. Used for bulk lease cleanups.
203
204 fr_value_box_t requested_address; //!< The address the client is requesting.
205
206 tmpl_t *allocated_address_attr; //!< Attribute to populate with allocated IP.
207
208 tmpl_t *range_attr; //!< Attribute to write the range ID to.
209
210 tmpl_t *expiry_attr; //!< Time at which the lease will expire.
212
213/** Resume context for validating WAIT replies.
214 *
215 */
216typedef struct {
218 bool fail;
220
221/** Resume context for loading scripts in response to NOSCRIPT reply.
222 */
223typedef struct {
224 fr_redis_command_set_t *cmds; //!< Command set for loading script.
225 fr_redis_async_cmd_t *cmd; //!< Async command for loading script.
226 fr_redis_async_cmd_t *eval_cmd; //!< Original EVAL async command which returned NOSCRIPT.
227 void *eval_rctx; //!< Resume context for original EVAL command.
228 module_method_t resume; //!< Resume function for EVAL command.
229 unlang_module_signal_t cancel; //!< Cancel function for EVAL command.
231
232/** Resume context for async calls to alloc script
233 *
234 */
235typedef struct {
236 redis_ippool_alloc_call_env_t *env; //!< Callenv for the current allocation
237 char *cmd_str; //!< Formatted redis command
238 fr_redis_command_set_t *cmds; //!< Command set to be run.
239 fr_redis_async_cmd_t *cmd; //!< Redis async command.
240 ippool_rcode_t ret; //!< Return code for the allocation result.
241 redis_wait_rctx_t wait_rctx; //!< WAIT resume context.
243
244/** Call environment used when calling redis_ippool update method.
245 *
246 */
247typedef struct {
248 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
249
250 fr_value_box_t lease_time; //!< How long an IP address should be allocated for.
251
252 fr_value_box_t association_time; //!< How long should a device be associated with an IP address.
253
254 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
255 ///< or a combination of User-Name and something
256 ///< unique to the device.
257
258 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
259 ///< Option 82 gateway. Used for bulk lease cleanups.
260
261 fr_value_box_t requested_address; //!< Address being updated.
262
263 tmpl_t *allocated_address_attr; //!< Attribute to populate with allocated IP.
264
265 tmpl_t *range_attr; //!< Attribute to write the range ID to.
266
267 tmpl_t *expiry_attr; //!< Time at which the lease will expire.
269
270/** Resume context for async calls to update script
271 *
272 */
273typedef struct {
274 redis_ippool_update_call_env_t *env; //!< Callenv for the current allocation
275 char *cmd_str; //!< Formatted redis command
276 fr_redis_command_set_t *cmds; //!< Command set to be run.
277 fr_redis_async_cmd_t *cmd; //!< Redis async command.
278 ippool_rcode_t ret; //!< Return code for the allocation result.
279 redis_wait_rctx_t wait_rctx; //!< WAIT resume context.
281
282/** Call environment used when calling redis_ippool release method.
283 *
284 */
285typedef struct {
286 fr_value_box_t pool_name; //!< Name of the pool we're releasing the IP addresses to.
287
288 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
289 ///< or a combination of User-Name and something
290 ///< unique to the device.
291
292 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
293 ///< Option 82 gateway. Used for bulk lease cleanups.
294
295 fr_value_box_t requested_address; //!< Address being released.
296
297 fr_value_box_t association_time; //!< How long should a device be associated with an IP address.
298
300
301/** Call environment used when calling redis_ippool show method.
302 *
303 */
304typedef struct {
305 fr_value_box_t pool_name; //!< Name of the pool we're looking up the address in.
306
307 fr_value_box_list_t requested_address; //!< Address(es) to fetch details of.
309
310/** Resume context for methods which retrieve info and produce pairs.
311 */
312typedef struct {
313 char **cmd_str; //!< Formatted redis commands.
314 fr_redis_command_set_t *cmds; //!< Command set to run.
315 fr_redis_async_cmd_t *cmd; //!< Redis async command.
316 fr_value_box_t **lookup; //!< Value boxes holding lookup keys.
317 size_t lookup_no; //!< Incremented on processing each reply.
318 fr_pair_list_t results; //!< Temporary list to hold results.
320
321/** Call environment used when calling redis_ippool stats method.
322 */
323typedef struct {
324 fr_value_box_t pool_name; //!< Name of the pool we're getting stats for.
326
327/** Resume context for async calls to update script
328 *
329 */
330typedef struct {
331 redis_ippool_release_call_env_t *env; //!< Callenv for the current allocation
332 char *cmd_str; //!< Formatted redis command
333 fr_redis_command_set_t *cmds; //!< Command set to be run.
334 fr_redis_async_cmd_t *cmd; //!< Redis async command.
335 ippool_rcode_t ret; //!< Return code for the allocation result.
336 redis_wait_rctx_t wait_rctx; //!< WAIT resume context.
338
339/** Resume context for IP pool updating xlats
340 *
341 */
342typedef struct {
343 char **cmd_str; //!< Formatted redis commands for this xlat
344 fr_redis_command_set_t *cmds; //!< Redis command set to run
345 fr_redis_async_cmd_t *cmd; //!< Redis async command.
346 uint32_t changes; //!< Number of changes reported by redis.
348
349/** Resume context for pool list module method
350 */
351typedef struct {
352 fr_redis_io_conf_t *nodes; //!< List of nodes to query.
353 uint8_t node_count; //!< How many nodes are in nodes.
354 uint8_t current_node; //!< Node number currently being queried.
355 char cursor[19]; //!< Cursor value returned in last result.
356 char *cmd_str; //!< Current preformatted Redis command.
357 fr_redis_command_set_t *cmds; //!< Command set to be run.
358 fr_redis_async_cmd_t *cmd; //!< Redis async command.
359 TALLOC_CTX *ctx; //!< Context to allocate pool names in.
360 fr_value_box_list_t pools; //!< Temporary list to store pool names.
362
363/** Call environment used when calling redis_ippool bulk release method.
364 *
365 */
366typedef struct {
367 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
368
369 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
370 ///< Option 82 gateway. Used for bulk lease cleanups.
372
375 .env = (call_env_parser_t[]){
377 redis_ippool_alloc_call_env_t, pool_name) },
381 redis_ippool_alloc_call_env_t, gateway_id ), .pair.dflt = "", .pair.dflt_quote = T_SINGLE_QUOTED_STRING },
386 .pair.dflt = "%{%{Requested-IP-Address} || %{Net.Src.IP}}", .pair.dflt_quote = T_DOUBLE_QUOTED_STRING },
389 .pair.dflt = "reply.IP-Pool.Range", .pair.dflt_quote = T_BARE_WORD },
392 }
393};
394
397 .env = (call_env_parser_t[]) {
401 .pair.dflt = "", .pair.dflt_quote = T_SINGLE_QUOTED_STRING },
405 .pair.dflt = "%{Requested-IP-Address || Net.Src.IP}", .pair.dflt_quote = T_DOUBLE_QUOTED_STRING },
408 .pair.dflt = "reply.IP-Pool.Range", .pair.dflt_quote = T_BARE_WORD },
411 }
412};
413
427
437
447
455
456#define EOL "\n"
457
458/** Lua script for allocating new leases
459 *
460 * - KEYS[1] The pool name.
461 * - ARGV[1] Wall time (seconds since epoch).
462 * - ARGV[2] Expires in (seconds).
463 * - ARGV[3] Lease owner identifier (administratively configured).
464 * - ARGV[4] Device -> IP association time (seconds).
465 * - ARGV[5] (optional) Gateway identifier.
466 * - ARGV[6] (optional) Requested address.
467 *
468 * Returns @verbatim { <rcode>[, <ip>][, <range>][, <lease time>][, <counter>] } @endverbatim
469 * - IPPOOL_RCODE_SUCCESS lease updated..
470 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
471 */
472static char lua_alloc_cmd[] =
473 "local ip" EOL /* 1 */
474 "local exists" EOL /* 2 */
475
476 "local pool_key" EOL /* 3 */
477 "local address_key" EOL /* 4 */
478 "local owner_key" EOL /* 5 */
479
480 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 6 */
481 "owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[3]" EOL /* 7 */
482
483 "local wall_time = tonumber(ARGV[1])" EOL /* 8* */
484
485 /*
486 * Check to see if the client already has a lease,
487 * and if it does return that.
488 *
489 * The additional sanity checks are to allow for the record
490 * of device/ip binding to persist for longer than the lease.
491 */
492 "exists = redis.call('GET', owner_key)" EOL /* 9 */
493 "if exists then" EOL /* 10 */
494 " local expires = tonumber(redis.call('ZSCORE', pool_key, exists))" EOL /* 11 */
495 " local static = expires >= " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 12 */
496 " local expires_in = expires - (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0) - ARGV[1]" EOL /* 13 */
497 " ip = redis.call('HMGET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. exists, 'device', 'range', 'counter', 'gateway')" EOL /* 14 */
498 " if ip and (ip[1] == ARGV[3]) then" EOL /* 15 */
499 " if expires_in < tonumber(ARGV[2]) then" EOL /* 16 */
500 " redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2] + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), exists)" EOL /* 17 */
501 " expires_in = tonumber(ARGV[2])" EOL /* 18 */
502 " if not static then" EOL /* 19 */
503 " redis.call('EXPIRE', owner_key, ARGV[4])" EOL /* 20 */
504 " end" EOL /* 21 */
505 " end" EOL /* 22 */
506
507 /*
508 * Ensure gateway is set correctly
509 */
510 " if ARGV[5] ~= ip[4] then" EOL /* 23 */
511 " redis.call('HSET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":', 'gateway', ARGV[5])" EOL /* 24 */
512 " end" EOL /* 25 */
513 " return {" STRINGIFY(_IPPOOL_RCODE_SUCCESS) ", exists, ip[2], expires_in, ip[3] }" EOL /* 26 */
514 " end" EOL /* 27 */
515 " ip = nil" EOL /* 28 */
516 "end" EOL /* 29 */
517
518 /*
519 * If there's a requested address, check if that is available i.e. not statically
520 * assigned, nor already allocated.
521 */
522 "if ARGV[6] and ARGV[6] ~= '' then" EOL /* 30 */
523 " local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[6]))" EOL /* 31 */
524 " if expires and tonumber(expires) < wall_time then" EOL /* 32 */
525 " ip = { ARGV[6] }" EOL /* 33 */
526 " end" EOL /* 34 */
527 "end" EOL /* 35 */
528
529 /*
530 * Else, get the IP address which expired the longest time ago.
531 */
532 "if not ip then" EOL /* 36 */
533 " ip = redis.call('ZREVRANGE', pool_key, -1, -1, 'WITHSCORES')" EOL /* 37 */
534 " if not ip or not ip[1] then" EOL /* 38 */
535 " return {" STRINGIFY(_IPPOOL_RCODE_POOL_EMPTY) "}" EOL /* 39 */
536 " end" EOL /* 40 */
537 " if tonumber(ip[2]) >= wall_time then" EOL /* 41 */
538 " return {" STRINGIFY(_IPPOOL_RCODE_POOL_EMPTY) "}" EOL /* 42 */
539 " end" EOL /* 43 */
540 "end" EOL /* 44 */
541 "redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2], ip[1])" EOL /* 45 */
542
543 /*
544 * Set the device/gateway keys
545 */
546 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ip[1]" EOL /* 45 */
547 "redis.call('HMSET', address_key, 'device', ARGV[3], 'gateway', ARGV[5])" EOL /* 46 */
548 "redis.call('SET', owner_key, ip[1])" EOL /* 47 */
549 "redis.call('EXPIRE', owner_key, ARGV[4])" EOL /* 48 */
550 "return { " EOL /* 49 */
551 " " STRINGIFY(_IPPOOL_RCODE_SUCCESS) "," EOL /* 50 */
552 " ip[1], " EOL /* 51 */
553 " redis.call('HGET', address_key, 'range'), " EOL /* 52 */
554 " tonumber(ARGV[2]), " EOL /* 53 */
555 " redis.call('HINCRBY', address_key, 'counter', 1)" EOL /* 54 */
556 "}" EOL; /* 55 */
558
559/** Lua script for updating leases
560 *
561 * - KEYS[1] The pool name.
562 * - ARGV[1] Wall time (seconds since epoch).
563 * - ARGV[2] Expires in (seconds).
564 * - ARGV[3] IP address to update.
565 * - ARGV[4] Lease owner identifier.
566 * - ARGV[5] Device -> IP association time (seconds).
567 * - ARGV[6] (optional) Gateway identifier.
568 *
569 * Returns @verbatim array { <rcode>[, <range>] } @endverbatim
570 * - IPPOOL_RCODE_SUCCESS lease updated..
571 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
572 * - IPPOOL_RCODE_EXPIRED lease has already expired.
573 * - IPPOOL_RCODE_DEVICE_MISMATCH lease was allocated to a different client.
574 */
575static char lua_update_cmd[] =
576 "local ret" EOL /* 1 */
577 "local found" EOL /* 2 */
578
579 "local pool_key" EOL /* 3 */
580 "local address_key" EOL /* 4 */
581 "local owner_key" EOL /* 5 */
582
583 /*
584 * We either need to know that the IP was last allocated to the
585 * same device, or that the lease on the IP has NOT expired.
586 */
587 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[3]" EOL /* 6 */
588 "found = redis.call('HMGET', address_key, 'range', 'device', 'gateway', 'counter' )" EOL /* 7 */
589 /*
590 * Range may be nil (if not used), so we use the device key
591 */
592 "if not found[2] then" EOL /* 8 */
593 " return {" STRINGIFY(_IPPOOL_RCODE_NOT_FOUND) "}" EOL /* 9 */
594 "end" EOL /* 10 */
595 "if found[2] ~= ARGV[4] then" EOL /* 11 */
596 " return {" STRINGIFY(_IPPOOL_RCODE_DEVICE_MISMATCH) ", found[2]}" EOL /* 12 */
597 "end" EOL /* 13 */
598
599 /*
600 * Update the expiry time
601 */
602 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 14 */
603 "local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[3]))" EOL /* 15 */
604 "local static = expires > " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 16 */
605 "redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2] + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), ARGV[3])" EOL /* 17 */
606
607 /*
608 * The device key should usually exist, but
609 * theoretically, if we were right on the cusp
610 * of a lease being expired, it may have been
611 * removed.
612 */
613 "owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[4]" EOL /* 18 */
614 "if not static and (redis.call('EXPIRE', owner_key, ARGV[2]) == 0) then" EOL /* 19 */
615 " redis.call('SET', owner_key, ARGV[3])" EOL /* 20 */
616 " redis.call('EXPIRE', owner_key, ARGV[5])" EOL /* 21 */
617 "end" EOL /* 22 */
618
619 /*
620 * Update the gateway address
621 */
622 "if ARGV[6] ~= found[3] then" EOL /* 23 */
623 " redis.call('HSET', address_key, 'gateway', ARGV[6])" EOL /* 24 */
624 "end" EOL /* 25 */
625 "return { " STRINGIFY(_IPPOOL_RCODE_SUCCESS) ", found[1], found[4] }"EOL; /* 26 */
627
628/** Lua script for releasing leases
629 *
630 * - KEYS[1] The pool name.
631 * - ARGV[1] Wall time (seconds since epoch).
632 * - ARGV[2] IP address to release.
633 * - ARGV[3] Client identifier.
634 * - ARGV[4] Device -> IP association time (seconds).
635 *
636 * Sets the expiry time to be NOW() - 1 to maximise time between
637 * IP address allocations.
638 *
639 * Returns @verbatim array { <rcode>[, <counter>] } @endverbatim
640 * - IPPOOL_RCODE_SUCCESS lease updated..
641 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
642 * - IPPOOL_RCODE_DEVICE_MISMATCH lease was allocated to a different client..
643 */
644static char lua_release_cmd[] =
645 "local ret" EOL /* 1 */
646 "local found" EOL /* 2 */
647
648 "local pool_key" EOL /* 3 */
649 "local address_key" EOL /* 4 */
650 "local owner_key" EOL /* 5 */
651
652 /*
653 * Check that the device releasing was the one
654 * the IP address is allocated to.
655 */
656 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[2]" EOL /* 6 */
657 "found = redis.call('HGET', address_key, 'device')" EOL /* 7 */
658 "if not found then" EOL /* 8 */
659 " return { " STRINGIFY(_IPPOOL_RCODE_NOT_FOUND) "}" EOL /* 9 */
660 "end" EOL /* 10 */
661 "if found and found ~= ARGV[3] then" EOL /* 11 */
662 " return { " STRINGIFY(_IPPOOL_RCODE_DEVICE_MISMATCH) ", found }" EOL /* 12 */
663 "end" EOL /* 13 */
664
665 /*
666 * Set expiry time to now() - 1
667 */
668 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 14 */
669 "found = tonumber(redis.call('ZSCORE', pool_key, ARGV[2]))" EOL /* 15 */
670 "local static = found > " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 16 */
671 "redis.call('ZADD', pool_key, 'XX', ARGV[1] - 1 + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), ARGV[2])" EOL /* 17 */
672
673 /*
674 * Remove the association between the device and a lease
675 * unless "sticky" addressing is in place where association_time
676 * is set, in which case use that to set the expiry of the association.
677 */
678 "if not static then" EOL /* 18 */
679 " owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[3]" EOL /* 19 */
680 " if tonumber(ARGV[4]) > 0 then" EOL /* 20 */
681 " redis.call('EXPIRE', owner_key, ARGV[4])" EOL /* 21 */
682 " else" EOL /* 22 */
683 " redis.call('DEL', owner_key)" EOL /* 24 */
684 " end" EOL /* 25 */
685 "end" EOL /* 26 */
686 "return { " EOL /* 27 */
687 " " STRINGIFY(_IPPOOL_RCODE_SUCCESS) "," EOL /* 28 */
688 " redis.call('HINCRBY', address_key, 'counter', 1) - 1" EOL /* 29 */
689 "}"; /* 30 */
691
692/** Lua script for removing a lease
693 *
694 * - KEYS[1] The pool name.
695 * - ARGV[1] IP address to remove.
696 *
697 * Removes the IP entry in the ZSET, then removes the address hash, and the device key
698 * if one exists.
699 *
700 * Will work with partially removed IP addresses (where the ZSET entry is absent but other
701 * elements weren't cleaned up).
702 *
703 * Returns
704 * - 0 if no ip addresses were removed.
705 * - 1 if an ip address was removed.
706 */
707static char lua_remove_cmd[] =
708 "local found" EOL /* 1 */
709 "local ret" EOL /* 2 */
710 "local address_key" EOL /* 3 */
711
712 "ret = redis.call('ZREM', '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"', ARGV[1])" EOL /* 4 */
713 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[1]" EOL /* 5 */
714 "found = redis.call('HGET', address_key, 'device')" EOL /* 6 */
715 "redis.call('DEL', address_key)" EOL /* 7 */
716 "if not found then" EOL /* 8 */
717 " return ret" EOL /* 9 */
718 "end" EOL /* 10 */
719
720 /*
721 * Remove the association between the device and a lease
722 */
723 "redis.call('DEL', '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. found)" EOL /* 11 */
724 "return 1" EOL; /* 12 */
725
726/** Lua script for releasing a lease
727 *
728 * - KEYS[1] The pool name.
729 * - ARGV[1] IP address to release.
730 *
731 * Removes the IP entry in the ZSET, then removes the address hash, and the device key
732 * if one exists.
733 *
734 * Will do nothing if the lease is not found in the ZSET.
735 *
736 * Returns
737 * - 0 if no ip addresses were removed.
738 * - 1 if an ip address was removed.
739 */
740static char lua_release_xlat_cmd[] =
741 "local found" EOL /* 1 */
742 "local ret" EOL /* 2 */
743
744 /*
745 * Set expiry time to 0
746 */
747 "ret = redis.call('ZADD', '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"', 'XX', 'CH', 0, ARGV[1])" EOL /* 3 */
748 "if ret == 0 then" EOL /* 4 */
749 " return 0" EOL /* 5 */
750 "end" EOL /* 6 */
751 "found = redis.call('HGET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":'"
752 " .. ARGV[1], 'device')" EOL /* 7 */
753 "if not found then" EOL /* 8 */
754 " return ret" EOL /* 9 */
755 "end" EOL /* 10 */
756
757 /*
758 * Remove the association between the device and a lease
759 */
760 "redis.call('DEL', '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. found)" EOL /* 11 */
761 "return 1"; /* 12 */
762
763/** Lua script for assigning a static lease
764 *
765 * - KEYS[1] The pool name.
766 * - ARGV[1] THE ip address to create a static assignment for.
767 * - ARGV[2] The owner to assign the static lease to.
768 * - ARGV[3] The range identifier.
769 * - ARGV[4] Wall time (seconds since epoch)
770 *
771 * Checks whether the IP already has a static assignment, and
772 * whether the owner is already associated with a different IP.
773 *
774 * If check pass, sets the static flag on the IP entry in the ZSET and
775 * creates the association between the IP and the owner.
776 *
777 * Returns
778 * - 0 if no assignment is made.
779 * - 1 if the IP assignment is made.
780 */
781static char lua_assign_cmd[] =
782 "local pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 1 */
783 "local owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[2]" EOL /* 2 */
784 "local ip_key = '{' .. KEYS[1]..'}:"IPPOOL_ADDRESS_KEY":' .. ARGV[1]" EOL /* 3 */
785
786 /*
787 * Check the address doesn't already have a static assignment.
788 */
789 "local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[1]))" EOL /* 4 */
790 "if expires and expires >= " STRINGIFY(IPPOOL_STATIC_BIT) " then" EOL /* 5 */
791 " return 0" EOL /* 6 */
792 "end" EOL /* 7 */
793
794 /*
795 * Check current assignment for device.
796 */
797 "local found = redis.call('GET', owner_key)" EOL /* 8 */
798 "if found and found ~= ARGV[1] then" EOL /* 9 */
799 " return 0" EOL /* 10 */
800 "end" EOL /* 11 */
801
802 /*
803 * If expires is in the future, check it is not
804 * another owner.
805 */
806 "if expires and expires > tonumber(ARGV[4]) then" EOL /* 12 */
807 " found = redis.call('HGET', ip_key, 'device')" /* 13 */
808 " if found and found ~= ARGV[2] then" EOL /* 14 */
809 " return 0" EOL /* 15 */
810 " end" EOL /* 16 */
811 "end" EOL /* 17 */
812
813 /*
814 * All checks passed - set the assignment.
815 */
816 "expires = (expires or 0) + " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 18 */
817 "redis.call('ZADD', pool_key, 'CH', expires, ARGV[1])" EOL /* 19 */
818 "redis.call('SET', owner_key, ARGV[1])" EOL /* 20 */
819 "redis.call('HSET', ip_key, 'device', ARGV[2], 'counter', 0)" EOL /* 21 */
820 "if ARGV[3] then" EOL /* 22 */
821 " redis.call('HSET', ip_key, 'range', ARGV[3])" EOL /* 23 */
822 "end" EOL /* 24 */
823 "return 1"; /* 25 */
824
825/** Lua script for un-assigning a static lease
826 *
827 * - KEYS[1] The pool name.
828 * - ARGV[1] IP address to remove static lease from.
829 * - ARGV[2] The owner the static lease should be removed from.
830 * - ARGV[3] Wall time (seconds since epoch).
831 *
832 * Removes the static flag from the IP entry in the ZSET, then, depending on the remaining time
833 * determined by the ZSCORE removes the address hash, and the device key.
834 *
835 * Will do nothing if the static assignment does not exist or the IP and device do not match.
836 *
837 * Returns
838 * - 0 if no ip addresses were unassigned.
839 * - 1 if an ip address was unassigned.
840 */
841static char lua_unassign_cmd[] =
842 "local found" EOL /* 1 */
843 "local pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 2 */
844 "local owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[2]" EOL /* 3 */
845
846 /*
847 * Check that the device hash exists and points at the correct IP
848 */
849 "found = redis.call('GET', owner_key)" EOL /* 4 */
850 "if not found or found ~= ARGV[1] then" EOL /* 5 */
851 " return 0" EOL /* 6 */
852 "end" EOL /* 7 */
853
854 /*
855 * Check the assignment is actually static
856 */
857 "local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[1]))" EOL /* 8 */
858 "local static = expires >= " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 9 */
859 "if not static then" EOL /* 10 */
860 " return 0" EOL /* 11 */
861 "end" EOL /* 12 */
862
863 /*
864 * Remove static bit from ZSCORE
865 */
866 "expires = expires - " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 13 */
867 "redis.call('ZADD', pool_key, 'XX', expires, ARGV[1])" EOL /* 14 */
868
869 /*
870 * If the lease still has time left, set an expiry on the device key.
871 * otherwise delete it.
872 */
873 "if expires > tonumber(ARGV[3]) then" EOL /* 15 */
874 " redis.call('EXPIRE', owner_key, expires - tonumber(ARGV[3]))" EOL /* 16 */
875 "else" EOL /* 17 */
876 " redis.call('DEL', owner_key)" EOL /* 18 */
877 "end" EOL /* 19 */
878 "return 1"; /* 20 */
879
881{
882 if (bits >= 32) return 0xffffffff;
883 return (1U << bits) - 1;
884}
885
886/** Check the requisite number of slaves replicated the lease info
887 *
888 * @param request The current request.
889 * @param cmd The Redis command triggering this callback
890 * @param reply we got from the server.
891 * @param rctx WAIT resume context.
892 */
893static inline void ippool_wait_check(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
894{
895 redis_wait_rctx_t *wait_rctx = rctx;
896 if (!wait_rctx->wait_num) return;
897
898 if (reply->type != REDIS_REPLY_INTEGER) {
899 REDEBUG("WAIT result is wrong type, expected integer got %s",
900 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
901 wait_rctx->fail = true;
902 return;
903 }
904 if (reply->integer < wait_rctx->wait_num) {
905 REDEBUG("Too few slaves acknowledged allocation, needed %i, got %lli",
906 wait_rctx->wait_num, reply->integer);
907 wait_rctx->fail = true;
908 return;
909 }
910}
911
912static void ippool_action_print(request_t *request, ippool_action_t action,
913 fr_log_lvl_t lvl,
914 fr_value_box_t const *key_prefix,
915 fr_value_box_t const *ip,
916 fr_value_box_t const *owner,
917 fr_value_box_t const *gateway_id,
918 uint32_t expires)
919{
920 char *device_str = NULL, *gateway_str = NULL;
921
922 if (gateway_id && gateway_id->vb_length > 0) gateway_str = fr_asprint(request, gateway_id->vb_strvalue,
923 gateway_id->vb_length, '"');
924 if (owner && owner->vb_length > 0) device_str = fr_asprint(request, owner->vb_strvalue, owner->vb_length, '"');
925
926 switch (action) {
928 RDEBUGX(lvl, "Allocating lease from pool \"%pV\"%s%s%s%s%s%s, expires in %us",
929 key_prefix,
930 device_str ? ", to \"" : "", device_str ? device_str : "",
931 device_str ? "\"" : "",
932 gateway_str ? ", on \"" : "", gateway_str ? gateway_str : "",
933 gateway_str ? "\"" : "",
934 expires);
935 break;
936
938 RDEBUGX(lvl, "Updating %pV in pool \"%pV\"%s%s%s%s%s%s, expires in %us",
939 ip, key_prefix,
940 device_str ? ", device \"" : "", device_str ? device_str : "",
941 device_str ? "\"" : "",
942 gateway_str ? ", gateway \"" : "", gateway_str ? gateway_str : "",
943 gateway_str ? "\"" : "",
944 expires);
945 break;
946
948 RDEBUGX(lvl, "Releasing %pV%s%s%s to pool \"%pV\"",
949 ip,
950 device_str ? " leased by \"" : "", device_str ? device_str : "",
951 device_str ? "\"" : "",
952 key_prefix);
953 break;
954
955 default:
956 break;
957 }
958
959 /*
960 * Ordering is important, needs to be LIFO
961 * for proper talloc pool reuse.
962 */
963 talloc_free(device_str);
964 talloc_free(gateway_str);
965}
966
967/** Enqueue a script to run against a redis cluster
968 *
969 * @param[in] ctx To allocate redis command set.
970 * @param[out] out_cmds Where to write a pointer to the command set.
971 * @param[out] out_cmd Where to write a pointer to the async command
972 * @param[in] request The current request.
973 * @param[in] thread Redis ippool thread
974 * @param[in] key to use to determine the cluster node.
975 * @param[in] key_len length of the key.
976 * @param[in] cmd Pre-formatted redis command to call script
977 * @param[in] cmd_len length of the pre-formatted command.
978 * @param[in] complete Callback to run when `cmd` is completed
979 * @param[in] resume Resume function to run after script completed.
980 * @param[in] rctx to pass to `complete` and `resume`.
981 * @return #unlang_action_t
982 */
984 fr_redis_async_cmd_t **out_cmd, request_t *request,
985 rlm_redis_ippool_thread_t *thread, uint8_t const *key, size_t key_len,
986 char const *cmd, int cmd_len, fr_redis_command_complete_t complete,
987 module_method_t resume, unlang_module_signal_t cancel, void *rctx,
988 redis_wait_rctx_t *wait_rctx)
989{
992
993 MEM(cmds = fr_redis_command_set_alloc(ctx, request, NULL, NULL, NULL, false));
994
995 if (fr_redis_command_preformatted_add(cmds, cmd, cmd_len, complete, rctx) != FR_REDIS_PIPELINE_OK) {
996 error:
997 talloc_free(cmds);
998 return UNLANG_ACTION_FAIL;
999 };
1000
1001 if (thread->inst->wait_cmd) {
1003 ippool_wait_check, wait_rctx) != FR_REDIS_PIPELINE_OK) goto error;
1004 }
1005
1006 *out_cmd = fr_redis_async_cmd_start(ctx, request, &ret, thread->rtcluster, key, key_len, cmds, false, NULL);
1007
1008 REDIS_ASYNC_START_RCODE_PROCESS(ret, thread->rtcluster, thread->cw, thread->inst->coord_pair_reg,
1009 "Failed enqueuing Redis command", UNLANG_ACTION_FAIL)
1010
1011 if (out_cmds) *out_cmds = cmds;
1012 return unlang_module_yield(request, resume, cancel, ~FR_SIGNAL_CANCEL, rctx);
1013}
1014
1015/** Callback to process results from allocation script
1016 */
1017static void redis_ippool_allocate_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
1018{
1019 redis_ippool_alloc_rctx_t *alloc_rctx = talloc_get_type_abort(rctx, redis_ippool_alloc_rctx_t);
1020 redis_ippool_alloc_call_env_t *env = alloc_rctx->env;
1021
1023
1024 if (reply->type != REDIS_REPLY_ARRAY) {
1025 REDEBUG("Expected result to be array got \"%s\"",
1026 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1027 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1028 return;
1029 }
1030
1031 if (reply->elements == 0) {
1032 REDEBUG("Got empty result array");
1033 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1034 return;
1035 }
1036
1037 /*
1038 * Process return code
1039 */
1040 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
1041 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
1042 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1043 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1044 return;
1045 }
1046 alloc_rctx->ret = reply->element[0]->integer;
1047 if (alloc_rctx->ret < 0) return;
1048
1049 /*
1050 * Process IP address
1051 */
1052 if (reply->elements > 1) {
1053 tmpl_t ip_rhs;
1054 map_t ip_map = {
1056 .op = T_OP_SET,
1057 .rhs = &ip_rhs
1058 };
1059
1060 tmpl_init_shallow(&ip_rhs, TMPL_TYPE_DATA, T_BARE_WORD, "", 0, NULL);
1061 switch (reply->element[1]->type) {
1062 /*
1063 * Destination attribute may not be IPv4, in which case
1064 * we want to pre-convert the integer value to an IPv4
1065 * address before casting it once more to the type of
1066 * the destination attribute.
1067 */
1068 case REDIS_REPLY_INTEGER:
1069 {
1070 if (tmpl_attr_tail_da(ip_map.lhs)->type != FR_TYPE_IPV4_ADDR) {
1071 fr_value_box_t tmp;
1072
1073 fr_value_box(&tmp, (uint32_t)ntohl((uint32_t)reply->element[1]->integer), true);
1075 NULL, &tmp)) {
1076 RPEDEBUG("Failed converting integer to IPv4 address");
1077 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1078 return;
1079 }
1080 } else {
1081 fr_value_box(&ip_map.rhs->data.literal,
1082 (uint32_t)ntohl((uint32_t)reply->element[1]->integer), true);
1083 }
1084 }
1085 goto do_ip_map;
1086
1087 case REDIS_REPLY_STRING:
1088 fr_value_box_bstrndup_shallow(&ip_map.rhs->data.literal,
1089 NULL, reply->element[1]->str, reply->element[1]->len, false);
1090 do_ip_map:
1091 if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) {
1092 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1093 return;
1094 }
1095 break;
1096
1097 default:
1098 REDEBUG("Server returned unexpected type \"%s\" for IP element (result[1])",
1099 fr_table_str_by_value(redis_reply_types, reply->element[1]->type, "<UNKNOWN>"));
1100 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1101 return;
1102 }
1103 }
1104
1105 /*
1106 * Process Range identifier
1107 */
1108 if (reply->elements > 2) {
1109 switch (reply->element[2]->type) {
1110 /*
1111 * Add range ID to request
1112 */
1113 case REDIS_REPLY_STRING:
1114 {
1115 tmpl_t range_rhs;
1116 map_t range_map = {
1117 .lhs = env->range_attr,
1118 .op = T_OP_SET,
1119 .rhs = &range_rhs
1120 };
1121
1122 tmpl_init_shallow(&range_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
1123 fr_value_box_bstrndup_shallow(&range_map.rhs->data.literal,
1124 NULL, reply->element[2]->str, reply->element[2]->len, true);
1125 if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) {
1126 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1127 return;
1128 }
1129 }
1130 break;
1131
1132 case REDIS_REPLY_NIL:
1133 break;
1134
1135 default:
1136 REDEBUG("Server returned unexpected type \"%s\" for range element (result[2])",
1137 fr_table_str_by_value(redis_reply_types, reply->element[2]->type, "<UNKNOWN>"));
1138 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1139 return;
1140 }
1141 }
1142
1143 /*
1144 * Process Expiry time
1145 */
1146 if (env->expiry_attr && (reply->elements > 3)) {
1147 tmpl_t expiry_rhs;
1148 map_t expiry_map = {
1149 .lhs = env->expiry_attr,
1150 .op = T_OP_SET,
1151 .rhs = &expiry_rhs
1152 };
1153
1154 tmpl_init_shallow(&expiry_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
1155 if (reply->element[3]->type != REDIS_REPLY_INTEGER) {
1156 REDEBUG("Server returned unexpected type \"%s\" for expiry element (result[3])",
1157 fr_table_str_by_value(redis_reply_types, reply->element[3]->type, "<UNKNOWN>"));
1158 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1159 return;
1160 }
1161
1162 fr_value_box(&expiry_map.rhs->data.literal, (uint32_t)reply->element[3]->integer, true);
1163 if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) {
1164 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1165 return;
1166 }
1167 }
1168}
1169
1170/** Callback to process results from allocation script
1171 */
1172static void redis_ippool_update_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
1173{
1174 redis_ippool_update_rctx_t *update_rctx = talloc_get_type_abort(rctx, redis_ippool_update_rctx_t);
1175 redis_ippool_update_call_env_t *env = update_rctx->env;
1176
1178
1179 if (reply->type != REDIS_REPLY_ARRAY) {
1180 REDEBUG("Expected result to be array got \"%s\"",
1181 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1182 update_rctx->ret = IPPOOL_RCODE_FAIL;
1183 return;
1184 }
1185
1186 if (reply->elements == 0) {
1187 REDEBUG("Got empty result array");
1188 update_rctx->ret = IPPOOL_RCODE_FAIL;
1189 return;
1190 }
1191
1192 /*
1193 * Process return code
1194 */
1195 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
1196 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
1197 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1198 update_rctx->ret = IPPOOL_RCODE_FAIL;
1199 return;
1200 }
1201 update_rctx->ret = reply->element[0]->integer;
1202 if (update_rctx->ret < 0) return;
1203
1204 /*
1205 * Process Range identifier
1206 */
1207 if (reply->elements > 1) {
1208 switch (reply->element[1]->type) {
1209 /*
1210 * Add range ID to request
1211 */
1212 case REDIS_REPLY_STRING:
1213 {
1214 tmpl_t range_rhs;
1215 map_t range_map = { .lhs = env->range_attr, .op = T_OP_SET, .rhs = &range_rhs };
1216
1217 tmpl_init_shallow(&range_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
1218 fr_value_box_bstrndup_shallow(&range_map.rhs->data.literal, NULL,
1219 reply->element[1]->str, reply->element[1]->len, true);
1220 if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) {
1221 update_rctx->ret = IPPOOL_RCODE_FAIL;
1222 return;
1223 }
1224 }
1225 break;
1226
1227 case REDIS_REPLY_NIL:
1228 break;
1229
1230 default:
1231 REDEBUG("Server returned unexpected type \"%s\" for range element (result[1])",
1232 fr_table_str_by_value(redis_reply_types, reply->element[0]->type, "<UNKNOWN>"));
1233 update_rctx->ret = IPPOOL_RCODE_FAIL;
1234 return;
1235 }
1236 }
1237
1238 /*
1239 * Copy expiry time to expires attribute (if set)
1240 */
1241 if (env->expiry_attr) {
1242 tmpl_t expiry_rhs;
1243 map_t expiry_map = {
1244 .lhs = env->expiry_attr,
1245 .op = T_OP_SET,
1246 .rhs = &expiry_rhs
1247 };
1248
1249
1250 tmpl_init_shallow(&expiry_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
1251
1252 fr_value_box(&expiry_map.rhs->data.literal, env->lease_time.vb_uint32, false);
1253 if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) {
1254 update_rctx->ret = IPPOOL_RCODE_FAIL;
1255 return;
1256 }
1257 }
1258}
1259
1260/** Callback to process results from release script
1261 */
1262static void redis_ippool_release_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply,
1263 void *rctx)
1264{
1265 redis_ippool_release_rctx_t *release_rctx = talloc_get_type_abort(rctx, redis_ippool_release_rctx_t);
1266
1268
1269 if (reply->type != REDIS_REPLY_ARRAY) {
1270 REDEBUG("Expected result to be array got \"%s\"",
1271 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1272 release_rctx->ret = IPPOOL_RCODE_FAIL;
1273 return;
1274 }
1275
1276 if (reply->elements == 0) {
1277 REDEBUG("Got empty result array");
1278 release_rctx->ret = IPPOOL_RCODE_FAIL;
1279 return;
1280 }
1281
1282 /*
1283 * Process return code
1284 */
1285 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
1286 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
1287 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1288 release_rctx->ret = IPPOOL_RCODE_FAIL;
1289 return;
1290 }
1291 release_rctx->ret = reply->element[0]->integer;
1292}
1293
1294#define CHECK_POOL_NAME \
1295 if (env->pool_name.vb_length > IPPOOL_MAX_KEY_PREFIX_SIZE) { \
1296 REDEBUG("Pool name too long. Expected %u bytes, got %ld bytes", \
1297 IPPOOL_MAX_KEY_PREFIX_SIZE, env->pool_name.vb_length); \
1298 RETURN_UNLANG_FAIL; \
1299 } \
1300 if (env->pool_name.vb_length == 0) { \
1301 RDEBUG2("Empty pool name. Doing nothing"); \
1302 RETURN_UNLANG_NOOP; \
1303 }
1304
1305static void redis_ippool_load_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
1306{
1307 redis_ippool_load_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_load_rctx_t);
1308
1309 RDEBUG2("Forcibly cancelling Redis script load command");
1310
1312}
1313
1315 request_t *request)
1316{
1317 redis_ippool_load_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_load_rctx_t);
1318
1319 switch(fr_redis_command_set_rcode(rctx->cmds)) {
1321 break;
1322
1323 default:
1324 RERROR("Failed loading Redis lua script");
1326 }
1327
1329
1330 return unlang_module_yield(request, rctx->resume, rctx->cancel, ~FR_SIGNAL_CANCEL, rctx->eval_rctx);
1331}
1332
1334 redisReply *reply, UNUSED void *rctx)
1335{
1337
1338 if (reply->type != REDIS_REPLY_STRING) {
1339 ERROR("Unexpected reply type after loading function");
1340 return;
1341 }
1342 DEBUG2("Loaded lua function with hash \"%s\" onto node", reply->str);
1343}
1344
1345#define REDIS_IPPOOL_SCRIPT_LOAD(_cmds, _script, _script_len, _ret) do { \
1346 char const **argv; \
1347 size_t *argv_len; \
1348 MEM(argv = talloc_array(_cmds, char const *, 3)); \
1349 MEM(argv_len = talloc_array(_cmds, size_t, 3)); \
1350 argv[0] = "SCRIPT"; \
1351 argv[1] = "LOAD"; \
1352 argv[2] = _script; \
1353 argv_len[0] = (sizeof("SCRIPT") - 1); \
1354 argv_len[1] = (sizeof("LOAD") - 1); \
1355 argv_len[2] = _script_len; \
1356 if (fr_redis_command_argv_add(_cmds, 3, argv, argv_len, lua_script_load_results, NULL) != FR_REDIS_PIPELINE_OK) { \
1357 talloc_free(_cmds); \
1358 return _ret; \
1359 } \
1360} while (0)
1361
1362/** Check the return code from an async redis command
1363 *
1364 * Redirecting to another node in response to ASK / MOVED codes.
1365 */
1367 fr_redis_async_cmd_t *cmd, module_ctx_t const *mctx,
1369 char const *script, size_t script_len)
1370{
1371 switch (fr_redis_command_set_rcode(cmds)) {
1373 {
1374 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
1376 fr_redis_command_set_t *load_cmds;
1378
1379 /*
1380 * Script loading is done following trunk connection.
1381 * A NO-SCRIPT response means the command was enqueued before the script load or
1382 * the script has been cleared from the node.
1383 */
1385 MEM(load_cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
1386 REDIS_IPPOOL_SCRIPT_LOAD(load_cmds, script, script_len, UNLANG_ACTION_FAIL);
1387 *rctx = (redis_ippool_load_rctx_t) {
1388 .cmds = load_cmds,
1389 .eval_cmd = cmd,
1390 .resume = resume,
1391 .cancel = cancel,
1392 .eval_rctx = mctx->rctx
1393 };
1394
1395 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &ret, thread->rtcluster, NULL, 0, load_cmds,
1396 false, fr_redis_async_cmd_node(cmd));
1398 ~FR_SIGNAL_CANCEL, rctx);
1399 }
1400
1402 {
1404 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
1405
1406 if (inst->conf.use_cluster_map) fr_redis_ct_map_get(thread->rtcluster, thread->cw,
1407 inst->coord_pair_reg, false);
1408 }
1410
1413 return unlang_module_yield(request, resume, cancel, ~FR_SIGNAL_CANCEL, mctx->rctx);
1414
1417 return unlang_module_yield(request, resume, cancel, ~FR_SIGNAL_CANCEL, mctx->rctx);
1418
1419 default:
1420 break;
1421 }
1423}
1424
1425static void mod_alloc_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
1426{
1427 redis_ippool_alloc_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_alloc_rctx_t);
1428
1429 RDEBUG2("Forcibly cancelling Redis alloc command");
1430
1432}
1433
1435 request_t *request)
1436{
1437 redis_ippool_alloc_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_alloc_rctx_t);
1438
1439 if (redis_ippool_rcode_check(request, rctx->cmds, rctx->cmd, mctx, mod_alloc_resume,
1442
1443 switch (rctx->ret) {
1446
1447 RDEBUG2("IP address lease allocated");
1449
1451 RWDEBUG("Pool contains no free addresses");
1453
1454 default:
1455 RPERROR("Allocating IP address failed");
1457 }
1458}
1459
1461{
1462 if (!rctx->cmd_str) return 0;
1463 redisFreeCommand(rctx->cmd_str);
1464 return 0;
1465}
1466
1467static unlang_action_t CC_HINT(nonnull) mod_alloc(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
1468{
1470 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
1471 redis_ippool_alloc_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_alloc_call_env_t);
1472 uint32_t lease_time;
1473 struct timeval now;
1474 uint32_t assoc_time;
1476 int cmd_len;
1477
1478 fr_assert(env->pool_name.vb_length > 0);
1479 fr_assert(env->owner.vb_length > 0);
1480
1482
1483 /*
1484 * If offer_time is defined, it will be FR_TYPE_UINT32.
1485 * Fall back to lease_time otherwise.
1486 */
1487 lease_time = (env->offer_time.type == FR_TYPE_UINT32) ?
1488 env->offer_time.vb_uint32 : env->lease_time.vb_uint32;
1490 &env->owner, &env->gateway_id, lease_time);
1491
1493 rctx->env = env;
1494 rctx->ret = IPPOOL_RCODE_FAIL;
1495 rctx->wait_rctx.wait_num = inst->wait_num;
1496 talloc_set_destructor(rctx, _redis_ippool_alloc_ctx_free);
1497
1498 now = fr_time_to_timeval(fr_time());
1499
1500 assoc_time = (env->association_time.type == FR_TYPE_UINT32) &&
1501 (env->association_time.vb_uint32 > lease_time) ? env->association_time.vb_uint32 : lease_time;
1502
1503 if ((env->requested_address.datum.ip.af == AF_INET) && inst->ipv4_integer) {
1504 cmd_len = redisFormatCommand(&rctx->cmd_str, "EVALSHA %s 1 %b %u %u %b %u %b %u",
1506 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1507 (unsigned int)now.tv_sec, lease_time,
1508 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length,
1509 assoc_time,
1510 (uint8_t const *)env->gateway_id.vb_strvalue, env->gateway_id.vb_length,
1511 htonl(env->requested_address.datum.ip.addr.v4.s_addr));
1512 if (cmd_len < 0) {
1513 format_error:
1514 RERROR("Failed formatting redis command");
1515 return UNLANG_ACTION_FAIL;
1516 }
1517 } else {
1518 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
1519 if (env->requested_address.type == FR_TYPE_COMBO_IP_ADDR) {
1520 IPPOOL_SPRINT_IP(ip_buff, &env->requested_address.datum.ip, env->requested_address.datum.ip.prefix);
1521 } else {
1522 ip_buff[0] = '\0';
1523 }
1524
1525 cmd_len = redisFormatCommand(&rctx->cmd_str, "EVALSHA %s 1 %b %u %u %b %u %b %s",
1527 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1528 (unsigned int)now.tv_sec, lease_time,
1529 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length,
1530 assoc_time,
1531 (uint8_t const *)env->gateway_id.vb_strvalue, env->gateway_id.vb_length,
1532 ip_buff);
1533 if (cmd_len < 0) goto format_error;
1534 }
1535
1536 return ippool_script_enqueue(rctx, &rctx->cmds, &rctx->cmd, request, thread,
1537 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1539 mod_alloc_cancel, rctx, &rctx->wait_rctx);
1540}
1541
1542static void mod_update_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
1543{
1544 redis_ippool_update_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_update_rctx_t);
1545
1546 RDEBUG2("Forcibly cancelling Redis update command");
1547
1549}
1550
1552 request_t *request)
1553{
1555 redis_ippool_update_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_update_rctx_t);
1556 redis_ippool_update_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_update_call_env_t);
1557
1558 if (redis_ippool_rcode_check(request, rctx->cmds, rctx->cmd, mctx, mod_update_resume,
1561
1562 switch (rctx->ret) {
1565
1566 RDEBUG2("Requested IP address' \"%pV\" lease updated", &env->requested_address);
1567
1568 /*
1569 * Copy over the input IP address to the reply attribute
1570 */
1571 if (inst->copy_on_update) {
1572 tmpl_t ip_rhs = {
1573 .name = "",
1574 .type = TMPL_TYPE_DATA,
1575 .quote = T_BARE_WORD,
1576 };
1577 map_t ip_map = {
1579 .op = T_OP_SET,
1580 .rhs = &ip_rhs
1581 };
1582
1583 if (unlikely(fr_value_box_copy(NULL, &ip_rhs.data.literal, &env->requested_address) < 0)) {
1584 RPEDEBUG("Failed copying IP address to reply attribute");
1586 }
1587
1588 if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) RETURN_UNLANG_FAIL;
1589 }
1591
1592 /*
1593 * It's useful to be able to identify the 'not found' case
1594 * as we can relay to a server where the IP address might
1595 * be found. This extremely useful for migrations.
1596 */
1598 REDEBUG("Requested IP address \"%pV\" is not a member of the specified pool",
1599 &env->requested_address);
1601
1603 REDEBUG("Requested IP address' \"%pV\" lease already expired at time of renewal",
1604 &env->requested_address);
1606
1608 REDEBUG("Requested IP address' \"%pV\" lease allocated to another device",
1609 &env->requested_address);
1611
1612 default:
1613 RPERROR("Failed updating IP address");
1615 }
1616}
1617
1619{
1620 if (!rctx->cmd_str) return 0;
1621 redisFreeCommand(rctx->cmd_str);
1622 return 0;
1623}
1624
1625static unlang_action_t CC_HINT(nonnull) mod_update(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
1626{
1628 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
1629 redis_ippool_update_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_update_call_env_t);
1630 struct timeval now;
1631 uint32_t assoc_time, expires;
1632 fr_ipaddr_t *ip = &env->requested_address.datum.ip;
1633 int cmd_len;
1635
1637
1639 &env->requested_address, &env->owner, &env->gateway_id, env->lease_time.vb_uint32);
1640
1642 rctx->env = env;
1643 rctx->ret = IPPOOL_RCODE_FAIL;
1644 rctx->wait_rctx.wait_num = inst->wait_num;
1645 talloc_set_destructor(rctx, _redis_ippool_update_rctx_free);
1646
1647 now = fr_time_to_timeval(fr_time());
1648 expires = env->lease_time.vb_uint32;
1649 assoc_time = (env->association_time.type == FR_TYPE_UINT32) &&
1650 (env->association_time.vb_uint32 > expires) ? env->association_time.vb_uint32 : expires;
1651
1652 if ((ip->af == AF_INET) && inst->ipv4_integer) {
1653 cmd_len = redisFormatCommand(&rctx->cmd_str, "EVALSHA %s 1 %b %u %u %u %b %u %b", lua_update_digest,
1654 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1655 (unsigned int)now.tv_sec, expires, htonl(ip->addr.v4.s_addr),
1656 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length, assoc_time,
1657 (uint8_t const *)env->gateway_id.vb_strvalue, env->gateway_id.vb_length);
1658 if (cmd_len < 0) {
1659 format_error:
1660 RERROR("Failed formatting redis command");
1661 return UNLANG_ACTION_FAIL;
1662 }
1663 } else {
1664 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
1665
1666 IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix);
1667 cmd_len = redisFormatCommand(&rctx->cmd_str, "EVALSHA %s 1 %b %u %u %s %b %u %b", lua_update_digest,
1668 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1669 (unsigned int)now.tv_sec, expires, ip_buff,
1670 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length, assoc_time,
1671 (uint8_t const *)env->gateway_id.vb_strvalue, env->gateway_id.vb_length);
1672 if (cmd_len < 0) goto format_error;
1673 }
1674
1675 return ippool_script_enqueue(rctx, &rctx->cmds, &rctx->cmd, request, thread,
1676 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1678 mod_update_cancel, rctx, &rctx->wait_rctx);
1679}
1680
1681static void mod_release_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
1682{
1683 redis_ippool_release_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_release_rctx_t);
1684
1685 RDEBUG2("Forcibly cancelling Redis release command");
1686
1688}
1689
1691 request_t *request)
1692{
1693 redis_ippool_release_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_release_rctx_t);
1695
1696 if (redis_ippool_rcode_check(request, rctx->cmds, rctx->cmd, mctx, mod_release_resume,
1699
1700 switch (rctx->ret) {
1703
1704 RDEBUG2("IP address \"%pV\" released", &env->requested_address);
1706
1707 /*
1708 * It's useful to be able to identify the 'not found' case
1709 * as we can relay to a server where the IP address might
1710 * be found. This extremely useful for migrations.
1711 */
1713 REDEBUG("Requested IP address \"%pV\" is not a member of the specified pool",
1714 &env->requested_address);
1716
1718 REDEBUG("Requested IP address' \"%pV\" lease allocated to another device",
1719 &env->requested_address);
1721
1722 default:
1723 RPERROR("Failed releasing IP address");
1725 }
1726}
1727
1729{
1730 if (!rctx->cmd_str) return 0;
1731 redisFreeCommand(rctx->cmd_str);
1732 return 0;
1733}
1734
1735static unlang_action_t CC_HINT(nonnull) mod_release(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
1736{
1738 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
1740 struct timeval now;
1741 fr_ipaddr_t *ip = &env->requested_address.datum.ip;
1743 int cmd_len;
1744
1746
1748 &env->requested_address, &env->owner, &env->gateway_id, 0);
1749
1751 rctx->wait_rctx.wait_num = inst->wait_num;
1752 talloc_set_destructor(rctx, _redis_ippool_release_rctx_free);
1753
1754 now = fr_time_to_timeval(fr_time());
1755
1756 if ((ip->af == AF_INET) && inst->ipv4_integer) {
1757 cmd_len = redisFormatCommand(&rctx->cmd_str, "EVALSHA %s 1 %b %u %u %b %u", lua_release_digest,
1758 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1759 (unsigned int)now.tv_sec, htonl(ip->addr.v4.s_addr),
1760 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length,
1761 env->association_time.vb_uint32);
1762 if (cmd_len < 0) {
1763 format_error:
1764 RERROR("Failed formatting redis command");
1765 return UNLANG_ACTION_FAIL;
1766 }
1767 } else {
1768 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
1769
1770 IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix);
1771 cmd_len = redisFormatCommand(&rctx->cmd_str, "EVALSHA %s 1 %b %u %s %b %u", lua_release_digest,
1772 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1773 (unsigned int)now.tv_sec, ip_buff,
1774 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length,
1775 env->association_time.vb_uint32);
1776 if (cmd_len < 0) goto format_error;
1777 }
1778
1779 return ippool_script_enqueue(rctx, &rctx->cmds, &rctx->cmd, request, thread,
1780 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1782 mod_release_cancel, rctx, &rctx->wait_rctx);
1783}
1784
1786 request_t *request)
1787{
1788 RDEBUG2("Bulk release not yet implemented");
1790}
1791
1792/** Parse the results from Redis SCAN command used to list pools
1793 */
1794static void mod_pools_list_result(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
1795{
1796 redis_pools_list_rctx_t *pools_rctx = talloc_get_type_abort(rctx, redis_pools_list_rctx_t);
1797 size_t k, len;
1798 char const *p;
1799
1801
1802 if (reply->type != REDIS_REPLY_ARRAY) {
1803 ERROR("Failed retrieving result, expected array got %s",
1804 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1805 return;
1806 }
1807
1808 if (reply->elements != 2) {
1809 ERROR("Failed retrieving result, expected array with two elements, got %zu elements",
1810 reply->elements);
1811 return;
1812 }
1813
1814 if (reply->element[0]->type != REDIS_REPLY_STRING) {
1815 ERROR("Failed retrieving result, expected string got %s",
1816 fr_table_str_by_value(redis_reply_types, reply->element[0]->type, "<UNKNOWN>"));
1817 return;
1818 }
1819
1820 if (reply->element[1]->type != REDIS_REPLY_ARRAY) {
1821 ERROR("Failed retrieving result, expected array got %s",
1822 fr_table_str_by_value(redis_reply_types, reply->element[1]->type, "<UNKNOWN>"));
1823 return;
1824 }
1825
1826 strlcpy(pools_rctx->cursor, reply->element[0]->str, sizeof(pools_rctx->cursor));
1827
1828 for (k = 0; k < reply->element[1]->elements; k++) {
1829 fr_value_box_t *vb;
1830 redisReply *pool_key = reply->element[1]->element[k];
1831
1832 /*
1833 * Skip over things which are not pool names
1834 */
1835 if (pool_key->len < 7) continue; /* { + [<name>] + }:pool */
1836
1837 if ((pool_key->str[0]) != '{') continue;
1838 p = memchr(pool_key->str + 1, '}', pool_key->len - 1);
1839 if (!p) continue;
1840
1841 len = (pool_key->len - ((p + 1) - pool_key->str));
1842 if (len != (sizeof(IPPOOL_POOL_KEY) - 1) + 1) continue;
1843 if (memcmp(p + 1, ":" IPPOOL_POOL_KEY, (sizeof(IPPOOL_POOL_KEY) - 1) + 1) != 0) continue;
1844
1845 /*
1846 * String between the curly braces is the pool name
1847 */
1848 MEM(vb = fr_value_box_alloc(pools_rctx->ctx, FR_TYPE_STRING, NULL));
1849 fr_value_box_bstrndup(vb, vb, NULL, pool_key->str + 1, (p - pool_key->str) - 1, false);
1850 fr_value_box_list_insert_tail(&pools_rctx->pools, vb);
1851 }
1852}
1853
1856
1857static void redis_ippool_list_cancel(xlat_ctx_t const *xctx, request_t *request, UNUSED fr_signal_t action)
1858{
1859 redis_pools_list_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, redis_pools_list_rctx_t);
1860
1861 RDEBUG2("Forcefully cancelling Redis SCAN command");
1862
1864}
1865
1867 UNUSED request_t *request, UNUSED fr_value_box_list_t *in)
1868{
1869 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
1870 redis_pools_list_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, redis_pools_list_rctx_t);
1871
1872 switch (fr_redis_command_set_rcode(rctx->cmds)) {
1877 fr_value_box_list_talloc_free(&rctx->pools);
1878 return XLAT_ACTION_FAIL;
1879
1883 ~FR_SIGNAL_CANCEL, xctx->rctx);
1884
1885 default:
1886 break;
1887 }
1888
1889 if ((rctx->cursor[0] == '0') && rctx->cursor[1] == '\0') {
1890 rctx->current_node++;
1891 if (rctx->current_node == rctx->node_count) {
1892 if (fr_value_box_list_num_elements(&rctx->pools) == 0) return XLAT_ACTION_DONE;
1893
1894 fr_value_box_list_foreach(&rctx->pools, vb) {
1895 fr_value_box_list_remove(&rctx->pools, vb);
1897 }
1898 return XLAT_ACTION_DONE;
1899 }
1900 }
1901
1902 return redis_ippool_list_next_scan(request, thread, rctx);
1903}
1904
1905/** Enqueue the next SCAN command
1906 *
1907 * This will either be on the same node, with the cursor value returned
1908 * by the last SCAN, or starting on a new node.
1909 */
1912{
1913 fr_redis_ct_node_t *node;
1914 int cmd_len;
1916
1917 do {
1918 node = fr_redis_ct_node_by_addr(thread->rtcluster, &rctx->nodes[rctx->current_node]);
1919 if (node) {
1920 RDEBUG3("Querying node %d - %s:%d", rctx->current_node,
1921 rctx->nodes[rctx->current_node].hostname,
1922 rctx->nodes[rctx->current_node].port);
1923 break;
1924 }
1925 RWARN("Unable to find node %s:%d", rctx->nodes[rctx->current_node].hostname,
1926 rctx->nodes[rctx->current_node].port);
1927 rctx->current_node++;
1928 } while (rctx->current_node < rctx->node_count);
1929 if (!node) return XLAT_ACTION_DONE;
1930
1931 if (rctx->cmd_str) {
1932 redisFreeCommand(rctx->cmd_str);
1933 rctx->cmd_str = NULL;
1934 if (fr_redis_command_set_clear(rctx->cmds) < 0) return XLAT_ACTION_FAIL;
1935 }
1936
1937 /*
1938 * Break up the scan so we don't block any single Redis node too long.
1939 */
1940 cmd_len = redisFormatCommand(&rctx->cmd_str, "SCAN %s MATCH {*}:"IPPOOL_POOL_KEY" COUNT 20", rctx->cursor);
1941 if (cmd_len < 0) return XLAT_ACTION_FAIL;
1942
1944 rctx) != FR_REDIS_PIPELINE_OK) return XLAT_ACTION_FAIL;
1945
1946 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &ret, thread->rtcluster, NULL, 0, rctx->cmds, false, node);
1947
1948 REDIS_ASYNC_START_RCODE_PROCESS(ret, thread->rtcluster, thread->cw, thread->inst->coord_pair_reg,
1949 "Failed enqueuing redis command", XLAT_ACTION_FAIL);
1950
1952}
1953
1955 request_t *request, UNUSED fr_value_box_list_t *in)
1956{
1957 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
1958 redis_pools_list_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, redis_pools_list_rctx_t);
1960
1961 /*
1962 * Fetch the list of master nodes in the cluster.
1963 */
1964 ret = fr_redis_ct_node_addr_by_role(rctx, &rctx->nodes, &rctx->node_count, thread->rtcluster, true, false);
1965
1966 switch (ret) {
1968 break;
1969
1971 fr_redis_ct_map_bootstrap(thread->rtcluster, thread->cw, thread->inst->coord_pair_reg);
1972 fr_redis_ct_request_yield(rctx, thread->rtcluster, request);
1973 return unlang_xlat_yield(request, redis_ippool_list_start, NULL, 0, rctx);
1974
1975 default:
1976 return XLAT_ACTION_FAIL;
1977 }
1978
1979 return redis_ippool_list_next_scan(request, thread, rctx);
1980}
1981
1983{
1984 if (!rctx->cmd_str) return 0;
1985 redisFreeCommand(rctx->cmd_str);
1986 return 0;
1987}
1988
1990 request_t *request, fr_value_box_list_t *in)
1991{
1993
1994 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), redis_pools_list_rctx_t));
1995 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
1996 fr_value_box_list_init(&rctx->pools);
1997 rctx->ctx = ctx;
1998 talloc_set_destructor(rctx, _redis_pools_list_rctx_free);
1999 strcpy(rctx->cursor, "0");
2000
2001 return redis_ippool_list_start(ctx, out, XLAT_CTX(xctx->inst, NULL, xctx->ex, xctx->mctx, NULL, rctx),
2002 request, in);
2003}
2004
2006{
2007 char *cmd_str;
2008 size_t i = 0;
2009
2010 /*
2011 * Free any Redis commands
2012 */
2013 while (i < talloc_array_length(rctx->cmd_str)) {
2014 cmd_str = rctx->cmd_str[i];
2015 if (!cmd_str) break;
2016 redisFreeCommand(cmd_str);
2017 i++;
2018 }
2019
2020 return 0;
2021}
2022
2024{
2025 size_t i;
2026
2027 for (i = 0; i < talloc_array_length(rctx->cmd_str); i++) {
2028 if (!rctx->cmd_str[i]) return 0;
2029 redisFreeCommand(rctx->cmd_str[i]);
2030 }
2031
2032 return 0;
2033}
2034
2035static void mod_info_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
2036{
2037 redis_ippool_info_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_info_rctx_t);
2038
2039 RDEBUG2("Forcibly cancelling Redis command");
2040
2042}
2043
2045 request_t *request)
2046{
2047 redis_ippool_info_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_info_rctx_t);
2048
2049 if (redis_ippool_rcode_check(request, rctx->cmds, rctx->cmd, mctx, mod_show_resume,
2051
2053
2054 RDEBUG2("%pP", fr_pair_list_head(&rctx->results));
2055 fr_pair_list_append(&request->control_pairs, &rctx->results);
2056
2058}
2059
2060static void mod_show_result(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
2061{
2062 redis_ippool_info_rctx_t *show_rctx = talloc_get_type_abort(rctx, redis_ippool_info_rctx_t);
2063 redisReply *ip_reply;
2064 fr_pair_t *lease_vp, *vp;
2065 bool active;
2066 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
2067
2069
2070 IPPOOL_SPRINT_IP(ip_buff, &show_rctx->lookup[show_rctx->lookup_no]->vb_ip,
2071 show_rctx->lookup[show_rctx->lookup_no]->vb_ip.prefix);
2072 show_rctx->lookup_no++;
2073
2074 if (reply->type != REDIS_REPLY_ARRAY) return;
2075 if (reply->elements < 2) return;
2076
2077 if (reply->element[0]->type == REDIS_REPLY_NIL) return; /* A nil result (IP didn't exist) */
2078 if (reply->element[0]->type != REDIS_REPLY_STRING) return; /* Something bad */
2079 if (reply->element[1]->type != REDIS_REPLY_ARRAY) return;
2080
2081 MEM(lease_vp = fr_pair_afrom_da_nested(request->control_ctx, &show_rctx->results, attr_ippool_lease));
2083 fr_value_box_strdup(vp, &vp->data, NULL, ip_buff, false);
2084 fr_pair_append(&lease_vp->vp_group, vp);
2085
2087 fr_value_box_from_str(vp, &vp->data, FR_TYPE_DATE, NULL, reply->element[0]->str, reply->element[0]->len, NULL);
2088 fr_pair_append(&lease_vp->vp_group, vp);
2089 active = fr_unix_time_to_sec(vp->vp_date) > fr_time_to_sec(fr_time()) ? true : false;
2090
2092 vp->vp_bool = active;
2093 fr_pair_append(&lease_vp->vp_group, vp);
2094
2095 ip_reply = reply->element[1];
2096 if (ip_reply->element[0]->type == REDIS_REPLY_STRING) {
2098 fr_value_box_bstrndup(vp, &vp->data, NULL, ip_reply->element[0]->str, ip_reply->element[0]->len, false);
2099 fr_pair_append(&lease_vp->vp_group, vp);
2100 }
2101 if (ip_reply->element[1]->type == REDIS_REPLY_STRING) {
2103 fr_value_box_bstrndup(vp, &vp->data, NULL, ip_reply->element[1]->str, ip_reply->element[1]->len, false);
2104 fr_pair_append(&lease_vp->vp_group, vp);
2105 }
2106 if (ip_reply->element[2]->type == REDIS_REPLY_STRING) {
2108 fr_value_box_bstrndup(vp, &vp->data, NULL, ip_reply->element[2]->str, ip_reply->element[2]->len, false);
2109 fr_pair_append(&lease_vp->vp_group, vp);
2110 }
2111}
2112
2113#define ADD_REDIS_COMMAND(_fmt, ...) cmd_len = redisFormatCommand(&rctx->cmd_str[cmd_no], _fmt, __VA_ARGS__); \
2114if (cmd_len < 0) RETURN_UNLANG_FAIL; \
2115if (fr_redis_command_preformatted_add(cmds, rctx->cmd_str[cmd_no++], cmd_len, NULL, NULL) != FR_REDIS_PIPELINE_OK) goto error
2116
2118 request_t *request)
2119{
2120 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
2121 redis_ippool_show_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_show_call_env_t);
2124 int cmd_len;
2125 size_t cmd_no = 0, lookup_no = 0;
2127 uint8_t *key_p = key;
2130
2131 IPPOOL_BUILD_KEY(key, key_p, env->pool_name.vb_strvalue, env->pool_name.vb_length);
2133
2134 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), redis_ippool_info_rctx_t));
2135 MEM(cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
2136 rctx->cmds = cmds;
2137 MEM(rctx->lookup = talloc_zero_array(rctx, fr_value_box_t *, fr_value_box_list_num_elements(&env->requested_address)));
2138 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, fr_value_box_list_num_elements(&env->requested_address) * 2));
2139 fr_pair_list_init(&rctx->results);
2140 talloc_set_destructor(rctx, _redis_ippool_info_rctx_free);
2141
2143 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
2145 uint8_t *ip_key_p = ip_key;
2146
2147 IPPOOL_SPRINT_IP(ip_buff, &addr->vb_ip, addr->vb_ip.prefix);
2148 IPPOOL_BUILD_IP_KEY_FROM_STR(ip_key, ip_key_p, env->pool_name.vb_strvalue,
2149 env->pool_name.vb_length, ip_buff);
2150 if (ret == IPPOOL_RCODE_FAIL) {
2151 error:
2152 talloc_free(rctx);
2154 }
2155
2156 rctx->lookup[lookup_no++] = addr;
2157 if (fr_redis_command_literal_add(cmds, "MULTI", NULL, NULL) != FR_REDIS_PIPELINE_OK) goto error;
2158 ADD_REDIS_COMMAND("ZSCORE %b %s", key, key_p - key, ip_buff);
2159 ADD_REDIS_COMMAND("HMGET %b device gateway range", ip_key, ip_key_p - ip_key);
2160
2161 if (fr_redis_command_literal_add(cmds, "EXEC", mod_show_result, rctx) != FR_REDIS_PIPELINE_OK) goto error;
2162 }
2163
2164 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, thread->rtcluster,
2165 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
2166 rctx->cmds, false, NULL);
2167
2168 REDIS_ASYNC_START_RCODE_PROCESS(rcode, thread->rtcluster, thread->cw, thread->inst->coord_pair_reg,
2169 "Failed enqueueing Redis command", UNLANG_ACTION_FAIL)
2170
2172}
2173
2174static void mod_stats_result(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
2175{
2176 redis_ippool_info_rctx_t *stats_rctx = talloc_get_type_abort(rctx, redis_ippool_info_rctx_t);
2177 fr_pair_t *stats_vp, *dynamic_vp, *static_vp, *vp;
2178 size_t i;
2179
2181
2182 if (reply->type != REDIS_REPLY_ARRAY) return;
2183 if (reply->elements != 12) return;
2184
2185 for (i = 0; i < reply->elements; i++) if (reply->element[i]->type != REDIS_REPLY_INTEGER) return;
2186
2187 MEM(vp = fr_pair_afrom_da_nested(request->control_ctx, &stats_rctx->results, attr_ippool_name));
2188 if (fr_value_box_copy(vp, &vp->data, stats_rctx->lookup[0]) < 0) return;
2189 MEM(stats_vp = fr_pair_afrom_da_nested(request->control_ctx, &stats_rctx->results, attr_ippool_stats));
2190
2191#define POPULATE_PAIR(_da, _i, _parent) MEM(vp = fr_pair_afrom_da(_parent, _da)); \
2192 vp->vp_uint64 = reply->element[_i]->integer; \
2193 fr_pair_append(&_parent->vp_group, vp)
2194
2196
2197 MEM(dynamic_vp = fr_pair_afrom_da(stats_vp, attr_ippool_stats_dynamic));
2198 fr_pair_append(&stats_vp->vp_group, dynamic_vp);
2199
2201 vp->vp_uint64 = reply->element[0]->integer - reply->element[6]->integer;
2202 fr_pair_append(&dynamic_vp->vp_group, vp);
2203
2209
2210 MEM(static_vp = fr_pair_afrom_da(stats_vp, attr_ippool_stats_static));
2211 fr_pair_append(&stats_vp->vp_group, static_vp);
2218}
2219
2221 request_t *request)
2222{
2223 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
2224 redis_ippool_stats_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_stats_call_env_t);
2227 int cmd_len;
2228 size_t cmd_no = 0;
2230 uint8_t *key_p = key;
2233 fr_time_t now;
2234
2235 IPPOOL_BUILD_KEY(key, key_p, env->pool_name.vb_strvalue, env->pool_name.vb_length);
2237
2238 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), redis_ippool_info_rctx_t));
2239 MEM(cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
2240 rctx->cmds = cmds;
2241 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, 12));
2242 MEM(rctx->lookup = talloc_zero_array(rctx, fr_value_box_t *, 1));
2243 fr_pair_list_init(&rctx->results);
2244 rctx->lookup[0] = &env->pool_name;
2245 talloc_set_destructor(rctx, _redis_ippool_info_rctx_free);
2246
2247 now = fr_time();
2248
2249 if (fr_redis_command_literal_add(cmds, "MULTI", NULL, NULL) != FR_REDIS_PIPELINE_OK) {
2250 error:
2251 talloc_free(rctx);
2253 };
2254 ADD_REDIS_COMMAND("ZCARD %b", key, key_p - key); /* Total */
2255 ADD_REDIS_COMMAND("ZCOUNT %b -inf %i", key, key_p - key, fr_time_to_sec(now)); /* Free */
2256 ADD_REDIS_COMMAND("ZCOUNT %b -inf %i", key, key_p - key, fr_time_to_sec(now) + 60); /* Free in next 60s */
2257 ADD_REDIS_COMMAND("ZCOUNT %b -inf %i", key, key_p - key, fr_time_to_sec(now) + (60 * 30)); /* Free in next 30 mins */
2258 ADD_REDIS_COMMAND("ZCOUNT %b -inf %i", key, key_p - key, fr_time_to_sec(now) + (60 * 60)); /* Free in next 60 mins */
2259 ADD_REDIS_COMMAND("ZCOUNT %b -inf %i", key, key_p - key, fr_time_to_sec(now) + (60 * 60 * 24)); /* Free in next day */
2260 ADD_REDIS_COMMAND("ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " inf", key, key_p - key); /* Total static */
2261 ADD_REDIS_COMMAND("ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
2262 key, key_p - key, IPPOOL_STATIC_BIT + fr_time_to_sec(now)); /* Static assignments 'free' */
2263 ADD_REDIS_COMMAND("ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
2264 key, key_p - key, IPPOOL_STATIC_BIT + fr_time_to_sec(now) + 60); /* Static renew in 60s */
2265 ADD_REDIS_COMMAND("ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
2266 key, key_p - key, IPPOOL_STATIC_BIT + fr_time_to_sec(now) + (60 * 30)); /* Static renew in 30 mins */
2267 ADD_REDIS_COMMAND("ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
2268 key, key_p - key, IPPOOL_STATIC_BIT + fr_time_to_sec(now) + (60 * 60)); /* Static renew in 60 mins */
2269 ADD_REDIS_COMMAND("ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
2270 key, key_p - key, IPPOOL_STATIC_BIT + fr_time_to_sec(now) + (60 * 60 * 24)); /* Static renew in 1 day */
2271 if (fr_redis_command_literal_add(cmds, "EXEC", mod_stats_result, rctx) != FR_REDIS_PIPELINE_OK) goto error;
2272
2273 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, thread->rtcluster,
2274 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
2275 rctx->cmds, false, NULL);
2276
2277 REDIS_ASYNC_START_RCODE_PROCESS(rcode, thread->rtcluster, thread->cw, thread->inst->coord_pair_reg,
2278 "Failed enqueueing Redis command", UNLANG_ACTION_FAIL)
2279
2281}
2282
2283/** Increment an IP address by a given number of addresses
2284 */
2285static void ipaddr_inc(fr_ipaddr_t *addr, size_t inc)
2286{
2287 switch (addr->af) {
2288 case AF_INET:
2289 addr->addr.v4.s_addr = htonl(ntohl(addr->addr.v4.s_addr) + inc);
2290 break;
2291 case AF_INET6:
2292 {
2293 uint128_t ip_curr;
2294
2295 /* Don't be tempted to cast */
2296 memcpy(&ip_curr, addr->addr.v6.s6_addr, sizeof(ip_curr));
2297 ip_curr = ntohlll(ip_curr);
2298
2299 /* Increment the prefix */
2300 ip_curr = uint128_add(ip_curr, inc);
2301 ip_curr = htonlll(ip_curr);
2302 memcpy(&addr->addr.v6.s6_addr, &ip_curr, sizeof(addr->addr.v6.s6_addr));
2303 break;
2304 }
2305 }
2306}
2307
2308/** Parse argments provided to IP pool manipulation xlats which work on subnets
2309 *
2310 * @param[in] request The current request, for debugging.
2311 * @param[out] start Where to write the start address.
2312 * @param[out] end Where to write the end address.
2313 * @param[out] prefix_out Where to write the parsed value of prefix.
2314 * @param[out] step Where to write the step size for multiple addresses.
2315 * @param[out] num_addr Where to write the number of addresses
2316 * @param[in] subnet to parse.
2317 * @param[in] prefix_in optinal prefix argument.
2318 */
2320 uint8_t *prefix_out, size_t *step, size_t *num_addr,
2321 fr_value_box_t *subnet, fr_value_box_t *prefix_in)
2322{
2323 uint8_t prefix, subnetlen = subnet->vb_ip.prefix;
2324
2325 switch (subnet->vb_ip.af) {
2326 case AF_INET:
2327 prefix = (prefix_in && prefix_in->type == FR_TYPE_UINT8) ? prefix_in->vb_uint8 : 32;
2328 if ((prefix < 1) || (prefix > 32)) {
2329 RERROR("Prefix %d out of range (1-32)", prefix);
2330 return -1;
2331 }
2332 *step = 1 << (32 - prefix);
2333 break;
2334
2335 case AF_INET6:
2336 prefix = (prefix_in && prefix_in->type == FR_TYPE_UINT8) ? prefix_in->vb_uint8 : 128;
2337 if ((prefix < 1) || (prefix > 128)) {
2338 RERROR("Prefix %d out of range (1-128)", prefix);
2339 return -1;
2340 }
2341 *step = 1 << (128 - prefix);
2342 break;
2343
2344 default:
2345 fr_assert(0);
2346 return -1;
2347 }
2348
2349 *prefix_out = prefix;
2350
2351 if (prefix < subnetlen) {
2352 ERROR("Prefix len must be greater than or equal to subnet length (%u)", subnetlen);
2353 return -1;
2354 }
2355
2356 switch (subnet->vb_ip.af) {
2357 case AF_INET:
2358 {
2359 uint32_t ip;
2360
2361#ifdef __clang_analyzer__
2362 /* cond assert to satisfy clang scan */
2363 if (!fr_cond_assert((prefix > 0) && (prefix <= 32))) return -1;
2364#endif
2365
2366 /* Set the input to /32 so cast works */
2367 subnet->vb_ip.prefix = 32;
2368 if (fr_value_box_cast(start, start, FR_TYPE_IPV4_ADDR, NULL, subnet) < 0) return -1;
2369 if (subnetlen == 32) {
2370 if (fr_value_box_copy(end, end, start) < 0) return -1;
2371 *num_addr = 1;
2372 return 0;
2373 }
2374
2375 ip = ntohl(start->vb_ip.addr.v4.s_addr);
2376 ip |= uint32_gen_mask(prefix - subnetlen) << (32 - prefix);
2377
2378 /*
2379 * Exclude the broadcast address if we are working with /32 addresses.
2380 */
2381 if (prefix == 32) ip--;
2382
2383 fr_value_box_init(end, FR_TYPE_IPV4_ADDR, NULL, start->tainted);
2384 end->vb_ipv4addr = htonl(ip);
2385 }
2386 break;
2387
2388 case AF_INET6:
2389 {
2390 uint128_t ip, p_mask;
2391
2392#ifdef __clang_analyzer__
2393 /* cond assert to satisfy clang scan */
2394 if (!fr_cond_assert((prefix > 0) && (prefix <= 128))) return -1;
2395#endif
2396
2397 /* Set the input to /128 so cast works */
2398 subnet->vb_ip.prefix = 128;
2399 if (fr_value_box_cast(start, start, FR_TYPE_IPV6_ADDR, NULL, subnet) < 0) return -1;
2400 if (subnetlen == 128) {
2401 if (fr_value_box_copy(end, end, start) < 0) return -1;
2402 *num_addr = 1;
2403 return 0;
2404 }
2405
2406 memcpy(&ip, start->vb_ipv6addr, sizeof(ip));
2407 ip = ntohlll(ip);
2408 p_mask = uint128_lshift(uint128_gen_mask(prefix - subnetlen), (128 - prefix));
2409 ip = htonlll(uint128_bor(p_mask, ip));
2410
2411 fr_value_box_init(end, FR_TYPE_IPV6_ADDR, NULL, start->tainted);
2412 memcpy(&end->vb_ipv6addr, &ip, sizeof(end->vb_ipv6addr));
2413 }
2414 break;
2415
2416 default:
2417 fr_assert(0);
2418 }
2419
2420 if (unlikely(!fr_cond_assert((prefix - subnetlen) < 128))) return -1;
2421 *num_addr = (size_t)1 << (prefix - subnetlen);
2422 return 0;
2423}
2424
2425/** Parse argments provided to IP pool manipulation xlats which work on start and end addresses
2426 *
2427 * @param[in] request The current request, for debugging.
2428 * @param[out] prefix_out Where to write the parsed value of prefix.
2429 * @param[out] step Where to write the step size for multiple addresses.
2430 * @param[out] num_addr Where to write the number of addresses
2431 * @param[in] start The provided start address.
2432 * @param[in] end The provided end address.
2433 * @param[in] prefix_in optinal prefix argument.
2434 */
2435static int redis_ippool_addresses_arg_parse(request_t *request, uint8_t *prefix_out, size_t *step, size_t *num_addr,
2436 fr_value_box_t *start, fr_value_box_t *end, fr_value_box_t *prefix_in)
2437{
2438 uint8_t prefix;
2439
2440 if (start->vb_ip.af != end->vb_ip.af) {
2441 RERROR("Mis-matched start and end IP address types");
2442 return XLAT_ACTION_FAIL;
2443 }
2444
2445 switch (start->vb_ip.af) {
2446 case AF_INET:
2447 {
2448 uint32_t start_ip, end_ip;
2449 start_ip = ntohl(start->vb_ipv4addr);
2450 end_ip = ntohl(end->vb_ipv4addr);
2451
2452 prefix = (prefix_in && prefix_in->type == FR_TYPE_UINT8) ? prefix_in->vb_uint8 : 32;
2453 if ((prefix < 1) || (prefix > 32)) {
2454 RERROR("Prefix %d out of range (1-32)", prefix);
2455 return -1;
2456 }
2457 *step = 1 << (32 - prefix);
2458 *num_addr = (end_ip - start_ip + 1) / *step;
2459 }
2460 break;
2461
2462 case AF_INET6:
2463 {
2464 uint128_t start_ip, end_ip;
2465
2466 memcpy(&start_ip, start->vb_ipv6addr, sizeof(start_ip));
2467 memcpy(&end_ip, end->vb_ipv6addr, sizeof(end_ip));
2468
2469 start_ip = ntohlll(start_ip);
2470 end_ip = ntohlll(end_ip);
2471
2472 prefix = (prefix_in && prefix_in->type == FR_TYPE_UINT8) ? prefix_in->vb_uint8 : 128;
2473 if ((prefix < 1) || (prefix > 128)) {
2474 RERROR("Prefix %d out of range (1-127)", prefix);
2475 return -1;
2476 }
2477 *step = 1 << (128 - prefix);
2478 *num_addr = uint128_sub(end_ip, start_ip) + 1 / *step;
2479 }
2480
2481 break;
2482
2483 default:
2484 fr_assert(0);
2485 return -1;
2486 }
2487
2488 *prefix_out = prefix;
2489 return 0;
2490}
2491
2492/** Common cancellation function for xlats using redi_ippool_tool_rctx_t
2493 *
2494 */
2495static void redis_ippool_common_cancel(xlat_ctx_t const *xctx, request_t *request, UNUSED fr_signal_t action)
2496{
2497 redis_ippool_tool_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, redis_ippool_tool_rctx_t);
2498
2499 RDEBUG2("Forcibly cancelling pending IP pool command");
2500 if (rctx->cmd) fr_redis_async_cmd_cancel(rctx->cmd);
2501}
2502
2503/** Common resume function for pool manipulation xlats
2504 *
2505 * Where the number of changes is to be returned
2506 */
2508 UNUSED request_t *request, UNUSED fr_value_box_list_t *in)
2509{
2510 redis_ippool_tool_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, redis_ippool_tool_rctx_t);
2511 fr_value_box_t *vb;
2512
2513 switch (fr_redis_command_set_rcode(rctx->cmds)) {
2515 break;
2516
2518 {
2520 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2521
2522 if (inst->conf.use_cluster_map) fr_redis_ct_map_get(thread->rtcluster, thread->cw,
2523 inst->coord_pair_reg, false);
2524 }
2526
2530
2534
2536 PERROR("Server returned error");
2537 return XLAT_ACTION_FAIL;
2538
2539 default:
2540 return XLAT_ACTION_FAIL;
2541 }
2542
2543 MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_UINT32, NULL));
2544 vb->vb_uint32 = rctx->changes;
2546
2547 return XLAT_ACTION_DONE;
2548}
2549
2550/** Callback to be used when Redis commands are expected to return a single integer
2551 *
2552 * With the value indicating the number of changes made.
2553 */
2554static void redis_xlat_common_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
2555{
2556 redis_ippool_tool_rctx_t *xlat_rctx = talloc_get_type_abort(rctx, redis_ippool_tool_rctx_t);
2557
2558 if (reply->type != REDIS_REPLY_INTEGER) {
2559 RERROR("Unexpected reply type");
2560 return;
2561 }
2562
2563 xlat_rctx->changes += reply->integer;
2564 return;
2565}
2566
2567/** Callback to be used when Redis commands are expected to return an array
2568 *
2569 * Where the first element is expected to be an integer indicating the number
2570 * of changes made.
2571 * Typically this is when a MULTI ... EXEC is used and this parses the
2572 * reply from EXEC.
2573 */
2574static void redis_xlat_array_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
2575{
2576 redis_ippool_tool_rctx_t *xlat_rctx = talloc_get_type_abort(rctx, redis_ippool_tool_rctx_t);
2577
2578 if (reply->type != REDIS_REPLY_ARRAY) {
2579 error:
2580 RERROR("Unexpected reply type");
2581 return;
2582 }
2583
2584 if ((reply->elements > 0) && (reply->element[0]->type == REDIS_REPLY_INTEGER)) {
2585 xlat_rctx->changes += reply->element[0]->integer;
2586 } else {
2587 goto error;
2588 }
2589}
2590
2591/** Common code for adding addresses to a pool.
2592 */
2594 fr_value_box_t *pool, fr_value_box_t *start, fr_value_box_t *end,
2595 fr_value_box_t *range, uint8_t prefix, size_t num_addr, size_t step)
2596{
2599 int cmd_len;
2600 uint8_t *p = key;
2603 bool use_range = false;
2604 size_t cmd_no = 0;
2605 fr_value_box_t curr_addr;
2606 char ipaddr[INET6_ADDRSTRLEN + 1];
2607
2608 if (fr_value_box_copy(request, &curr_addr, start) < 0) return XLAT_ACTION_FAIL;
2609
2610 if (prefix != (curr_addr.vb_ip.af == AF_INET ? 32 : 128)) {
2611 if (fr_value_box_cast_in_place(request, &curr_addr, curr_addr.vb_ip.af == AF_INET ?
2613 curr_addr.vb_ip.prefix = prefix;
2614 }
2615
2616 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), redis_ippool_tool_rctx_t));
2617 talloc_set_destructor(rctx, _redis_ippool_tool_rctx_free);
2618
2619 IPPOOL_BUILD_KEY(key, p, pool->vb_strvalue, pool->vb_length);
2620 if (ret == IPPOOL_RCODE_FAIL) return XLAT_ACTION_FAIL;
2621
2622 use_range = range && range->type == FR_TYPE_STRING;
2623
2624 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
2625 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, num_addr * (use_range ? 2 : 1)));
2626
2627 do {
2628 DEBUG3("Adding %pV to pool \"%pV\"", &curr_addr, pool);
2629
2630 if (fr_value_box_print(&FR_SBUFF_OUT(ipaddr, sizeof(ipaddr)), &curr_addr, NULL) < 0) return XLAT_ACTION_FAIL;
2631 cmd_len = redisFormatCommand(&rctx->cmd_str[cmd_no], "ZADD %b NX %u %s", key, p - key, 0,
2632 ipaddr);
2633 if (cmd_len < 0) return XLAT_ACTION_FAIL;
2634
2635 if (use_range) {
2637 uint8_t *ip_key_p = ip_key;
2638
2639 if (fr_redis_command_literal_add(rctx->cmds, "MULTI", NULL, NULL) != FR_REDIS_PIPELINE_OK) {
2640 error:
2641 talloc_free(rctx);
2642 return XLAT_ACTION_FAIL;
2643 }
2644 if (fr_redis_command_preformatted_add(rctx->cmds, rctx->cmd_str[cmd_no++], cmd_len, NULL,
2645 NULL) != FR_REDIS_PIPELINE_OK) goto error;
2646
2647 IPPOOL_BUILD_IP_KEY_FROM_STR(ip_key, ip_key_p, pool->vb_strvalue, pool->vb_length, ipaddr);
2648 if (ret == IPPOOL_RCODE_FAIL) return XLAT_ACTION_FAIL;
2649 cmd_len = redisFormatCommand(&rctx->cmd_str[cmd_no], "HSET %b range %b", ip_key, ip_key_p - ip_key,
2650 range->vb_strvalue, range->vb_length);
2651 if (cmd_len < 0) return XLAT_ACTION_FAIL;
2652
2653 if (fr_redis_command_preformatted_add(rctx->cmds, rctx->cmd_str[cmd_no++], cmd_len, NULL,
2654 NULL) != FR_REDIS_PIPELINE_OK) goto error;
2656 rctx) != FR_REDIS_PIPELINE_OK) goto error;
2657 } else {
2658 if (fr_redis_command_preformatted_add(rctx->cmds, rctx->cmd_str[cmd_no++], cmd_len,
2660 rctx) != FR_REDIS_PIPELINE_OK) goto error;
2661 }
2662
2663 ipaddr_inc(&curr_addr.vb_ip, step);
2664 } while (fr_ipaddr_cmp(&curr_addr.vb_ip, &end->vb_ip) != 1);
2665
2666 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, t->rtcluster, (uint8_t const *)pool->vb_strvalue,
2667 pool->vb_length, rctx->cmds, false, NULL);
2668
2669 REDIS_ASYNC_START_RCODE_PROCESS(rcode, t->rtcluster, t->cw, inst->coord_pair_reg,
2670 "Failed to launch Redis command", XLAT_ACTION_FAIL);
2671
2673 ~FR_SIGNAL_CANCEL, rctx);
2674}
2675
2677 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2678 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_PREFIX }, // IP subnet
2679 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2680 { .single = true, .type = FR_TYPE_STRING }, // Range
2682};
2683
2685 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
2686{
2687 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
2688 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2689 fr_value_box_t *pool, *subnet, start, end, *prefix_in, *range;
2690 uint8_t prefix;
2691 size_t num_addr, step;
2692
2693 XLAT_ARGS(in, &pool, &subnet, &prefix_in, &range);
2694
2695 if (redis_ippool_subnet_arg_parse(request, &start, &end, &prefix, &step, &num_addr,
2696 subnet, prefix_in) < 0) return XLAT_ACTION_FAIL;
2697
2698 return redis_ippool_add_common(request, inst, t, pool, &start, &end, range, prefix, num_addr, step);
2699}
2700
2702 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2703 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // Start address
2704 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // End address
2705 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2706 { .single = true, .type = FR_TYPE_STRING }, // Range
2708};
2709
2711 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
2712{
2713 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
2714 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2715 fr_value_box_t *pool, *start, *end, *prefix_in, *range;
2716 uint8_t prefix = 0;
2717 size_t num_addr = 1, step = 1;
2718
2719 XLAT_ARGS(in, &pool, &start, &end, &prefix_in, &range);
2720
2721 if (redis_ippool_addresses_arg_parse(request, &prefix, &step, &num_addr,
2722 start, end, prefix_in) < 0) return XLAT_ACTION_FAIL;
2723
2724 return redis_ippool_add_common(request, inst, t, pool, start, end, range, prefix, num_addr, step);
2725}
2726
2727/** Common code for removing addresses from a pool.
2728 */
2730 fr_value_box_t *pool, fr_value_box_t *start, fr_value_box_t *end,
2731 uint8_t prefix, size_t num_addr, size_t step)
2732{
2734 int cmd_len;
2736 size_t cmd_no = 0;
2737 fr_value_box_t curr_addr;
2738 char ipaddr[INET6_ADDRSTRLEN + 1];
2739
2740 if (fr_value_box_copy(request, &curr_addr, start) < 0) return XLAT_ACTION_FAIL;
2741
2742 if (prefix != (curr_addr.vb_ip.af == AF_INET ? 32 : 128)) {
2743 if (fr_value_box_cast_in_place(request, &curr_addr, curr_addr.vb_ip.af == AF_INET ?
2745 curr_addr.vb_ip.prefix = prefix;
2746 }
2747
2748 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), redis_ippool_tool_rctx_t));
2749 talloc_set_destructor(rctx, _redis_ippool_tool_rctx_free);
2750
2751 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
2752 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, num_addr));
2753
2754 do {
2755 DEBUG3("Removing %pV from pool \"%pV\"", &curr_addr, pool);
2756
2757 if (fr_value_box_print(&FR_SBUFF_OUT(ipaddr, sizeof(ipaddr)), &curr_addr, NULL) < 0) return XLAT_ACTION_FAIL;
2758 cmd_len = redisFormatCommand(&rctx->cmd_str[cmd_no], "EVAL %s 1 %b %s", lua_remove_cmd,
2759 pool->vb_strvalue, pool->vb_length, ipaddr);
2760 if (cmd_len < 0) return XLAT_ACTION_FAIL;
2761
2762 if (fr_redis_command_preformatted_add(rctx->cmds, rctx->cmd_str[cmd_no++], cmd_len,
2764 talloc_free(rctx);
2765 return XLAT_ACTION_FAIL;
2766 };
2767
2768 ipaddr_inc(&curr_addr.vb_ip, step);
2769 } while (fr_ipaddr_cmp(&curr_addr.vb_ip, &end->vb_ip) != 1);
2770
2771 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, t->rtcluster, (uint8_t const *)pool->vb_strvalue,
2772 pool->vb_length, rctx->cmds, false, NULL);
2773
2774 REDIS_ASYNC_START_RCODE_PROCESS(rcode, t->rtcluster, t->cw, inst->coord_pair_reg,
2775 "Failed to launch Redis command", XLAT_ACTION_FAIL);
2776
2778 ~FR_SIGNAL_CANCEL, rctx);
2779}
2780
2782 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2783 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_PREFIX }, // IP subnet
2784 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2786};
2787
2789 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
2790{
2791 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
2792 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2793 fr_value_box_t *pool, *subnet, start, end, *prefix_in;
2794 uint8_t prefix;
2795 size_t num_addr, step;
2796
2797 XLAT_ARGS(in, &pool, &subnet, &prefix_in);
2798
2799 if (redis_ippool_subnet_arg_parse(request, &start, &end, &prefix, &step, &num_addr,
2800 subnet, prefix_in) < 0) return XLAT_ACTION_FAIL;
2801
2802 return redis_ippool_remove_common(request, inst, t, pool, &start, &end, prefix, num_addr, step);
2803}
2804
2806 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2807 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // Start address
2808 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // End address
2809 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2811};
2812
2814 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
2815{
2816 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
2817 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2818 fr_value_box_t *pool, *start, *end, *prefix_in;
2819 uint8_t prefix = 0;
2820 size_t num_addr = 1, step = 1;
2821
2822 XLAT_ARGS(in, &pool, &start, &end, &prefix_in);
2823
2824 if (redis_ippool_addresses_arg_parse(request, &prefix, &step, &num_addr,
2825 start, end, prefix_in) < 0) return XLAT_ACTION_FAIL;
2826
2827 return redis_ippool_remove_common(request, inst, t, pool, start, end, prefix, num_addr, step);
2828}
2829
2830/** Common code for releasing address assignments.
2831 */
2833 fr_value_box_t *pool, fr_value_box_t *start, fr_value_box_t *end,
2834 uint8_t prefix, size_t num_addr, size_t step)
2835{
2837 int cmd_len;
2839 size_t cmd_no = 0;
2840 fr_value_box_t curr_addr;
2841 char ipaddr[INET6_ADDRSTRLEN + 1];
2842
2843 if (fr_value_box_copy(request, &curr_addr, start) < 0) return XLAT_ACTION_FAIL;
2844
2845 if (prefix != (curr_addr.vb_ip.af == AF_INET ? 32 : 128)) {
2846 if (fr_value_box_cast_in_place(request, &curr_addr, curr_addr.vb_ip.af == AF_INET ?
2848 curr_addr.vb_ip.prefix = prefix;
2849 }
2850
2851 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), redis_ippool_tool_rctx_t));
2852 talloc_set_destructor(rctx, _redis_ippool_tool_rctx_free);
2853
2854 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
2855 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, num_addr));
2856
2857 do {
2858 DEBUG3("Releasing %pV to pool \"%pV\"", &curr_addr, pool);
2859
2860 if (fr_value_box_print(&FR_SBUFF_OUT(ipaddr, sizeof(ipaddr)), &curr_addr, NULL) < 0) return XLAT_ACTION_FAIL;
2861 cmd_len = redisFormatCommand(&rctx->cmd_str[cmd_no], "EVAL %s 1 %b %s", lua_release_xlat_cmd,
2862 pool->vb_strvalue, pool->vb_length, ipaddr);
2863 if (cmd_len < 0) return XLAT_ACTION_FAIL;
2864
2865 if (fr_redis_command_preformatted_add(rctx->cmds, rctx->cmd_str[cmd_no++], cmd_len,
2867 talloc_free(rctx);
2868 return XLAT_ACTION_FAIL;
2869 }
2870
2871 ipaddr_inc(&curr_addr.vb_ip, step);
2872 } while (fr_ipaddr_cmp(&curr_addr.vb_ip, &end->vb_ip) != 1);
2873
2874 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, t->rtcluster, (uint8_t const *)pool->vb_strvalue,
2875 pool->vb_length, rctx->cmds, false, NULL);
2876
2877 REDIS_ASYNC_START_RCODE_PROCESS(rcode, t->rtcluster, t->cw, inst->coord_pair_reg,
2878 "Failed to launch Redis command", XLAT_ACTION_FAIL);
2879
2881 ~FR_SIGNAL_CANCEL, rctx);
2882}
2883
2885 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2886 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_PREFIX }, // IP subnet
2887 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2889};
2890
2892 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
2893{
2894 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
2895 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2896 fr_value_box_t *pool, *subnet, start, end, *prefix_in;
2897 uint8_t prefix;
2898 size_t num_addr, step;
2899
2900 XLAT_ARGS(in, &pool, &subnet, &prefix_in);
2901
2902 if (redis_ippool_subnet_arg_parse(request, &start, &end, &prefix, &step, &num_addr,
2903 subnet, prefix_in) < 0) return XLAT_ACTION_FAIL;
2904
2905 return redis_ippool_release_common(request, inst, t, pool, &start, &end, prefix, num_addr, step);
2906}
2907
2909 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2910 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // Start address
2911 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // End address
2912 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2914};
2915
2917 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
2918{
2919 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
2920 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2921 fr_value_box_t *pool, *start, *end, *prefix_in;
2922 uint8_t prefix = 0;
2923 size_t num_addr = 1, step = 1;
2924
2925 XLAT_ARGS(in, &pool, &start, &end, &prefix_in);
2926
2927 if (redis_ippool_addresses_arg_parse(request, &prefix, &step, &num_addr,
2928 start, end, prefix_in) < 0) return XLAT_ACTION_FAIL;
2929
2930 return redis_ippool_release_common(request, inst, t, pool, start, end, prefix, num_addr, step);
2931}
2932
2933/** Common code for modifying the range assigned to addresses
2934 */
2936 fr_value_box_t *pool, fr_value_box_t *start, fr_value_box_t *end,
2937 fr_value_box_t *range, uint8_t prefix, size_t num_addr, size_t step)
2938{
2940 int cmd_len;
2943 size_t cmd_no = 0;
2944 fr_value_box_t curr_addr;
2945
2946 if (fr_value_box_copy(request, &curr_addr, start) < 0) return XLAT_ACTION_FAIL;
2947
2948 if (prefix != (curr_addr.vb_ip.af == AF_INET ? 32 : 128)) {
2949 if (fr_value_box_cast_in_place(request, &curr_addr, curr_addr.vb_ip.af == AF_INET ?
2951 curr_addr.vb_ip.prefix = prefix;
2952 }
2953
2954 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), redis_ippool_tool_rctx_t));
2955 talloc_set_destructor(rctx, _redis_ippool_tool_rctx_free);
2956
2957 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
2958 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, num_addr));
2959
2960 do {
2962 uint8_t *ip_key_p = ip_key;
2963
2964 DEBUG3("Modifying %pV in pool \"%pV\"", &curr_addr, pool);
2965
2966 IPPOOL_BUILD_IP_KEY(ip_key, ip_key_p, pool->vb_strvalue, pool->vb_length, &curr_addr);
2967 if (ret == IPPOOL_RCODE_FAIL) return XLAT_ACTION_FAIL;
2968
2969 cmd_len = redisFormatCommand(&rctx->cmd_str[cmd_no], "HSET %b range %b", ip_key, ip_key_p - ip_key,
2970 (uint8_t const *)range->vb_strvalue, range->vb_length);
2971 if (cmd_len < 0) return XLAT_ACTION_FAIL;
2972
2973 if (fr_redis_command_preformatted_add(rctx->cmds, rctx->cmd_str[cmd_no++], cmd_len,
2975 talloc_free(rctx);
2976 return XLAT_ACTION_FAIL;
2977 }
2978
2979 ipaddr_inc(&curr_addr.vb_ip, step);
2980 } while (fr_ipaddr_cmp(&curr_addr.vb_ip, &end->vb_ip) != 1);
2981
2982 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, t->rtcluster, (uint8_t const *)pool->vb_strvalue,
2983 pool->vb_length, rctx->cmds, false, NULL);
2984
2985 REDIS_ASYNC_START_RCODE_PROCESS(rcode, t->rtcluster, t->cw, inst->coord_pair_reg,
2986 "Failed to launch Redis command", XLAT_ACTION_FAIL);
2987
2989 ~FR_SIGNAL_CANCEL, rctx);
2990}
2991
2993 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2994 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_PREFIX }, // IP subnet
2995 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Range
2996 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2998};
2999
3001 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
3002{
3003 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
3004 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
3005 fr_value_box_t *pool, *subnet, *range, start, end, *prefix_in;
3006 uint8_t prefix;
3007 size_t num_addr, step;
3008
3009 XLAT_ARGS(in, &pool, &subnet, &range, &prefix_in);
3010
3011 if (redis_ippool_subnet_arg_parse(request, &start, &end, &prefix, &step, &num_addr,
3012 subnet, prefix_in) < 0) return XLAT_ACTION_FAIL;
3013
3014 return redis_ippool_modify_common(request, inst, t, pool, &start, &end, range, prefix, num_addr, step);
3015}
3016
3018 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
3019 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // Start address
3020 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // End address
3021 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Range
3022 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
3024};
3025
3027 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
3028{
3029 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
3030 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
3031 fr_value_box_t *pool, *start, *end, *range, *prefix_in;
3032 uint8_t prefix = 0;
3033 size_t num_addr = 1, step = 1;
3034
3035 XLAT_ARGS(in, &pool, &start, &end, &range, &prefix_in);
3036
3037 if (redis_ippool_addresses_arg_parse(request, &prefix, &step, &num_addr,
3038 start, end, prefix_in) < 0) return XLAT_ACTION_FAIL;
3039
3040 return redis_ippool_modify_common(request, inst, t, pool, start, end, range, prefix, num_addr, step);
3041}
3042
3044 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
3045 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_PREFIX }, // IP / prefix
3046 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Owner
3047 { .concat = true, .type = FR_TYPE_STRING }, // Range
3049};
3050
3052 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
3053{
3054 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
3055 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
3056 fr_value_box_t *pool, *ipaddr, *owner, *range;
3058 int cmd_len;
3060 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
3061 uint8_t const *range_str = NULL;
3062 size_t range_len = 0;
3063
3064 XLAT_ARGS(in, &pool, &ipaddr, &owner, &range);
3065
3066 if (range && (range->type == FR_TYPE_STRING)) {
3067 range_str = (uint8_t const *)range->vb_strvalue;
3068 range_len = range->vb_length;
3069 }
3070
3071 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), redis_ippool_tool_rctx_t));
3072 talloc_set_destructor(rctx, _redis_ippool_tool_rctx_free);
3073
3074 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
3075 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, 1));
3076
3077 IPPOOL_SPRINT_IP(ip_buff, &ipaddr->vb_ip, ipaddr->vb_ip.prefix);
3078 cmd_len = redisFormatCommand(&rctx->cmd_str[0], "EVAL %s 1 %b %s %b %b %i", lua_assign_cmd,
3079 (uint8_t const *)pool->vb_strvalue, pool->vb_length, ip_buff,
3080 (uint8_t const *)owner->vb_strvalue, owner->vb_length,
3081 range_str, range_len, fr_time_to_sec(fr_time()));
3082 if (cmd_len < 0) return XLAT_ACTION_FAIL;
3083
3085 rctx) != FR_REDIS_PIPELINE_OK) {
3086 talloc_free(rctx);
3087 return XLAT_ACTION_FAIL;
3088 }
3089
3090 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, t->rtcluster, (uint8_t const *)pool->vb_strvalue,
3091 pool->vb_length, rctx->cmds, false, NULL);
3092
3093 REDIS_ASYNC_START_RCODE_PROCESS(rcode, t->rtcluster, t->cw, inst->coord_pair_reg,
3094 "Failed to launch Redis command", XLAT_ACTION_FAIL);
3095
3097 ~FR_SIGNAL_CANCEL, rctx);
3098}
3099
3101 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
3102 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_PREFIX }, // IP / prefix
3103 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Owner
3105};
3106
3108 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
3109{
3110 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
3111 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
3112 fr_value_box_t *pool, *ipaddr, *owner;
3114 int cmd_len;
3116 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
3117
3118 XLAT_ARGS(in, &pool, &ipaddr, &owner);
3119
3120 MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), redis_ippool_tool_rctx_t));
3121 talloc_set_destructor(rctx, _redis_ippool_tool_rctx_free);
3122
3123 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
3124 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, 1));
3125
3126 IPPOOL_SPRINT_IP(ip_buff, &ipaddr->vb_ip, ipaddr->vb_ip.prefix);
3127 cmd_len = redisFormatCommand(&rctx->cmd_str[0], "EVAL %s 1 %b %s %b %i", lua_unassign_cmd,
3128 (uint8_t const *)pool->vb_strvalue, pool->vb_length, ip_buff,
3129 (uint8_t const *)owner->vb_strvalue, owner->vb_length,
3131 if (cmd_len < 0) return XLAT_ACTION_FAIL;
3132
3134 rctx) != FR_REDIS_PIPELINE_OK) {
3135 talloc_free(rctx);
3136 return XLAT_ACTION_FAIL;
3137 }
3138
3139 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, t->rtcluster, (uint8_t const *)pool->vb_strvalue,
3140 pool->vb_length, rctx->cmds, false, NULL);
3141
3142 REDIS_ASYNC_START_RCODE_PROCESS(rcode, t->rtcluster, t->cw, inst->coord_pair_reg,
3143 "Failed to launch Redis command", XLAT_ACTION_FAIL);
3144
3146 ~FR_SIGNAL_CANCEL, rctx);
3147}
3148
3149static void lua_script_load(fr_redis_trunk_t *rtrunk, UNUSED void *uctx)
3150{
3152
3153 MEM(cmds = fr_redis_command_set_alloc(rtrunk, NULL, NULL, NULL, NULL, true));
3154
3158
3159 if (redis_command_set_enqueue(rtrunk, cmds) != FR_REDIS_PIPELINE_OK) {
3160 ERROR("Failed to enqueue lua function loading");
3161 talloc_free(cmds);
3162 }
3163}
3164
3166{
3167 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
3168 rlm_redis_ippool_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_ippool_t);
3169
3170 t->rtcluster = fr_redis_ct_alloc(t, inst->tls_conf, mctx->el, &inst->conf, lua_script_load, t, true);
3171
3172 if (!t->rtcluster) return -1;
3173 t->inst = inst;
3174
3175 return 0;
3176}
3177
3179{
3180 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
3181 rlm_redis_ippool_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_ippool_t);
3182
3183 if (!inst->conf.use_cluster_map) return 0;
3184
3185 t->cw = fr_coord_attach(t, mctx->el, inst->coord_reg);
3186
3187 if (!t->cw) {
3188 ERROR("Failed to attach to coordinator");
3189 return -1;
3190 }
3191
3192 if ((inst->conf.trunk_conf.start == 0) || (fr_schedule_worker_id() != 0)) return 0;
3193
3194 return fr_redis_ct_map_bootstrap(t->rtcluster, t->cw, inst->coord_pair_reg);
3195}
3196
3198
3199static int mod_instantiate(module_inst_ctx_t const *mctx)
3200{
3201 static bool done_hash = false;
3202 CONF_SECTION *subcs = cf_section_find(mctx->mi->conf, "redis", NULL);
3203 rlm_redis_ippool_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_ippool_t);
3204
3205 fr_assert(subcs);
3206
3207 inst->conf.log_prefix = mctx->mi->name;
3208 inst->conf.module_name = mctx->mi->module->name;
3209 inst->conf.inst_name = mctx->mi->name;
3210
3211 if (inst->conf.use_tls) {
3212 inst->tls_conf = cf_section_find(subcs, "tls", CF_IDENT_ANY);
3213
3214 if (!inst->tls_conf) {
3215 cf_log_err(mctx->mi->conf, "Missing tls section");
3216 return -1;
3217 }
3218 }
3219
3220 if (!inst->conf.use_cluster_map) goto cmds;
3221
3222 if (inst->conf.database) {
3223 cf_log_err(mctx->mi->conf, "Cannot set Redis database number when cluster in use");
3224 return -1;
3225 }
3226
3228 .name = mctx->mi->name,
3229 .worker_cb = worker_pair_callbacks,
3230 .cb_id = REDIS_COORD_PAIR_CALLBACK_ID,
3231 .root = fr_dict_root(dict_redis),
3232 .cs = subcs,
3233 }
3234 );
3235 if (!inst->coord_pair_reg) return -1;
3236
3237 FR_COORD_PAIR_CB_CTX_SET(coord_callbacks, worker_callbacks, inst->coord_pair_reg);
3238
3239 inst->coord_reg = fr_coord_register(&(fr_coord_reg_ctx_t) {
3240 .name = mctx->mi->name,
3241 .coord_cb = coord_callbacks,
3242 .worker_cb = worker_callbacks,
3243 .mi = mctx->mi
3244 });
3245
3246 if (!inst->coord_reg) return -1;
3247
3248cmds:
3249 if (inst->wait_num) {
3250 inst->wait_cmd_len = redisFormatCommand(&inst->wait_cmd, "WAIT %i %i", inst->wait_num,
3251 fr_time_delta_to_msec(inst->wait_timeout));
3252 if (inst->wait_cmd_len < 0) return -1;
3253 }
3254
3255 /*
3256 * Pre-Compute the SHA1 hashes of the Lua scripts
3257 */
3258 if (!done_hash) {
3259 fr_sha1_ctx sha1_ctx;
3261
3262 fr_sha1_init(&sha1_ctx);
3263 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_alloc_cmd, sizeof(lua_alloc_cmd) - 1);
3264 fr_sha1_final(digest, &sha1_ctx);
3265 fr_base16_encode(&FR_SBUFF_OUT(lua_alloc_digest, sizeof(lua_alloc_digest)), &FR_DBUFF_TMP(digest, sizeof(digest)));
3266
3267 fr_sha1_init(&sha1_ctx);
3268 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_update_cmd, sizeof(lua_update_cmd) - 1);
3269 fr_sha1_final(digest, &sha1_ctx);
3271
3272 fr_sha1_init(&sha1_ctx);
3273 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_release_cmd, sizeof(lua_release_cmd) - 1);
3274 fr_sha1_final(digest, &sha1_ctx);
3276 done_hash = true;
3277 }
3278
3279 return 0;
3280}
3281
3283{
3284 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
3285
3286 if (!t->cw) return 0;
3287
3288 fr_coord_detach(t->cw, true);
3289 t->cw = NULL;
3290 return 0;
3291}
3292
3293static int mod_detach(module_detach_ctx_t const *mctx)
3294{
3295 rlm_redis_ippool_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_ippool_t);
3296
3297 fr_coord_deregister(inst->coord_reg);
3298 talloc_free(inst->coord_pair_reg);
3299
3300 if (inst->wait_cmd) redisFreeCommand(inst->wait_cmd);
3301
3302 return 0;
3303}
3304
3305static int mod_bootstrap(module_inst_ctx_t const *mctx)
3306{
3307 xlat_t *xlat;
3308
3309 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "subnet.add", redis_ippool_subnet_add_xlat,
3310 FR_TYPE_UINT32)) == NULL)) return -1;
3313
3314 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "addresses.add", redis_ippool_addresses_add_xlat,
3315 FR_TYPE_UINT32)) == NULL)) return -1;
3318
3319 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "subnet.remove", redis_ippool_subnet_remove_xlat,
3320 FR_TYPE_UINT32)) == NULL)) return -1;
3323
3324 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "addresses.remove", redis_ippool_addresses_remove_xlat,
3325 FR_TYPE_UINT32)) == NULL)) return -1;
3328
3329 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "subnet.release", redis_ippool_subnet_release_xlat,
3330 FR_TYPE_UINT32)) == NULL)) return -1;
3333
3334 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "addresses.release", redis_ippool_addresses_release_xlat,
3335 FR_TYPE_UINT32)) == NULL)) return -1;
3338
3339 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "subnet.modify", redis_ippool_subnet_modify_xlat,
3340 FR_TYPE_UINT32)) == NULL)) return -1;
3343
3344 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "addresses.modify", redis_ippool_addresses_modify_xlat,
3345 FR_TYPE_UINT32)) == NULL)) return -1;
3348
3349 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "address.assign", redis_ippool_assign_xlat,
3350 FR_TYPE_UINT32)) == NULL)) return -1;
3353
3354 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "address.unassign", redis_ippool_unassign_xlat,
3355 FR_TYPE_UINT32)) == NULL)) return -1;
3358
3359 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "pools.list", redis_ippool_list_xlat,
3360 FR_TYPE_STRING)) == NULL)) return -1;
3362
3363 return 0;
3364}
3365
3366static int mod_load(void)
3367{
3369
3370 return 0;
3371}
3372
3375 .common = {
3376 .magic = MODULE_MAGIC_INIT,
3377 .name = "redis_ippool",
3378 .inst_size = sizeof(rlm_redis_ippool_t),
3380 .onload = mod_load,
3381 .bootstrap = mod_bootstrap,
3382 .instantiate = mod_instantiate,
3383 .coord_attach = mod_coord_attach,
3384 .detach = mod_detach,
3386 .thread_instantiate = mod_thread_instantiate,
3387 .thread_detach = mod_thread_detach,
3388 },
3389 .method_group = {
3390 .bindings = (module_method_binding_t[]){
3391 { .section = SECTION_NAME("recv", "Access-Request"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* radius */
3392 { .section = SECTION_NAME("accounting", "Start"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* radius */
3393 { .section = SECTION_NAME("accounting", "Interim-Update"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* radius */
3394 { .section = SECTION_NAME("accounting", "Stop"), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* radius */
3395 { .section = SECTION_NAME("accounting", "Accounting-On"), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* radius */
3396 { .section = SECTION_NAME("accounting", "Accounting-Off"), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* radius */
3397
3398 { .section = SECTION_NAME("recv", "Discover"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* dhcpv4 */
3399 { .section = SECTION_NAME("recv", "Release"), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* dhcpv4 */
3400 { .section = SECTION_NAME("send", "Ack"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* dhcpv4 */
3401
3402 { .section = SECTION_NAME("recv", "Solicit"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* dhcpv6 */
3403
3404 { .section = SECTION_NAME("recv", CF_IDENT_ANY), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* generic */
3405 { .section = SECTION_NAME("send", CF_IDENT_ANY), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* generic */
3406
3407 { .section = SECTION_NAME("allocate", NULL), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* verb */
3408 { .section = SECTION_NAME("update", NULL), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* verb */
3409 { .section = SECTION_NAME("renew", NULL), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* verb */
3410 { .section = SECTION_NAME("release", NULL), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* verb */
3411 { .section = SECTION_NAME("bulk-release", NULL), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* verb */
3412
3413 { .section = SECTION_NAME("show", CF_IDENT_ANY), .method = mod_show, .method_env = &redis_ippool_show_method_env }, /* verb */
3414 { .section = SECTION_NAME("stats", CF_IDENT_ANY), .method = mod_stats, .method_env = &redis_ippool_stats_method_env }, /* verb */
3416 }
3417 }
3418};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
@ UNLANG_ACTION_FAIL
Encountered an unexpected error.
Definition action.h:36
@ UNLANG_ACTION_CALCULATE_RESULT
Calculate a new section rlm_rcode_t value.
Definition action.h:37
@ UNLANG_ACTION_YIELD
Temporarily pause execution until an event occurs.
Definition action.h:41
strcpy(log_entry->msg, buffer)
#define fr_base16_encode(_out, _in)
Definition base16.h:54
#define RCSID(id)
Definition build.h:560
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:391
#define STRINGIFY(x)
Definition build.h:216
#define unlikely(_x)
Definition build.h:455
#define UNUSED
Definition build.h:384
#define CALL_ENV_TERMINATOR
Definition call_env.h:236
#define FR_CALL_ENV_METHOD_OUT(_inst)
Helper macro for populating the size/type fields of a call_env_method_t from the output structure typ...
Definition call_env.h:240
call_env_parser_t const * env
Parsing rules for call method env.
Definition call_env.h:247
@ CALL_ENV_FLAG_CONCAT
If the tmpl produced multiple boxes they should be concatenated.
Definition call_env.h:76
@ CALL_ENV_FLAG_ATTRIBUTE
Tmpl MUST contain an attribute reference.
Definition call_env.h:86
@ CALL_ENV_FLAG_NONE
Definition call_env.h:74
@ CALL_ENV_FLAG_REQUIRED
Associated conf pair or section is required.
Definition call_env.h:75
@ CALL_ENV_FLAG_BARE_WORD_ATTRIBUTE
bare words are treated as an attribute, but strings may be xlats.
Definition call_env.h:92
@ CALL_ENV_FLAG_NULLABLE
Tmpl expansions are allowed to produce no output.
Definition call_env.h:80
#define FR_CALL_ENV_OFFSET(_name, _cast_type, _flags, _struct, _field)
Specify a call_env_parser_t which writes out runtime results to the specified field.
Definition call_env.h:340
#define FR_CALL_ENV_PARSE_ONLY_OFFSET(_name, _cast_type, _flags, _struct, _parse_field)
Specify a call_env_parser_t which writes out the result of the parsing phase to the field specified.
Definition call_env.h:389
Per method call config.
Definition call_env.h:180
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:669
#define FR_CONF_DEPRECATED(_name, _struct, _field)
conf_parser_t entry which raises an error if a matching CONF_PAIR is found
Definition cf_parse.h:409
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:280
fr_token_t quote
Quoting around the default value. Only used for templates.
Definition cf_parse.h:661
#define FR_CONF_POINTER(_name, _type, _flags, _res_p)
conf_parser_t which parses a single CONF_PAIR producing a single global result
Definition cf_parse.h:334
@ CONF_FLAG_SUBSECTION
Instead of putting the information into a configuration structure, the configuration file routines MA...
Definition cf_parse.h:423
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:606
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:106
CONF_SECTION * cf_section_find(CONF_SECTION const *cs, char const *name1, char const *name2)
Find a CONF_SECTION with name1 and optionally name2.
Definition cf_util.c:1204
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:345
#define CF_IDENT_ANY
Definition cf_util.h:80
void fr_redis_ct_request_yield(TALLOC_CTX *ctx, fr_redis_ct_t *rtcluster, request_t *request)
Add a request to the list of those waiting for the cluster map.
int fr_redis_ct_map_bootstrap(fr_redis_ct_t *rtcluster, fr_coord_worker_t *cw, fr_coord_pair_reg_t *coord_pair_reg)
Initiate bootstrapping of the cluster map.
fr_redis_async_cmd_t * fr_redis_async_cmd_start(TALLOC_CTX *ctx, request_t *request, fr_redis_async_rcode_t *rcode, fr_redis_ct_t *rtcluster, uint8_t const *key, size_t key_len, fr_redis_command_set_t *cmds, bool read_only, fr_redis_ct_node_t *node)
Start running a command set on an async redis cluster.
fr_redis_async_rcode_t fr_redis_ct_node_addr_by_role(TALLOC_CTX *ctx, fr_redis_io_conf_t *out[], uint8_t *count_out, fr_redis_ct_t *rtcluster, bool is_master, bool is_replica)
fr_redis_async_rcode_t fr_redis_ct_map_get(fr_redis_ct_t *rtcluster, fr_coord_worker_t *cw, fr_coord_pair_reg_t *coord_pair_reg, bool force)
Initiate updating of the cluster map.
void fr_redis_async_cmd_cancel(fr_redis_async_cmd_t *cmd)
Cancel a Redis async command.
fr_redis_ct_node_t * fr_redis_ct_node_by_addr(fr_redis_ct_t *rtcluster, fr_redis_io_conf_t *ioconf)
fr_redis_async_rcode_t fr_redis_async_cmd_resend(fr_redis_async_cmd_t *cmd)
Re-submit a redis async command set.
fr_redis_ct_node_t * fr_redis_async_cmd_node(fr_redis_async_cmd_t *cmd)
Fetch the cluster node a command was last sent to.
fr_redis_async_rcode_t fr_redis_async_cmd_redirect(fr_redis_async_cmd_t *cmd)
Re-submit a redis async command set on a different node.
fr_redis_ct_t * fr_redis_ct_alloc(TALLOC_CTX *ctx, CONF_SECTION *tls_cs, fr_event_list_t *el, fr_redis_conf_t *conf, fr_redis_trunk_active_t active, void *active_uctx, bool active_oneshot)
Allocate per-thread, per-cluster instance.
Structure for holding the state of an async redis command set.
Thread local state for a cluster.
#define REDIS_ASYNC_COORD_CALLBACKS(_thread_type)
#define REDIS_ASYNC_START_RCODE_PROCESS(_rcode, _cluster, _cw, _coord_pair_reg, _error_msg, _error_ret)
Convenience macro to reduce boilerplate.
fr_coord_reg_t * fr_coord_register(fr_coord_reg_ctx_t *reg_ctx)
Register a coordinator.
Definition coord.c:137
fr_coord_worker_t * fr_coord_attach(TALLOC_CTX *ctx, fr_event_list_t *el, fr_coord_reg_t *coord_reg)
Attach a worker to a coordinator.
Definition coord.c:662
void fr_coord_deregister(fr_coord_reg_t *coord_reg)
De-register a coordinator.
Definition coord.c:172
int fr_coord_detach(fr_coord_worker_t *cw, bool exiting)
Signal a coordinator that a worker wants to detach.
Definition coord.c:623
A coordinator registration.
Definition coord.c:85
The worker end of worker <-> coordinator communication.
Definition coord.c:73
fr_coord_pair_reg_t * fr_coord_pair_register(fr_coord_pair_reg_ctx_t *reg_ctx)
Register a set of callbacks for pair list based coordinator messages.
Definition coord_pair.c:113
struct fr_coord_pair_reg_s fr_coord_pair_reg_t
Definition coord_pair.h:32
#define FR_COORD_PAIR_CB_CTX_SET(_in_cb, _out_cb, _reg)
Set up ctx on pair list callbacks.
Definition coord_pair.h:93
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:522
static int fr_dcursor_append(fr_dcursor_t *cursor, void *v)
Insert a single item at the end of the list.
Definition dcursor.h:406
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition debug.h:172
#define MEM(x)
Definition debug.h:38
#define ERROR(fmt,...)
Definition dhcpclient.c:40
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition dict_util.c:2637
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:292
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:305
#define DICT_AUTOLOAD_TERMINATOR
Definition dict.h:311
static fr_slen_t in
Definition dict.h:882
Specifies an attribute which must be present for the module to function.
Definition dict.h:291
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:304
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
talloc_free(hp)
fr_cmp_ret_t fr_ipaddr_cmp(fr_ipaddr_t const *a, fr_ipaddr_t const *b)
Compare two ip addresses.
Definition inet.c:1353
union fr_ipaddr_t::@154 addr
uint8_t prefix
Prefix length - Between 0-32 for IPv4 and 0-128 for IPv6.
Definition inet.h:69
int af
Address family.
Definition inet.h:64
#define FR_IPADDR_PREFIX_STRLEN
Like FR_IPADDR_STRLEN but with space for a prefix.
Definition inet.h:93
IPv4/6 prefix.
TALLOC_CTX * unlang_interpret_frame_talloc_ctx(request_t *request)
Get a talloc_ctx which is valid only for this frame.
Definition interpret.c:2053
char const * hostname
Definition io.h:51
uint16_t port
Definition io.h:52
#define PERROR(_fmt,...)
Definition log.h:233
#define DEBUG3(_fmt,...)
Definition log.h:271
#define RWDEBUG(fmt,...)
Definition log.h:378
#define RDEBUG3(fmt,...)
Definition log.h:360
#define RWARN(fmt,...)
Definition log.h:314
#define RDEBUGX(_l, fmt,...)
Definition log.h:357
#define RERROR(fmt,...)
Definition log.h:315
#define RPERROR(fmt,...)
Definition log.h:319
#define RPEDEBUG(fmt,...)
Definition log.h:393
int map_to_vp(TALLOC_CTX *ctx, fr_pair_list_t *out, request_t *request, map_t const *map, UNUSED void *uctx)
Convert a map to a fr_pair_t.
Definition map.c:1534
int map_to_request(request_t *request, map_t const *map, radius_map_getvalue_t func, void *ctx)
Convert map_t to fr_pair_t (s) and add them to a request_t.
Definition map.c:1814
#define fr_time()
Definition event.c:60
fr_log_lvl_t
Definition log.h:64
@ L_DBG_LVL_3
3rd highest priority debug messages (-xxx | -Xx).
Definition log.h:69
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition log.h:68
@ FR_TYPE_IPV4_ADDR
32 Bit IPv4 Address.
@ FR_TYPE_TLV
Contains nested attributes.
@ FR_TYPE_IPV6_PREFIX
IPv6 Prefix.
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_DATE
Unix time stamp, always has value >2^31.
@ FR_TYPE_COMBO_IP_PREFIX
IPv4 or IPv6 address prefix depending on length.
@ FR_TYPE_UINT8
8 Bit unsigned integer.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_UINT64
64 Bit unsigned integer.
@ FR_TYPE_IPV6_ADDR
128 Bit IPv6 Address.
@ FR_TYPE_IPV4_PREFIX
IPv4 Prefix.
@ FR_TYPE_VOID
User data.
@ FR_TYPE_BOOL
A truth value.
@ FR_TYPE_COMBO_IP_ADDR
IPv4 or IPv6 address depending on length.
unsigned int uint32_t
unsigned char uint8_t
unsigned long int size_t
void * env_data
Per call environment data.
Definition module_ctx.h:44
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
void * thread
Thread specific instance data.
Definition module_ctx.h:43
void * rctx
Resume ctx that a module previously set.
Definition module_ctx.h:45
fr_event_list_t * el
Event list to register any IO handlers and timers against.
Definition module_ctx.h:68
module_instance_t * mi
Module instance to detach.
Definition module_ctx.h:57
void * thread
Thread instance data.
Definition module_ctx.h:67
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:64
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
Temporary structure to hold arguments for detach calls.
Definition module_ctx.h:56
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
Temporary structure to hold arguments for thread_instantiation calls.
Definition module_ctx.h:63
xlat_t * module_rlm_xlat_register(TALLOC_CTX *ctx, module_inst_ctx_t const *mctx, char const *name, xlat_func_t func, fr_type_t return_type)
Definition module_rlm.c:234
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1352
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:290
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:46
fr_pair_t * fr_pair_afrom_da_nested(TALLOC_CTX *ctx, fr_pair_list_t *list, fr_dict_attr_t const *da)
Create a pair (and all intermediate parents), and append it to the list.
Definition pair.c:480
fr_redis_async_rcode_t fr_redis_command_set_rcode(fr_redis_command_set_t *cmds)
Extract the rcode from a command set.
Definition pipeline.c:994
fr_redis_pipeline_status_t fr_redis_command_preformatted_add(fr_redis_command_set_t *cmds, char const *cmd_str, size_t cmd_len, fr_redis_command_complete_t complete, void *rctx)
Add an preformatted command to the command set as formatted by redisCommandFormat or it's variants.
Definition pipeline.c:479
fr_redis_pipeline_status_t redis_command_set_enqueue(fr_redis_trunk_t *rtrunk, fr_redis_command_set_t *cmds)
Enqueue a command set on a specific trunk.
Definition pipeline.c:532
fr_redis_command_set_t * fr_redis_command_set_alloc(TALLOC_CTX *ctx, request_t *request, fr_redis_command_set_complete_t complete, fr_redis_command_set_fail_t fail, void *rctx, bool autofree)
Allocate a new command set.
Definition pipeline.c:249
fr_redis_pipeline_status_t fr_redis_command_literal_add(fr_redis_command_set_t *cmds, char const *cmd_str, fr_redis_command_complete_t complete, void *rctx)
Add a literal command to the command set.
Definition pipeline.c:401
int fr_redis_command_set_clear(fr_redis_command_set_t *cmds)
Definition pipeline.c:1036
Represents a single command.
Definition pipeline.c:62
Represents a collection of pipelined commands.
Definition pipeline.c:94
void(* fr_redis_command_complete_t)(request_t *request, fr_redis_command_t *cmd, redisReply *reply, void *rctx)
Process the reply from a single command.
Definition pipeline.h:60
@ FR_REDIS_PIPELINE_OK
No failure.
Definition pipeline.h:44
static const conf_parser_t config[]
Definition base.c:162
#define fr_assert(_expr)
Definition rad_assert.h:37
#define REDEBUG(fmt,...)
#define RDEBUG2(fmt,...)
#define DEBUG2(fmt,...)
#define RETURN_UNLANG_UPDATED
Definition rcode.h:70
#define RETURN_UNLANG_INVALID
Definition rcode.h:66
#define RETURN_UNLANG_NOTFOUND
Definition rcode.h:68
#define RETURN_UNLANG_FAIL
Definition rcode.h:63
#define RETURN_UNLANG_NOOP
Definition rcode.h:69
void fr_redis_reply_print(fr_log_lvl_t lvl, redisReply *reply, request_t *request, int idx, fr_redis_rcode_t status)
Print the response data in a useful treelike form.
Definition redis.c:153
#define REDIS_COMMON_CONFIG
Definition base.h:143
fr_redis_async_rcode_t
Definition base.h:80
@ REDIS_ASYNC_RCODE_BOOTSTRAP
The caller should issue a request to bootstrap the cluster map.
Definition base.h:83
@ REDIS_ASYNC_RCODE_MOVE
Attempt operation on an alternative node with remap.
Definition base.h:88
@ REDIS_ASYNC_RCODE_ERROR
Unrecoverable error.
Definition base.h:82
@ REDIS_ASYNC_RCODE_ASK
Attempt operation on an alternative node.
Definition base.h:87
@ REDIS_ASYNC_RCODE_FAIL
The command set trunk request has been failed.
Definition base.h:90
@ REDIS_ASYNC_RCODE_TRY_AGAIN
Try the operation again.
Definition base.h:86
@ REDIS_ASYNC_RCODE_NO_SCRIPT
Script doesn't exist.
Definition base.h:89
@ REDIS_ASYNC_RCODE_SUCCESS
Operation was successful.
Definition base.h:81
void fr_redis_version_print(void)
Print the version of libhiredis the server was built against.
Definition redis.c:107
fr_table_num_sorted_t const redis_reply_types[]
Definition redis.c:31
@ REDIS_RCODE_SUCCESS
Operation was successful.
Definition base.h:70
Configuration parameters for a redis connection.
Definition base.h:114
#define IPPOOL_STATIC_BIT
#define _IPPOOL_RCODE_NOT_FOUND
ippool_action_t
@ POOL_ACTION_RELEASE
@ POOL_ACTION_ALLOCATE
@ POOL_ACTION_UPDATE
#define IPPOOL_MAX_POOL_KEY_SIZE
{prefix}:pool
#define IPPOOL_OWNER_KEY
#define _IPPOOL_RCODE_POOL_EMPTY
ippool_rcode_t
@ IPPOOL_RCODE_EXPIRED
@ IPPOOL_RCODE_DEVICE_MISMATCH
@ IPPOOL_RCODE_NOT_FOUND
@ IPPOOL_RCODE_POOL_EMPTY
@ IPPOOL_RCODE_SUCCESS
@ IPPOOL_RCODE_FAIL
#define IPPOOL_POOL_KEY
#define _IPPOOL_RCODE_DEVICE_MISMATCH
#define _IPPOOL_RCODE_SUCCESS
#define IPPOOL_SPRINT_IP(_buff, _ip, _prefix)
If the prefix is as wide as the AF data size then print it without CIDR notation.
#define IPPOOL_ADDRESS_KEY
#define IPPOOL_BUILD_IP_KEY_FROM_STR(_buff, _p, _key, _key_len, _ip_str)
#define IPPOOL_BUILD_IP_KEY(_buff, _p, _key, _key_len, _ip)
Build the IP key {prefix}:ip.
#define IPPOOL_MAX_IP_KEY_SIZE
{prefix}:ipaddr/prefix
#define IPPOOL_BUILD_KEY(_buff, _p, _key, _key_len)
Wrap the prefix in {} and add the pool suffix.
static void ippool_action_print(request_t *request, ippool_action_t action, fr_log_lvl_t lvl, fr_value_box_t const *key_prefix, fr_value_box_t const *ip, fr_value_box_t const *owner, fr_value_box_t const *gateway_id, uint32_t expires)
fr_dict_attr_autoload_t rlm_redis_ippool_dict_attr[]
tmpl_t * expiry_attr
Time at which the lease will expire.
static fr_dict_attr_t const * attr_ippool_stats_dynamic_expire1d
static void ippool_wait_check(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
Check the requisite number of slaves replicated the lease info.
fr_redis_command_set_t * cmds
Redis command set to run.
static unlang_action_t mod_release_resume(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
#define ADD_REDIS_COMMAND(_fmt,...)
static int mod_detach(module_detach_ctx_t const *mctx)
static int mod_load(void)
static char lua_remove_cmd[]
Lua script for removing a lease.
unlang_module_signal_t cancel
Cancel function for EVAL command.
static void redis_ippool_update_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
Callback to process results from allocation script.
ippool_rcode_t ret
Return code for the allocation result.
fr_value_box_list_t pools
Temporary list to store pool names.
fr_value_box_list_t requested_address
Address(es) to fetch details of.
static char lua_update_cmd[]
Lua script for updating leases.
fr_redis_async_cmd_t * cmd
Redis async command.
static fr_dict_attr_t const * attr_ippool_stats_static_renew1m
static fr_dict_attr_t const * attr_ippool_stats_static
static unlang_action_t mod_show_resume(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
static void lua_script_load_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, UNUSED void *rctx)
fr_redis_command_set_t * cmds
Command set to be run.
fr_value_box_t pool_name
Name of the pool we're allocating IP addresses from.
static xlat_action_t redis_ippool_add_common(request_t *request, rlm_redis_ippool_t *inst, rlm_redis_ippool_thread_t *t, fr_value_box_t *pool, fr_value_box_t *start, fr_value_box_t *end, fr_value_box_t *range, uint8_t prefix, size_t num_addr, size_t step)
Common code for adding addresses to a pool.
char cursor[19]
Cursor value returned in last result.
static fr_dict_attr_t const * attr_ippool_stats_static_renew1h
static unlang_action_t ippool_script_enqueue(TALLOC_CTX *ctx, fr_redis_command_set_t **out_cmds, fr_redis_async_cmd_t **out_cmd, request_t *request, rlm_redis_ippool_thread_t *thread, uint8_t const *key, size_t key_len, char const *cmd, int cmd_len, fr_redis_command_complete_t complete, module_method_t resume, unlang_module_signal_t cancel, void *rctx, redis_wait_rctx_t *wait_rctx)
Enqueue a script to run against a redis cluster.
static char lua_release_digest[(SHA1_DIGEST_LENGTH *2)+1]
static fr_dict_attr_t const * attr_ippool_stats_dynamic_free
static const call_env_method_t redis_ippool_stats_method_env
static void ipaddr_inc(fr_ipaddr_t *addr, size_t inc)
Increment an IP address by a given number of addresses.
static fr_dict_attr_t const * attr_ippool_stats_static_renew30m
static int mod_coord_attach(module_thread_inst_ctx_t const *mctx)
static char lua_assign_cmd[]
Lua script for assigning a static lease.
module_method_t resume
Resume function for EVAL command.
static void redis_xlat_array_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
Callback to be used when Redis commands are expected to return an array.
static char lua_alloc_digest[(SHA1_DIGEST_LENGTH *2)+1]
char const * name
Instance name.
fr_value_box_t gateway_id
Gateway identifier, usually NAS-Identifier or Option 82 gateway.
tmpl_t * range_attr
Attribute to write the range ID to.
module_rlm_t rlm_redis_ippool
fr_redis_ct_t * rtcluster
Per thread Redis cluster.
static xlat_action_t redis_ippool_addresses_remove_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
fr_redis_io_conf_t * nodes
List of nodes to query.
static fr_dict_attr_t const * attr_ippool_stats_dynamic_expire1m
fr_redis_command_set_t * cmds
Command set to be run.
static int _redis_ippool_update_rctx_free(redis_ippool_update_rctx_t *rctx)
static xlat_action_t redis_ippool_list_start(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, UNUSED fr_value_box_list_t *in)
fr_value_box_t offer_time
How long we should reserve a lease for during the pre-allocation stage (typically responding to DHCP ...
static fr_dict_attr_t const * attr_ippool_lease_device
static xlat_action_t redis_ippool_addresses_modify_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
fr_coord_pair_reg_t * coord_pair_reg
Coord pair registration.
static unlang_action_t redis_ippool_load_resume(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
static void redis_ippool_common_cancel(xlat_ctx_t const *xctx, request_t *request, UNUSED fr_signal_t action)
Common cancellation function for xlats using redi_ippool_tool_rctx_t.
uint8_t current_node
Node number currently being queried.
fr_value_box_t gateway_id
Gateway identifier, usually NAS-Identifier or Option 82 gateway.
redis_ippool_release_call_env_t * env
Callenv for the current allocation.
char * cmd_str
Formatted redis command.
static xlat_action_t redis_ippool_remove_common(request_t *request, rlm_redis_ippool_t *inst, rlm_redis_ippool_thread_t *t, fr_value_box_t *pool, fr_value_box_t *start, fr_value_box_t *end, uint8_t prefix, size_t num_addr, size_t step)
Common code for removing addresses from a pool.
static fr_dict_attr_t const * attr_ippool_stats_dynamic_expire30m
static xlat_action_t redis_ippool_assign_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
static fr_dict_attr_t const * attr_ippool_lease_gateway
fr_redis_command_set_t * cmds
Command set to be run.
static xlat_action_t redis_ippool_subnet_modify_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
static unlang_action_t mod_update(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
static unlang_action_t mod_bulk_release(unlang_result_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request)
static unlang_action_t mod_alloc(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
static fr_dict_t const * dict_freeradius
static void redis_ippool_list_cancel(xlat_ctx_t const *xctx, request_t *request, UNUSED fr_signal_t action)
char ** cmd_str
Formatted redis commands.
static void redis_ippool_release_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
Callback to process results from release script.
#define EOL
static fr_dict_attr_t const * attr_ippool_stats_static_total
static const call_env_method_t redis_ippool_update_method_env
fr_value_box_t owner
Unique lease owner identifier.
static xlat_action_t redis_ippool_subnet_remove_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
static void mod_alloc_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
static void lua_script_load(fr_redis_trunk_t *rtrunk, UNUSED void *uctx)
static void mod_info_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
static const call_env_method_t redis_ippool_show_method_env
static fr_dict_attr_t const * attr_ippool_name
ippool_rcode_t ret
Return code for the allocation result.
static fr_dict_attr_t const * attr_ippool_stats_static_free
static fr_dict_attr_t const * attr_ippool_lease
fr_value_box_t pool_name
Name of the pool we're allocating IP addresses from.
rlm_redis_ippool_t * inst
Module instance.
char * cmd_str
Current preformatted Redis command.
static xlat_arg_parser_t const redis_ippool_subnet_remove_args[]
static uint32_t uint32_gen_mask(uint8_t bits)
fr_value_box_t association_time
How long should a device be associated with an IP address.
static fr_dict_attr_t const * attr_ippool_lease_active
static xlat_arg_parser_t const redis_ippool_subnet_modify_args[]
static xlat_arg_parser_t const redis_ippool_addresses_remove_args[]
static unlang_action_t mod_release(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
static int mod_bootstrap(module_inst_ctx_t const *mctx)
static xlat_arg_parser_t const redis_ippool_subnet_add_args[]
fr_value_box_t pool_name
Name of the pool we're allocating IP addresses from.
ippool_rcode_t ret
Return code for the allocation result.
static char lua_unassign_cmd[]
Lua script for un-assigning a static lease.
fr_redis_command_set_t * cmds
Command set for loading script.
#define POPULATE_PAIR(_da, _i, _parent)
fr_value_box_t lease_time
How long an IP address should be allocated for.
static xlat_action_t redis_ippool_list_resume(UNUSED TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, UNUSED request_t *request, UNUSED fr_value_box_list_t *in)
fr_value_box_t gateway_id
Gateway identifier, usually NAS-Identifier or Option 82 gateway.
static conf_parser_t redis_config[]
fr_value_box_t requested_address
Address being updated.
fr_value_box_t pool_name
Name of the pool we're looking up the address in.
static xlat_arg_parser_t const redis_ippool_addresses_release_args[]
fr_redis_async_cmd_t * cmd
Redis async command.
static xlat_action_t redis_ippool_list_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
fr_dict_autoload_t rlm_redis_ippool_dict[]
static int _redis_ippool_release_rctx_free(redis_ippool_release_rctx_t *rctx)
static xlat_action_t redis_ippool_subnet_release_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
static int redis_ippool_addresses_arg_parse(request_t *request, uint8_t *prefix_out, size_t *step, size_t *num_addr, fr_value_box_t *start, fr_value_box_t *end, fr_value_box_t *prefix_in)
Parse argments provided to IP pool manipulation xlats which work on start and end addresses.
static xlat_action_t redis_ippool_list_next_scan(request_t *request, rlm_redis_ippool_thread_t *thread, redis_pools_list_rctx_t *rctx)
Enqueue the next SCAN command.
static void redis_ippool_allocate_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
Callback to process results from allocation script.
fr_value_box_t requested_address
The address the client is requesting.
static fr_dict_attr_t const * attr_ippool_stats_dynamic
fr_value_box_t association_time
How long should a device be associated with an IP address.
redis_ippool_update_call_env_t * env
Callenv for the current allocation.
static int _redis_ippool_info_rctx_free(redis_ippool_info_rctx_t *rctx)
char * cmd_str
Formatted redis command.
fr_value_box_t ** lookup
Value boxes holding lookup keys.
redis_wait_rctx_t wait_rctx
WAIT resume context.
static char lua_release_cmd[]
Lua script for releasing leases.
static xlat_action_t redis_ippool_unassign_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
static fr_dict_t const * dict_redis
fr_redis_conf_t conf
Connection parameters for the Redis server.
fr_coord_reg_t * coord_reg
Coordinator registration.
static xlat_action_t redis_ippool_release_common(request_t *request, rlm_redis_ippool_t *inst, rlm_redis_ippool_thread_t *t, fr_value_box_t *pool, fr_value_box_t *start, fr_value_box_t *end, uint8_t prefix, size_t num_addr, size_t step)
Common code for releasing address assignments.
uint32_t changes
Number of changes reported by redis.
static unlang_action_t mod_alloc_resume(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
static void mod_release_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
static xlat_action_t redis_ippool_addresses_add_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
uint32_t wait_num
How many slaves we want to acknowledge allocations or updates.
static xlat_arg_parser_t const redis_ippool_assign_args[]
redis_wait_rctx_t wait_rctx
WAIT resume context.
static const call_env_method_t redis_ippool_release_method_env
static fr_dict_attr_t const * attr_ippool_lease_address
static fr_dict_attr_t const * attr_ippool_lease_expires
CONF_SECTION * tls_conf
TLS CONF_SECTION.
static int redis_ippool_subnet_arg_parse(request_t *request, fr_value_box_t *start, fr_value_box_t *end, uint8_t *prefix_out, size_t *step, size_t *num_addr, fr_value_box_t *subnet, fr_value_box_t *prefix_in)
Parse argments provided to IP pool manipulation xlats which work on subnets.
fr_time_delta_t wait_timeout
How long we wait for slaves to acknowledge writing.
fr_redis_async_cmd_t * cmd
Async command for loading script.
size_t lookup_no
Incremented on processing each reply.
fr_pair_list_t results
Temporary list to hold results.
bool copy_on_update
Copy the address provided by ip_address to the allocated_address_attr if updates are successful.
fr_redis_async_cmd_t * cmd
Redis async command.
static void mod_stats_result(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
fr_value_box_t association_time
How log should a device be associated with an IP address.
static char lua_release_xlat_cmd[]
Lua script for releasing a lease.
tmpl_t * expiry_attr
Time at which the lease will expire.
static char lua_alloc_cmd[]
Lua script for allocating new leases.
static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
tmpl_t * range_attr
Attribute to write the range ID to.
fr_coord_worker_t * cw
Coord-worker for fetching cluster map.
static void mod_show_result(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
static xlat_arg_parser_t const redis_ippool_subnet_release_args[]
static xlat_arg_parser_t const redis_ippool_addresses_add_args[]
#define REDIS_IPPOOL_SCRIPT_LOAD(_cmds, _script, _script_len, _ret)
static void mod_update_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
char ** cmd_str
Formatted redis commands for this xlat.
fr_redis_async_cmd_t * cmd
Redis async command.
static void redis_ippool_load_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
tmpl_t * allocated_address_attr
Attribute to populate with allocated IP.
redis_ippool_alloc_call_env_t * env
Callenv for the current allocation.
static xlat_action_t redis_ippool_common_resume(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, UNUSED request_t *request, UNUSED fr_value_box_list_t *in)
Common resume function for pool manipulation xlats.
bool ipv4_integer
Whether IPv4 addresses should be cast to integers, for renew operations.
static xlat_action_t redis_ippool_subnet_add_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
fr_value_box_t owner
Unique lease owner identifier.
fr_redis_command_set_t * cmds
Command set to be run.
tmpl_t * allocated_address_attr
Attribute to populate with allocated IP.
static fr_dict_attr_t const * attr_ippool_lease_range
static unlang_action_t mod_show(unlang_result_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request)
fr_redis_async_cmd_t * cmd
Redis async command.
static xlat_action_t redis_ippool_modify_common(request_t *request, rlm_redis_ippool_t *inst, rlm_redis_ippool_thread_t *t, fr_value_box_t *pool, fr_value_box_t *start, fr_value_box_t *end, fr_value_box_t *range, uint8_t prefix, size_t num_addr, size_t step)
Common code for modifying the range assigned to addresses.
fr_value_box_t gateway_id
Gateway identifier, usually NAS-Identifier or Option 82 gateway.
#define CHECK_POOL_NAME
TALLOC_CTX * ctx
Context to allocate pool names in.
static fr_dict_attr_t const * attr_ippool_stats
static fr_dict_attr_t const * attr_ippool_stats_dynamic_expire1h
static fr_dict_attr_t const * attr_ippool_stats_static_renew1d
fr_value_box_t pool_name
Name of the pool we're getting stats for.
static unlang_action_t mod_update_resume(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
uint8_t node_count
How many nodes are in nodes.
fr_value_box_t lease_time
How long an IP address should be allocated for.
static xlat_arg_parser_t const redis_ippool_unassign_args[]
static xlat_action_t redis_ippool_addresses_release_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
void * eval_rctx
Resume context for original EVAL command.
static int _redis_pools_list_rctx_free(redis_pools_list_rctx_t *rctx)
static unlang_action_t mod_stats(unlang_result_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request)
fr_redis_command_set_t * cmds
Command set to run.
char * wait_cmd
Preformatted redis "WAIT" command.
fr_redis_async_cmd_t * cmd
Redis async command.
static fr_dict_attr_t const * attr_ippool_stats_dynamic_total
static int _redis_ippool_tool_rctx_free(redis_ippool_tool_rctx_t *rctx)
fr_redis_async_cmd_t * eval_cmd
Original EVAL async command which returned NOSCRIPT.
static unlang_action_t redis_ippool_rcode_check(request_t *request, fr_redis_command_set_t *cmds, fr_redis_async_cmd_t *cmd, module_ctx_t const *mctx, module_method_t resume, unlang_module_signal_t cancel, char const *script, size_t script_len)
Check the return code from an async redis command.
static void mod_pools_list_result(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
Parse the results from Redis SCAN command used to list pools.
static int mod_thread_detach(module_thread_inst_ctx_t const *mctx)
static int _redis_ippool_alloc_ctx_free(redis_ippool_alloc_rctx_t *rctx)
static int mod_instantiate(module_inst_ctx_t const *mctx)
fr_value_box_t pool_name
Name of the pool we're releasing the IP addresses to.
static const call_env_method_t redis_ippool_bulk_release_method_env
static char lua_update_digest[(SHA1_DIGEST_LENGTH *2)+1]
fr_value_box_t owner
Unique lease owner identifier.
static conf_parser_t module_config[]
static void redis_xlat_common_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
Callback to be used when Redis commands are expected to return a single integer.
static xlat_arg_parser_t const redis_ippool_addresses_modify_args[]
int wait_cmd_len
Length of wait_cmd.
redis_wait_rctx_t wait_rctx
WAIT resume context.
static fr_dict_attr_t const * attr_ippool_stats_total
fr_value_box_t requested_address
Address being released.
static const call_env_method_t redis_ippool_alloc_method_env
char * cmd_str
Formatted redis command.
Call environment used when calling redis_ippool allocate method.
Resume context for async calls to alloc script.
Call environment used when calling redis_ippool bulk release method.
Resume context for methods which retrieve info and produce pairs.
Resume context for loading scripts in response to NOSCRIPT reply.
Call environment used when calling redis_ippool release method.
Resume context for async calls to update script.
Call environment used when calling redis_ippool show method.
Call environment used when calling redis_ippool stats method.
Resume context for IP pool updating xlats.
Call environment used when calling redis_ippool update method.
Resume context for async calls to update script.
Resume context for pool list module method.
Resume context for validating WAIT replies.
rlm_redis module instance
#define FR_SBUFF_OUT(_start, _len_or_end)
int fr_schedule_worker_id(void)
Return the worker id for the current thread.
Definition schedule.c:110
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition section.h:39
#define MODULE_THREAD_INST(_ctype)
Definition module.h:258
char const * name
Instance name e.g. user_database.
Definition module.h:357
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:351
size_t inst_size
Size of the module's instance data.
Definition module.h:212
void * data
Module's instance data.
Definition module.h:293
unlang_action_t(* module_method_t)(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
Module section callback.
Definition module.h:69
void * boot
Data allocated during the boostrap phase.
Definition module.h:296
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:152
Named methods exported by a module.
Definition module.h:174
#define tmpl_value(_tmpl)
Definition tmpl.h:937
@ TMPL_TYPE_DATA
Value in native boxed format.
Definition tmpl.h:138
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition tmpl.h:801
tmpl_t * tmpl_init_shallow(tmpl_t *vpt, tmpl_type_t type, fr_token_t quote, char const *name, ssize_t len, tmpl_rules_t const *t_rules))
Initialise a tmpl without copying the input name string.
void fr_sha1_init(fr_sha1_ctx *context)
Definition sha1.c:93
void fr_sha1_final(uint8_t digest[static SHA1_DIGEST_LENGTH], fr_sha1_ctx *context)
Definition sha1.c:141
void fr_sha1_update(fr_sha1_ctx *context, uint8_t const *in, size_t len)
Definition sha1.c:105
#define SHA1_DIGEST_LENGTH
Definition sha1.h:29
fr_signal_t
Signals that can be generated/processed by request signal handlers.
Definition signal.h:38
@ FR_SIGNAL_CANCEL
Request has been cancelled.
Definition signal.h:40
unlang_action_t unlang_module_yield(request_t *request, module_method_t resume, unlang_module_signal_t signal, fr_signal_t sigmask, void *rctx)
Yield a request back to the interpreter from within a module.
Definition module.c:431
eap_aka_sim_process_conf_t * inst
fr_pair_t * vp
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition strlcpy.c:34
Value pair map.
Definition map.h:77
tmpl_t * lhs
Typically describes the attribute to add, modify or compare.
Definition map.h:78
tmpl_t * rhs
Typically describes a literal value or a src attribute to copy or compare.
Definition map.h:79
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition table.h:804
#define talloc_get_type_abort_const
Definition talloc.h:117
static int64_t fr_time_to_sec(fr_time_t when)
Convert an fr_time_t (internal time) to number of sec since the unix epoch (wallclock time)
Definition time.h:731
static int64_t fr_unix_time_to_sec(fr_unix_time_t delta)
Definition time.h:506
#define fr_time_to_timeval(_when)
Convert server epoch time to unix epoch time.
Definition time.h:742
static int64_t fr_time_delta_to_msec(fr_time_delta_t delta)
Definition time.h:637
A time delta, a difference in time measured in nanoseconds.
Definition time.h:80
"server local" time.
Definition time.h:69
@ T_SINGLE_QUOTED_STRING
Definition token.h:120
@ T_BARE_WORD
Definition token.h:118
@ T_OP_SET
Definition token.h:82
@ T_DOUBLE_QUOTED_STRING
Definition token.h:119
static uint128_t uint128_gen_mask(uint8_t bits)
Create a 128 bit integer value with n bits high.
Definition uint128.h:65
static uint128_t uint128_bor(uint128_t a, uint128_t b)
Perform bitwise | of two 128bit unsigned integers.
Definition uint128.h:239
static uint128_t uint128_lshift(uint128_t num, uint8_t bits)
Left shift 128 bit integer.
Definition uint128.h:191
static uint128_t uint128_sub(uint128_t a, uint128_t b)
Subtract one 128bit integer from another.
Definition uint128.h:128
static uint128_t uint128_add(uint128_t a, uint128_t b)
Add two 128bit unsigned integers.
Definition uint128.h:115
void(* unlang_module_signal_t)(module_ctx_t const *mctx, request_t *request, fr_signal_t action)
A callback when the request gets a fr_signal_t.
Definition module.h:81
xlat_action_t unlang_xlat_yield(request_t *request, xlat_func_t resume, xlat_func_signal_t signal, fr_signal_t sigmask, void *rctx)
Yield a request back to the interpreter from within a module.
Definition xlat.c:543
#define XLAT_ARGS(_list,...)
Populate local variables with value boxes from the input list.
Definition xlat.h:384
unsigned int required
Argument must be present, and non-empty.
Definition xlat.h:147
#define XLAT_ARG_PARSER_TERMINATOR
Definition xlat.h:171
xlat_action_t
Definition xlat.h:37
@ XLAT_ACTION_FAIL
An xlat function failed.
Definition xlat.h:44
@ XLAT_ACTION_DONE
We're done evaluating this level of nesting.
Definition xlat.h:43
Definition for a single argument consumed by an xlat function.
Definition xlat.h:146
void fr_pair_list_append(fr_pair_list_t *dst, fr_pair_list_t *src)
Appends a list of fr_pair_t from a temporary list to a destination list.
fr_pair_t * fr_pair_list_head(fr_pair_list_t const *list)
Get the head of a valuepair list.
Definition pair_inline.c:42
size_t fr_pair_list_num_elements(fr_pair_list_t const *list)
Get the length of a list of fr_pair_t.
char * fr_asprint(TALLOC_CTX *ctx, char const *in, ssize_t inlen, char quote)
Escape string that may contain binary data, and write it to a new buffer.
Definition print.c:438
ssize_t fr_value_box_print(fr_sbuff_t *out, fr_value_box_t const *data, fr_sbuff_escape_rules_t const *e_rules)
Print one boxed value to a string.
Definition value.c:6131
int fr_value_box_cast(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, fr_value_box_t const *src)
Convert one type of fr_value_box_t to another.
Definition value.c:3968
int fr_value_box_copy(TALLOC_CTX *ctx, fr_value_box_t *dst, const fr_value_box_t *src)
Copy value data verbatim duplicating any buffers.
Definition value.c:4416
int fr_value_box_cast_in_place(TALLOC_CTX *ctx, fr_value_box_t *vb, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv)
Convert one type of fr_value_box_t to another in place.
Definition value.c:4218
ssize_t fr_value_box_from_str(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, char const *in, size_t inlen, fr_sbuff_unescape_rules_t const *erules)
Definition value.c:6094
int fr_value_box_strdup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, bool tainted)
Copy a nul terminated string to a fr_value_box_t.
Definition value.c:4643
int fr_value_box_bstrndup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, size_t len, bool tainted)
Copy a string to to a fr_value_box_t.
Definition value.c:4862
void fr_value_box_bstrndup_shallow(fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, size_t len, bool tainted)
Assign a string to to a fr_value_box_t.
Definition value.c:4949
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition value.h:644
#define fr_value_box(_box, _var, _tainted)
Automagically fill in a box, determining the value type from the type of the C variable.
Definition value.h:904
int nonnull(2, 5))
#define fr_value_box_init(_vb, _type, _enumv, _tainted)
Initialise a fr_value_box_t.
Definition value.h:610
#define fr_value_box_list_foreach(_list_head, _iter)
Definition value.h:224
static size_t char ** out
Definition value.h:1030
void * rctx
Resume context.
Definition xlat_ctx.h:54
xlat_exp_t const * ex
Tokenized expression.
Definition xlat_ctx.h:55
void const * inst
xlat instance data.
Definition xlat_ctx.h:50
#define XLAT_CTX(_inst, _thread, _ex, _mctx, _env_data, _rctx)
Wrapper to create a xlat_ctx_t as a compound literal.
Definition xlat_ctx.h:95
module_ctx_t const * mctx
Synthesised module calling ctx.
Definition xlat_ctx.h:52
An xlat calling ctx.
Definition xlat_ctx.h:49
void xlat_func_flags_set(xlat_t *x, xlat_func_flags_t flags)
Specify flags that alter the xlat's behaviour.
Definition xlat_func.c:401
int xlat_func_args_set(xlat_t *x, xlat_arg_parser_t const args[])
Register the arguments of an xlat.
Definition xlat_func.c:374
@ XLAT_FUNC_FLAG_MODULE_STATUS
Definition xlat_func.h:40