The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
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: 888f0b6bb0c20f99e28a59551a596fb256cffa83 $
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: 888f0b6bb0c20f99e28a59551a596fb256cffa83 $")
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
93
96 { .out = &attr_ippool_name, .name = "IP-Pool.Name", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
97 { .out = &attr_ippool_lease, .name = "IP-Pool.Lease", .type = FR_TYPE_TLV, .dict = &dict_freeradius },
98 { .out = &attr_ippool_lease_address, .name = "IP-Pool.Lease.Address", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
99 { .out = &attr_ippool_lease_active, .name = "IP-Pool.Lease.Active", .type = FR_TYPE_BOOL, .dict = &dict_freeradius },
100 { .out = &attr_ippool_lease_expires, .name = "IP-Pool.Lease.Expires", .type = FR_TYPE_DATE, .dict = &dict_freeradius },
101 { .out = &attr_ippool_lease_device, .name = "IP-Pool.Lease.Device", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
102 { .out = &attr_ippool_lease_gateway, .name = "IP-Pool.Lease.Gateway", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
103 { .out = &attr_ippool_lease_range, .name = "IP-Pool.Lease.Range", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
104 { .out = &attr_ippool_lease_static, .name = "IP-Pool.Lease.Static", .type = FR_TYPE_BOOL, .dict = &dict_freeradius },
105 { .out = &attr_ippool_stats, .name = "IP-Pool.Stats", .type = FR_TYPE_TLV, .dict = &dict_freeradius },
106 { .out = &attr_ippool_stats_total, .name = "IP-Pool.Stats.Total", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
107 { .out = &attr_ippool_stats_dynamic, .name = "IP-Pool.Stats.Dynamic", .type = FR_TYPE_TLV, .dict = &dict_freeradius },
108 { .out = &attr_ippool_stats_dynamic_total, .name = "IP-Pool.Stats.Dynamic.Total", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
109 { .out = &attr_ippool_stats_dynamic_free, .name = "IP-Pool.Stats.Dynamic.Free", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
110 { .out = &attr_ippool_stats_dynamic_expire1m, .name = "IP-Pool.Stats.Dynamic.Expire-1m", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
111 { .out = &attr_ippool_stats_dynamic_expire30m, .name = "IP-Pool.Stats.Dynamic.Expire-30m", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
112 { .out = &attr_ippool_stats_dynamic_expire1h, .name = "IP-Pool.Stats.Dynamic.Expire-1h", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
113 { .out = &attr_ippool_stats_dynamic_expire1d, .name = "IP-Pool.Stats.Dynamic.Expire-1d", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
114 { .out = &attr_ippool_stats_static, .name = "IP-Pool.Stats.Static", .type = FR_TYPE_TLV, .dict = &dict_freeradius },
115 { .out = &attr_ippool_stats_static_total, .name = "IP-Pool.Stats.Static.Total", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
116 { .out = &attr_ippool_stats_static_free, .name = "IP-Pool.Stats.Static.Free", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
117 { .out = &attr_ippool_stats_static_renew1m, .name = "IP-Pool.Stats.Static.Renew-1m", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
118 { .out = &attr_ippool_stats_static_renew30m, .name = "IP-Pool.Stats.Static.Renew-30m", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
119 { .out = &attr_ippool_stats_static_renew1h, .name = "IP-Pool.Stats.Static.Renew-1h", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
120 { .out = &attr_ippool_stats_static_renew1d, .name = "IP-Pool.Stats.Static.Renew-1d", .type = FR_TYPE_UINT64, .dict = &dict_freeradius },
122};
123
124/** rlm_redis module instance
125 *
126 */
127typedef struct {
128 fr_redis_conf_t conf; //!< Connection parameters for the Redis server.
129 //!< Must be first field in this struct.
130
131
132 char const *name; //!< Instance name.
133
134 uint32_t wait_num; //!< How many slaves we want to acknowledge allocations
135 //!< or updates.
136
137 fr_time_delta_t wait_timeout; //!< How long we wait for slaves to acknowledge writing.
138
139 char *wait_cmd; //!< Preformatted redis "WAIT" command.
140 int wait_cmd_len; //!< Length of wait_cmd
141
142 bool ipv4_integer; //!< Whether IPv4 addresses should be cast to integers,
143 //!< for renew operations.
144
145 bool copy_on_update; //!< Copy the address provided by ip_address to the
146 //!< allocated_address_attr if updates are successful.
147
148 fr_coord_reg_t *coord_reg; //!< Coordinator registration.
149 fr_coord_pair_reg_t *coord_pair_reg; //!< Coord pair registration.
151
152typedef struct {
153 rlm_redis_ippool_t *inst; //!< Module instance.
154 fr_redis_ct_t *rtcluster; //!< Per thread Redis cluster.
155 fr_coord_worker_t *cw; //!< Coord-worker for fetching cluster map.
157
162
164 { FR_CONF_OFFSET("wait_num", rlm_redis_ippool_t, wait_num) },
165 { FR_CONF_OFFSET("wait_timeout", rlm_redis_ippool_t, wait_timeout), .dflt = "1s" },
166
167 { FR_CONF_V3_DEPRECATED("ip_address", rlm_redis_ippool_t, NULL) },
168
169 { FR_CONF_V3_DEPRECATED("reply_attr", rlm_redis_ippool_t, NULL) },
170
171 { FR_CONF_OFFSET("ipv4_integer", rlm_redis_ippool_t, ipv4_integer) },
172 { FR_CONF_OFFSET("copy_on_update", rlm_redis_ippool_t, copy_on_update), .dflt = "yes", .quote = T_BARE_WORD },
173
174 /*
175 * Split out to allow conversion to universal ippool module with
176 * minimum of config changes.
177 */
178 { FR_CONF_POINTER("redis", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = redis_config },
180};
181
182/** Call environment used when calling redis_ippool allocate method.
183 *
184 */
185typedef struct {
186 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
187
188 fr_value_box_t offer_time; //!< How long we should reserve a lease for during
189 ///< the pre-allocation stage (typically responding
190 ///< to DHCP discover).
191
192 fr_value_box_t lease_time; //!< How long an IP address should be allocated for.
193
194 fr_value_box_t association_time; //!< How log should a device be associated with an IP address.
195 ///< This allows for "sticky" addressing, where the device -> IP
196 ///< association lasts longer than the lease time.
197
198 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
199 ///< or a combination of User-Name and something
200 ///< unique to the device.
201
202 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
203 ///< Option 82 gateway. Used for bulk lease cleanups.
204
205 fr_value_box_t requested_address; //!< The address the client is requesting.
206
207 tmpl_t *allocated_address_attr; //!< Attribute to populate with allocated IP.
208
209 tmpl_t *range_attr; //!< Attribute to write the range ID to.
210
211 tmpl_t *expiry_attr; //!< Time at which the lease will expire.
213
214/** Resume context for validating WAIT replies.
215 *
216 */
217typedef struct {
219 bool fail;
221
222/** Resume context for loading scripts in response to NOSCRIPT reply.
223 */
224typedef struct {
225 fr_redis_command_set_t *cmds; //!< Command set for loading script.
226 fr_redis_async_cmd_t *cmd; //!< Async command for loading script.
227 fr_redis_async_cmd_t *eval_cmd; //!< Original EVAL async command which returned NOSCRIPT.
228 void *eval_rctx; //!< Resume context for original EVAL command.
229 module_method_t resume; //!< Resume function for EVAL command.
230 unlang_module_signal_t cancel; //!< Cancel function for EVAL command.
232
233/** Resume context for async calls to alloc script
234 *
235 */
236typedef struct {
237 redis_ippool_alloc_call_env_t *env; //!< Callenv for the current allocation
238 char *cmd_str; //!< Formatted redis command
239 fr_redis_command_set_t *cmds; //!< Command set to be run.
240 fr_redis_async_cmd_t *cmd; //!< Redis async command.
241 ippool_rcode_t ret; //!< Return code for the allocation result.
242 redis_wait_rctx_t wait_rctx; //!< WAIT resume context.
244
245/** Call environment used when calling redis_ippool update method.
246 *
247 */
248typedef struct {
249 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
250
251 fr_value_box_t lease_time; //!< How long an IP address should be allocated for.
252
253 fr_value_box_t association_time; //!< How long should a device be associated with an IP address.
254
255 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
256 ///< or a combination of User-Name and something
257 ///< unique to the device.
258
259 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
260 ///< Option 82 gateway. Used for bulk lease cleanups.
261
262 fr_value_box_t requested_address; //!< Address being updated.
263
264 tmpl_t *allocated_address_attr; //!< Attribute to populate with allocated IP.
265
266 tmpl_t *range_attr; //!< Attribute to write the range ID to.
267
268 tmpl_t *expiry_attr; //!< Time at which the lease will expire.
270
271/** Resume context for async calls to update script
272 *
273 */
274typedef struct {
275 redis_ippool_update_call_env_t *env; //!< Callenv for the current allocation
276 char *cmd_str; //!< Formatted redis command
277 fr_redis_command_set_t *cmds; //!< Command set to be run.
278 fr_redis_async_cmd_t *cmd; //!< Redis async command.
279 ippool_rcode_t ret; //!< Return code for the allocation result.
280 redis_wait_rctx_t wait_rctx; //!< WAIT resume context.
282
283/** Call environment used when calling redis_ippool release method.
284 *
285 */
286typedef struct {
287 fr_value_box_t pool_name; //!< Name of the pool we're releasing the IP addresses to.
288
289 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
290 ///< or a combination of User-Name and something
291 ///< unique to the device.
292
293 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
294 ///< Option 82 gateway. Used for bulk lease cleanups.
295
296 fr_value_box_t requested_address; //!< Address being released.
297
298 fr_value_box_t association_time; //!< How long should a device be associated with an IP address.
299
301
302/** Call environment used when calling redis_ippool show method.
303 *
304 */
305typedef struct {
306 fr_value_box_t pool_name; //!< Name of the pool we're looking up the address in.
307
308 fr_value_box_list_t requested_address; //!< Address(es) to fetch details of.
310
311/** Resume context for methods which retrieve info and produce pairs.
312 */
313typedef struct {
314 char **cmd_str; //!< Formatted redis commands.
315 fr_redis_command_set_t *cmds; //!< Command set to run.
316 fr_redis_async_cmd_t *cmd; //!< Redis async command.
317 fr_value_box_t **lookup; //!< Value boxes holding lookup keys.
318 size_t lookup_no; //!< Incremented on processing each reply.
319 fr_pair_list_t results; //!< Temporary list to hold results.
321
322/** Call environment used when calling redis_ippool stats method.
323 */
324typedef struct {
325 fr_value_box_t pool_name; //!< Name of the pool we're getting stats for.
327
328/** Resume context for async calls to update script
329 *
330 */
331typedef struct {
332 redis_ippool_release_call_env_t *env; //!< Callenv for the current allocation
333 char *cmd_str; //!< Formatted redis command
334 fr_redis_command_set_t *cmds; //!< Command set to be run.
335 fr_redis_async_cmd_t *cmd; //!< Redis async command.
336 ippool_rcode_t ret; //!< Return code for the allocation result.
337 redis_wait_rctx_t wait_rctx; //!< WAIT resume context.
339
340/** Resume context for IP pool updating xlats
341 *
342 */
343typedef struct {
344 char **cmd_str; //!< Formatted redis commands for this xlat
345 fr_redis_command_set_t *cmds; //!< Redis command set to run
346 fr_redis_async_cmd_t *cmd; //!< Redis async command.
347 uint32_t changes; //!< How many changes are submitted.
348 uint32_t changed; //!< Number of changes reported by redis.
350
351/** Resume context for pool list module method
352 */
353typedef struct {
354 fr_redis_io_conf_t *nodes; //!< List of nodes to query.
355 uint8_t node_count; //!< How many nodes are in nodes.
356 uint8_t current_node; //!< Node number currently being queried.
357 char cursor[19]; //!< Cursor value returned in last result.
358 char *cmd_str; //!< Current preformatted Redis command.
359 fr_redis_command_set_t *cmds; //!< Command set to be run.
360 fr_redis_async_cmd_t *cmd; //!< Redis async command.
361 TALLOC_CTX *ctx; //!< Context to allocate pool names in.
362 fr_value_box_list_t pools; //!< Temporary list to store pool names.
364
365/** Call environment used when calling redis_ippool bulk release method.
366 *
367 */
368typedef struct {
369 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
370
371 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
372 ///< Option 82 gateway. Used for bulk lease cleanups.
374
377 .env = (call_env_parser_t[]){
379 redis_ippool_alloc_call_env_t, pool_name) },
383 redis_ippool_alloc_call_env_t, gateway_id ), .pair.dflt = "", .pair.dflt_quote = T_SINGLE_QUOTED_STRING },
388 .pair.dflt = "%{%{Requested-IP-Address} || %{Net.Src.IP}}", .pair.dflt_quote = T_DOUBLE_QUOTED_STRING },
391 .pair.dflt = "reply.IP-Pool.Range", .pair.dflt_quote = T_BARE_WORD },
394 }
395};
396
399 .env = (call_env_parser_t[]) {
403 .pair.dflt = "", .pair.dflt_quote = T_SINGLE_QUOTED_STRING },
407 .pair.dflt = "%{Requested-IP-Address || Net.Src.IP}", .pair.dflt_quote = T_DOUBLE_QUOTED_STRING },
410 .pair.dflt = "reply.IP-Pool.Range", .pair.dflt_quote = T_BARE_WORD },
413 }
414};
415
429
439
449
457
458#define EOL "\n"
459
460/** Lua script for allocating new leases
461 *
462 * - KEYS[1] The pool name.
463 * - ARGV[1] Wall time (seconds since epoch).
464 * - ARGV[2] Expires in (seconds).
465 * - ARGV[3] Lease owner identifier (administratively configured).
466 * - ARGV[4] Device -> IP association time (seconds).
467 * - ARGV[5] (optional) Gateway identifier.
468 * - ARGV[6] (optional) Requested address.
469 *
470 * Returns @verbatim { <rcode>[, <ip>][, <range>][, <lease time>][, <counter>] } @endverbatim
471 * - IPPOOL_RCODE_SUCCESS lease updated..
472 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
473 */
474static char lua_alloc_cmd[] =
475 "local ip" EOL /* 1 */
476 "local exists" EOL /* 2 */
477
478 "local pool_key" EOL /* 3 */
479 "local address_key" EOL /* 4 */
480 "local owner_key" EOL /* 5 */
481
482 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 6 */
483 "owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[3]" EOL /* 7 */
484
485 "local wall_time = tonumber(ARGV[1])" EOL /* 8* */
486
487 /*
488 * Check to see if the client already has a lease,
489 * and if it does return that.
490 *
491 * The additional sanity checks are to allow for the record
492 * of device/ip binding to persist for longer than the lease.
493 */
494 "exists = redis.call('GET', owner_key)" EOL /* 9 */
495 "if exists then" EOL /* 10 */
496 " local expires = tonumber(redis.call('ZSCORE', pool_key, exists))" EOL /* 11 */
497 " local static = expires >= " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 12 */
498 " local expires_in = expires - (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0) - ARGV[1]" EOL /* 13 */
499 " ip = redis.call('HMGET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. exists, 'device', 'range', 'counter', 'gateway')" EOL /* 14 */
500 " if ip and (ip[1] == ARGV[3]) then" EOL /* 15 */
501 " if expires_in < tonumber(ARGV[2]) then" EOL /* 16 */
502 " redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2] + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), exists)" EOL /* 17 */
503 " expires_in = tonumber(ARGV[2])" EOL /* 18 */
504 " if not static then" EOL /* 19 */
505 " redis.call('EXPIRE', owner_key, ARGV[4])" EOL /* 20 */
506 " end" EOL /* 21 */
507 " end" EOL /* 22 */
508
509 /*
510 * Ensure gateway is set correctly
511 */
512 " if ARGV[5] ~= ip[4] then" EOL /* 23 */
513 " redis.call('HSET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":', 'gateway', ARGV[5])" EOL /* 24 */
514 " end" EOL /* 25 */
515 " return {" STRINGIFY(_IPPOOL_RCODE_SUCCESS) ", exists, ip[2], expires_in, ip[3] }" EOL /* 26 */
516 " end" EOL /* 27 */
517 " ip = nil" EOL /* 28 */
518 "end" EOL /* 29 */
519
520 /*
521 * If there's a requested address, check if that is available i.e. not statically
522 * assigned, nor already allocated.
523 */
524 "if ARGV[6] and ARGV[6] ~= '' then" EOL /* 30 */
525 " local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[6]))" EOL /* 31 */
526 " if expires and tonumber(expires) < wall_time then" EOL /* 32 */
527 " ip = { ARGV[6] }" EOL /* 33 */
528 " end" EOL /* 34 */
529 "end" EOL /* 35 */
530
531 /*
532 * Else, get the IP address which expired the longest time ago.
533 */
534 "if not ip then" EOL /* 36 */
535 " ip = redis.call('ZREVRANGE', pool_key, -1, -1, 'WITHSCORES')" EOL /* 37 */
536 " if not ip or not ip[1] then" EOL /* 38 */
537 " return {" STRINGIFY(_IPPOOL_RCODE_POOL_EMPTY) "}" EOL /* 39 */
538 " end" EOL /* 40 */
539 " if tonumber(ip[2]) >= wall_time then" EOL /* 41 */
540 " return {" STRINGIFY(_IPPOOL_RCODE_POOL_EMPTY) "}" EOL /* 42 */
541 " end" EOL /* 43 */
542 "end" EOL /* 44 */
543 "redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2], ip[1])" EOL /* 45 */
544
545 /*
546 * Set the device/gateway keys
547 */
548 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ip[1]" EOL /* 45 */
549 "redis.call('HMSET', address_key, 'device', ARGV[3], 'gateway', ARGV[5])" EOL /* 46 */
550 "redis.call('SET', owner_key, ip[1])" EOL /* 47 */
551 "redis.call('EXPIRE', owner_key, ARGV[4])" EOL /* 48 */
552 "return { " EOL /* 49 */
553 " " STRINGIFY(_IPPOOL_RCODE_SUCCESS) "," EOL /* 50 */
554 " ip[1], " EOL /* 51 */
555 " redis.call('HGET', address_key, 'range'), " EOL /* 52 */
556 " tonumber(ARGV[2]), " EOL /* 53 */
557 " redis.call('HINCRBY', address_key, 'counter', 1)" EOL /* 54 */
558 "}" EOL; /* 55 */
560
561/** Lua script for updating leases
562 *
563 * - KEYS[1] The pool name.
564 * - ARGV[1] Wall time (seconds since epoch).
565 * - ARGV[2] Expires in (seconds).
566 * - ARGV[3] IP address to update.
567 * - ARGV[4] Lease owner identifier.
568 * - ARGV[5] Device -> IP association time (seconds).
569 * - ARGV[6] (optional) Gateway identifier.
570 *
571 * Returns @verbatim array { <rcode>[, <range>] } @endverbatim
572 * - IPPOOL_RCODE_SUCCESS lease updated..
573 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
574 * - IPPOOL_RCODE_EXPIRED lease has already expired.
575 * - IPPOOL_RCODE_DEVICE_MISMATCH lease was allocated to a different client.
576 */
577static char lua_update_cmd[] =
578 "local ret" EOL /* 1 */
579 "local found" EOL /* 2 */
580
581 "local pool_key" EOL /* 3 */
582 "local address_key" EOL /* 4 */
583 "local owner_key" EOL /* 5 */
584
585 /*
586 * We either need to know that the IP was last allocated to the
587 * same device, or that the lease on the IP has NOT expired.
588 */
589 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[3]" EOL /* 6 */
590 "found = redis.call('HMGET', address_key, 'range', 'device', 'gateway', 'counter' )" EOL /* 7 */
591 /*
592 * Range may be nil (if not used), so we use the device key
593 */
594 "if not found[2] then" EOL /* 8 */
595 " return {" STRINGIFY(_IPPOOL_RCODE_NOT_FOUND) "}" EOL /* 9 */
596 "end" EOL /* 10 */
597 "if found[2] ~= ARGV[4] then" EOL /* 11 */
598 " return {" STRINGIFY(_IPPOOL_RCODE_DEVICE_MISMATCH) ", found[2]}" EOL /* 12 */
599 "end" EOL /* 13 */
600
601 /*
602 * Update the expiry time
603 */
604 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 14 */
605 "local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[3]))" EOL /* 15 */
606 "local static = expires > " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 16 */
607 "redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2] + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), ARGV[3])" EOL /* 17 */
608
609 /*
610 * The device key should usually exist, but
611 * theoretically, if we were right on the cusp
612 * of a lease being expired, it may have been
613 * removed.
614 */
615 "owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[4]" EOL /* 18 */
616 "if not static and (redis.call('EXPIRE', owner_key, ARGV[2]) == 0) then" EOL /* 19 */
617 " redis.call('SET', owner_key, ARGV[3])" EOL /* 20 */
618 " redis.call('EXPIRE', owner_key, ARGV[5])" EOL /* 21 */
619 "end" EOL /* 22 */
620
621 /*
622 * Update the gateway address
623 */
624 "if ARGV[6] ~= found[3] then" EOL /* 23 */
625 " redis.call('HSET', address_key, 'gateway', ARGV[6])" EOL /* 24 */
626 "end" EOL /* 25 */
627 "return { " STRINGIFY(_IPPOOL_RCODE_SUCCESS) ", found[1], found[4] }"EOL; /* 26 */
629
630/** Lua script for releasing leases
631 *
632 * - KEYS[1] The pool name.
633 * - ARGV[1] Wall time (seconds since epoch).
634 * - ARGV[2] IP address to release.
635 * - ARGV[3] Client identifier.
636 * - ARGV[4] Device -> IP association time (seconds).
637 *
638 * Sets the expiry time to be NOW() - 1 to maximise time between
639 * IP address allocations.
640 *
641 * Returns @verbatim array { <rcode>[, <counter>] } @endverbatim
642 * - IPPOOL_RCODE_SUCCESS lease updated..
643 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
644 * - IPPOOL_RCODE_DEVICE_MISMATCH lease was allocated to a different client..
645 */
646static char lua_release_cmd[] =
647 "local ret" EOL /* 1 */
648 "local found" EOL /* 2 */
649
650 "local pool_key" EOL /* 3 */
651 "local address_key" EOL /* 4 */
652 "local owner_key" EOL /* 5 */
653
654 /*
655 * Check that the device releasing was the one
656 * the IP address is allocated to.
657 */
658 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[2]" EOL /* 6 */
659 "found = redis.call('HGET', address_key, 'device')" EOL /* 7 */
660 "if not found then" EOL /* 8 */
661 " return { " STRINGIFY(_IPPOOL_RCODE_NOT_FOUND) "}" EOL /* 9 */
662 "end" EOL /* 10 */
663 "if found and found ~= ARGV[3] then" EOL /* 11 */
664 " return { " STRINGIFY(_IPPOOL_RCODE_DEVICE_MISMATCH) ", found }" EOL /* 12 */
665 "end" EOL /* 13 */
666
667 /*
668 * Set expiry time to now() - 1
669 */
670 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 14 */
671 "found = tonumber(redis.call('ZSCORE', pool_key, ARGV[2]))" EOL /* 15 */
672 "local static = found > " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 16 */
673 "redis.call('ZADD', pool_key, 'XX', ARGV[1] - 1 + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), ARGV[2])" EOL /* 17 */
674
675 /*
676 * Remove the association between the device and a lease
677 * unless "sticky" addressing is in place where association_time
678 * is set, in which case use that to set the expiry of the association.
679 */
680 "if not static then" EOL /* 18 */
681 " owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[3]" EOL /* 19 */
682 " if tonumber(ARGV[4]) > 0 then" EOL /* 20 */
683 " redis.call('EXPIRE', owner_key, ARGV[4])" EOL /* 21 */
684 " else" EOL /* 22 */
685 " redis.call('DEL', owner_key)" EOL /* 24 */
686 " end" EOL /* 25 */
687 "end" EOL /* 26 */
688 "return { " EOL /* 27 */
689 " " STRINGIFY(_IPPOOL_RCODE_SUCCESS) "," EOL /* 28 */
690 " redis.call('HINCRBY', address_key, 'counter', 1) - 1" EOL /* 29 */
691 "}"; /* 30 */
693
694/** Lua script for removing a lease
695 *
696 * - KEYS[1] The pool name.
697 * - ARGV[1] IP address to remove.
698 *
699 * Removes the IP entry in the ZSET, then removes the address hash, and the device key
700 * if one exists.
701 *
702 * Will work with partially removed IP addresses (where the ZSET entry is absent but other
703 * elements weren't cleaned up).
704 *
705 * Returns
706 * - 0 if no ip addresses were removed.
707 * - 1 if an ip address was removed.
708 */
709static char lua_remove_cmd[] =
710 "local found" EOL /* 1 */
711 "local ret" EOL /* 2 */
712 "local address_key" EOL /* 3 */
713
714 "ret = redis.call('ZREM', '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"', ARGV[1])" EOL /* 4 */
715 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[1]" EOL /* 5 */
716 "found = redis.call('HGET', address_key, 'device')" EOL /* 6 */
717 "redis.call('DEL', address_key)" EOL /* 7 */
718 "if not found then" EOL /* 8 */
719 " return ret" EOL /* 9 */
720 "end" EOL /* 10 */
721
722 /*
723 * Remove the association between the device and a lease
724 */
725 "redis.call('DEL', '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. found)" EOL /* 11 */
726 "return 1" EOL; /* 12 */
727
728/** Lua script for releasing a lease
729 *
730 * - KEYS[1] The pool name.
731 * - ARGV[1] IP address to release.
732 *
733 * Removes the IP entry in the ZSET, then removes the address hash, and the device key
734 * if one exists.
735 *
736 * Will do nothing if the lease is not found in the ZSET.
737 *
738 * Returns
739 * - 0 if no ip addresses were removed.
740 * - 1 if an ip address was removed.
741 */
742static char lua_release_xlat_cmd[] =
743 "local found" EOL /* 1 */
744 "local ret" EOL /* 2 */
745
746 /*
747 * Set expiry time to 0
748 */
749 "ret = redis.call('ZADD', '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"', 'XX', 'CH', 0, ARGV[1])" EOL /* 3 */
750 "if ret == 0 then" EOL /* 4 */
751 " return 0" EOL /* 5 */
752 "end" EOL /* 6 */
753 "found = redis.call('HGET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":'"
754 " .. ARGV[1], 'device')" EOL /* 7 */
755 "if not found then" EOL /* 8 */
756 " return ret" EOL /* 9 */
757 "end" EOL /* 10 */
758
759 /*
760 * Remove the association between the device and a lease
761 */
762 "redis.call('DEL', '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. found)" EOL /* 11 */
763 "return 1"; /* 12 */
764
765/** Lua script for assigning a static lease
766 *
767 * - KEYS[1] The pool name.
768 * - ARGV[1] THE ip address to create a static assignment for.
769 * - ARGV[2] The owner to assign the static lease to.
770 * - ARGV[3] The range identifier.
771 * - ARGV[4] Wall time (seconds since epoch)
772 *
773 * Checks whether the IP already has a static assignment, and
774 * whether the owner is already associated with a different IP.
775 *
776 * If check pass, sets the static flag on the IP entry in the ZSET and
777 * creates the association between the IP and the owner.
778 *
779 * Returns
780 * - 0 if no assignment is made.
781 * - 1 if the IP assignment is made.
782 */
783static char lua_assign_cmd[] =
784 "local pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 1 */
785 "local owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[2]" EOL /* 2 */
786 "local ip_key = '{' .. KEYS[1]..'}:"IPPOOL_ADDRESS_KEY":' .. ARGV[1]" EOL /* 3 */
787
788 /*
789 * Check the address doesn't already have a static assignment.
790 */
791 "local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[1]))" EOL /* 4 */
792 "if expires and expires >= " STRINGIFY(IPPOOL_STATIC_BIT) " then" EOL /* 5 */
793 " return 0" EOL /* 6 */
794 "end" EOL /* 7 */
795
796 /*
797 * Check current assignment for device.
798 */
799 "local found = redis.call('GET', owner_key)" EOL /* 8 */
800 "if found and found ~= ARGV[1] then" EOL /* 9 */
801 " return 0" EOL /* 10 */
802 "end" EOL /* 11 */
803
804 /*
805 * If expires is in the future, check it is not
806 * another owner.
807 */
808 "if expires and expires > tonumber(ARGV[4]) then" EOL /* 12 */
809 " found = redis.call('HGET', ip_key, 'device')" /* 13 */
810 " if found and found ~= ARGV[2] then" EOL /* 14 */
811 " return 0" EOL /* 15 */
812 " end" EOL /* 16 */
813 "end" EOL /* 17 */
814
815 /*
816 * All checks passed - set the assignment.
817 */
818 "expires = (expires or 0) + " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 18 */
819 "redis.call('ZADD', pool_key, 'CH', expires, ARGV[1])" EOL /* 19 */
820 "redis.call('SET', owner_key, ARGV[1])" EOL /* 20 */
821 "redis.call('HSET', ip_key, 'device', ARGV[2], 'counter', 0)" EOL /* 21 */
822 "if ARGV[3] then" EOL /* 22 */
823 " redis.call('HSET', ip_key, 'range', ARGV[3])" EOL /* 23 */
824 "end" EOL /* 24 */
825 "return 1"; /* 25 */
826
827/** Lua script for un-assigning a static lease
828 *
829 * - KEYS[1] The pool name.
830 * - ARGV[1] IP address to remove static lease from.
831 * - ARGV[2] The owner the static lease should be removed from.
832 * - ARGV[3] Wall time (seconds since epoch).
833 *
834 * Removes the static flag from the IP entry in the ZSET, then, depending on the remaining time
835 * determined by the ZSCORE removes the address hash, and the device key.
836 *
837 * Will do nothing if the static assignment does not exist or the IP and device do not match.
838 *
839 * Returns
840 * - 0 if no ip addresses were unassigned.
841 * - 1 if an ip address was unassigned.
842 */
843static char lua_unassign_cmd[] =
844 "local found" EOL /* 1 */
845 "local pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 2 */
846 "local owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[2]" EOL /* 3 */
847
848 /*
849 * Check that the device hash exists and points at the correct IP
850 */
851 "found = redis.call('GET', owner_key)" EOL /* 4 */
852 "if not found or found ~= ARGV[1] then" EOL /* 5 */
853 " return 0" EOL /* 6 */
854 "end" EOL /* 7 */
855
856 /*
857 * Check the assignment is actually static
858 */
859 "local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[1]))" EOL /* 8 */
860 "local static = expires >= " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 9 */
861 "if not static then" EOL /* 10 */
862 " return 0" EOL /* 11 */
863 "end" EOL /* 12 */
864
865 /*
866 * Remove static bit from ZSCORE
867 */
868 "expires = expires - " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 13 */
869 "redis.call('ZADD', pool_key, 'XX', expires, ARGV[1])" EOL /* 14 */
870
871 /*
872 * If the lease still has time left, set an expiry on the device key.
873 * otherwise delete it.
874 */
875 "if expires > tonumber(ARGV[3]) then" EOL /* 15 */
876 " redis.call('EXPIRE', owner_key, expires - tonumber(ARGV[3]))" EOL /* 16 */
877 "else" EOL /* 17 */
878 " redis.call('DEL', owner_key)" EOL /* 18 */
879 "end" EOL /* 19 */
880 "return 1"; /* 20 */
881
883{
884 if (bits >= 32) return 0xffffffff;
885 return (1U << bits) - 1;
886}
887
888/** Check the requisite number of slaves replicated the lease info
889 *
890 * @param request The current request.
891 * @param cmd The Redis command triggering this callback
892 * @param reply we got from the server.
893 * @param rctx WAIT resume context.
894 */
895static inline void ippool_wait_check(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
896{
897 redis_wait_rctx_t *wait_rctx = rctx;
898 if (!wait_rctx->wait_num) return;
899
900 if (reply->type != REDIS_REPLY_INTEGER) {
901 REDEBUG("WAIT result is wrong type, expected integer got %s",
902 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
903 wait_rctx->fail = true;
904 return;
905 }
906 if (reply->integer < wait_rctx->wait_num) {
907 REDEBUG("Too few slaves acknowledged allocation, needed %i, got %lli",
908 wait_rctx->wait_num, reply->integer);
909 wait_rctx->fail = true;
910 return;
911 }
912}
913
915 fr_log_lvl_t lvl,
916 fr_value_box_t const *key_prefix,
917 fr_value_box_t const *ip,
918 fr_value_box_t const *owner,
919 fr_value_box_t const *gateway_id,
920 uint32_t expires)
921{
922 char *device_str = NULL, *gateway_str = NULL;
923
924 if (gateway_id && gateway_id->vb_length > 0) gateway_str = fr_asprint(request, gateway_id->vb_strvalue,
925 gateway_id->vb_length, '"');
926 if (owner && owner->vb_length > 0) device_str = fr_asprint(request, owner->vb_strvalue, owner->vb_length, '"');
927
928 switch (action) {
930 RDEBUGX(lvl, "Allocating lease from pool \"%pV\"%s%s%s%s%s%s, expires in %us",
931 key_prefix,
932 device_str ? ", to \"" : "", device_str ? device_str : "",
933 device_str ? "\"" : "",
934 gateway_str ? ", on \"" : "", gateway_str ? gateway_str : "",
935 gateway_str ? "\"" : "",
936 expires);
937 break;
938
940 RDEBUGX(lvl, "Updating %pV in pool \"%pV\"%s%s%s%s%s%s, expires in %us",
941 ip, key_prefix,
942 device_str ? ", device \"" : "", device_str ? device_str : "",
943 device_str ? "\"" : "",
944 gateway_str ? ", gateway \"" : "", gateway_str ? gateway_str : "",
945 gateway_str ? "\"" : "",
946 expires);
947 break;
948
950 RDEBUGX(lvl, "Releasing %pV%s%s%s to pool \"%pV\"",
951 ip,
952 device_str ? " leased by \"" : "", device_str ? device_str : "",
953 device_str ? "\"" : "",
954 key_prefix);
955 break;
956
957 default:
958 break;
959 }
960
961 /*
962 * Ordering is important, needs to be LIFO
963 * for proper talloc pool reuse.
964 */
965 talloc_free(device_str);
966 talloc_free(gateway_str);
967}
968
969/** Enqueue a script to run against a redis cluster
970 *
971 * @param[in] ctx To allocate redis command set.
972 * @param[out] out_cmds Where to write a pointer to the command set.
973 * @param[out] out_cmd Where to write a pointer to the async command
974 * @param[in] request The current request.
975 * @param[in] thread Redis ippool thread
976 * @param[in] key to use to determine the cluster node.
977 * @param[in] key_len length of the key.
978 * @param[in] cmd Pre-formatted redis command to call script
979 * @param[in] cmd_len length of the pre-formatted command.
980 * @param[in] complete Callback to run when `cmd` is completed
981 * @param[in] resume Resume function to run after script completed.
982 * @param[in] rctx to pass to `complete` and `resume`.
983 * @return #unlang_action_t
984 */
987 rlm_redis_ippool_thread_t *thread, uint8_t const *key, size_t key_len,
988 char const *cmd, int cmd_len, fr_redis_command_complete_t complete,
989 module_method_t resume, unlang_module_signal_t cancel, void *rctx,
990 redis_wait_rctx_t *wait_rctx)
991{
994
995 MEM(cmds = fr_redis_command_set_alloc(ctx, request, NULL, NULL, NULL, false));
996
997 if (fr_redis_command_preformatted_add(cmds, cmd, cmd_len, complete, rctx) != FR_REDIS_PIPELINE_OK) {
998 error:
999 talloc_free(cmds);
1000 return UNLANG_ACTION_FAIL;
1001 }
1002
1003 if (thread->inst->wait_cmd) {
1005 ippool_wait_check, wait_rctx) != FR_REDIS_PIPELINE_OK) goto error;
1006 }
1007
1008 *out_cmd = fr_redis_async_cmd_start(ctx, request, &ret, thread->rtcluster, key, key_len, cmds, false, NULL);
1009
1010 REDIS_ASYNC_START_RCODE_PROCESS(ret, thread->rtcluster, thread->cw, thread->inst->coord_pair_reg,
1011 "Failed enqueuing Redis command", UNLANG_ACTION_FAIL);
1012
1013 if (out_cmds) *out_cmds = cmds;
1014 return unlang_module_yield(request, resume, cancel, FR_SIGNAL_ALLOW(FR_SIGNAL_CANCEL), rctx);
1015}
1016
1017/** Convert the rcode element of a Lua script reply to an ippool_rcode_t
1018 *
1019 * hiredis returns the rcode as a long long, and ippool_rcode_t has an int8_t underlying type.
1020 * Assigning the rcode directly would truncate an out of range value into another code, for
1021 * example 256 into IPPOOL_RCODE_SUCCESS.
1022 *
1023 * For any rcode outside IPPOOL_RCODE_FAIL to IPPOOL_RCODE_SUCCESS, the function logs the rcode
1024 * and returns IPPOOL_RCODE_FAIL. Otherwise the function returns the rcode as an ippool_rcode_t.
1025 */
1026static inline CC_HINT(always_inline) ippool_rcode_t ippool_rcode_from_reply(request_t *request, long long rcode)
1027{
1028 if ((rcode < IPPOOL_RCODE_FAIL) || (rcode > IPPOOL_RCODE_SUCCESS)) {
1029 REDEBUG("Server returned unknown rcode %lld", rcode);
1030 return IPPOOL_RCODE_FAIL;
1031 }
1032
1033 return (ippool_rcode_t) rcode;
1034}
1035
1036/** Callback to process results from allocation script
1037 */
1039{
1040 redis_ippool_alloc_rctx_t *alloc_rctx = talloc_get_type_abort(rctx, redis_ippool_alloc_rctx_t);
1041 redis_ippool_alloc_call_env_t *env = alloc_rctx->env;
1042
1044
1045 if (reply->type != REDIS_REPLY_ARRAY) {
1046 REDEBUG("Expected result to be array got \"%s\"",
1047 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1048 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1049 return;
1050 }
1051
1052 if (reply->elements == 0) {
1053 REDEBUG("Got empty result array");
1054 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1055 return;
1056 }
1057
1058 /*
1059 * Process return code
1060 */
1061 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
1062 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
1063 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1064 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1065 return;
1066 }
1067 alloc_rctx->ret = ippool_rcode_from_reply(request, reply->element[0]->integer);
1068 if (alloc_rctx->ret < 0) return;
1069
1070 /*
1071 * Process IP address
1072 */
1073 if (reply->elements > 1) {
1074 tmpl_t ip_rhs;
1075 map_t ip_map = {
1077 .op = T_OP_SET,
1078 .rhs = &ip_rhs
1079 };
1080
1081 tmpl_init_shallow(&ip_rhs, TMPL_TYPE_DATA, T_BARE_WORD, "", 0, NULL);
1082 switch (reply->element[1]->type) {
1083 /*
1084 * Destination attribute may not be IPv4, in which case
1085 * we want to pre-convert the integer value to an IPv4
1086 * address before casting it once more to the type of
1087 * the destination attribute.
1088 */
1089 case REDIS_REPLY_INTEGER:
1090 {
1091 if (tmpl_attr_tail_da(ip_map.lhs)->type != FR_TYPE_IPV4_ADDR) {
1092 fr_value_box_t tmp;
1093
1094 fr_value_box(&tmp, (uint32_t)ntohl((uint32_t)reply->element[1]->integer), true);
1096 NULL, &tmp)) {
1097 RPEDEBUG("Failed converting integer to IPv4 address");
1098 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1099 return;
1100 }
1101 } else {
1102 fr_value_box(&ip_map.rhs->data.literal,
1103 (uint32_t)ntohl((uint32_t)reply->element[1]->integer), true);
1104 }
1105 }
1106 goto do_ip_map;
1107
1108 case REDIS_REPLY_STRING:
1109 fr_value_box_bstrndup_shallow(&ip_map.rhs->data.literal,
1110 NULL, reply->element[1]->str, reply->element[1]->len, false);
1111 do_ip_map:
1112 if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) {
1113 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1114 return;
1115 }
1116 break;
1117
1118 default:
1119 REDEBUG("Server returned unexpected type \"%s\" for IP element (result[1])",
1120 fr_table_str_by_value(redis_reply_types, reply->element[1]->type, "<UNKNOWN>"));
1121 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1122 return;
1123 }
1124 }
1125
1126 /*
1127 * Process Range identifier
1128 */
1129 if (reply->elements > 2) {
1130 switch (reply->element[2]->type) {
1131 /*
1132 * Add range ID to request
1133 */
1134 case REDIS_REPLY_STRING:
1135 {
1136 tmpl_t range_rhs;
1137 map_t range_map = {
1138 .lhs = env->range_attr,
1139 .op = T_OP_SET,
1140 .rhs = &range_rhs
1141 };
1142
1143 tmpl_init_shallow(&range_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
1144 fr_value_box_bstrndup_shallow(&range_map.rhs->data.literal,
1145 NULL, reply->element[2]->str, reply->element[2]->len, true);
1146 if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) {
1147 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1148 return;
1149 }
1150 }
1151 break;
1152
1153 case REDIS_REPLY_NIL:
1154 break;
1155
1156 default:
1157 REDEBUG("Server returned unexpected type \"%s\" for range element (result[2])",
1158 fr_table_str_by_value(redis_reply_types, reply->element[2]->type, "<UNKNOWN>"));
1159 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1160 return;
1161 }
1162 }
1163
1164 /*
1165 * Process Expiry time
1166 */
1167 if (env->expiry_attr && (reply->elements > 3)) {
1168 tmpl_t expiry_rhs;
1169 map_t expiry_map = {
1170 .lhs = env->expiry_attr,
1171 .op = T_OP_SET,
1172 .rhs = &expiry_rhs
1173 };
1174
1175 tmpl_init_shallow(&expiry_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
1176 if (reply->element[3]->type != REDIS_REPLY_INTEGER) {
1177 REDEBUG("Server returned unexpected type \"%s\" for expiry element (result[3])",
1178 fr_table_str_by_value(redis_reply_types, reply->element[3]->type, "<UNKNOWN>"));
1179 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1180 return;
1181 }
1182
1183 fr_value_box(&expiry_map.rhs->data.literal, (uint32_t)reply->element[3]->integer, true);
1184 if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) {
1185 alloc_rctx->ret = IPPOOL_RCODE_FAIL;
1186 return;
1187 }
1188 }
1189}
1190
1191/** Callback to process results from allocation script
1192 */
1194{
1195 redis_ippool_update_rctx_t *update_rctx = talloc_get_type_abort(rctx, redis_ippool_update_rctx_t);
1196 redis_ippool_update_call_env_t *env = update_rctx->env;
1197
1199
1200 if (reply->type != REDIS_REPLY_ARRAY) {
1201 REDEBUG("Expected result to be array got \"%s\"",
1202 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1203 update_rctx->ret = IPPOOL_RCODE_FAIL;
1204 return;
1205 }
1206
1207 if (reply->elements == 0) {
1208 REDEBUG("Got empty result array");
1209 update_rctx->ret = IPPOOL_RCODE_FAIL;
1210 return;
1211 }
1212
1213 /*
1214 * Process return code
1215 */
1216 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
1217 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
1218 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1219 update_rctx->ret = IPPOOL_RCODE_FAIL;
1220 return;
1221 }
1222 update_rctx->ret = ippool_rcode_from_reply(request, reply->element[0]->integer);
1223 if (update_rctx->ret < 0) return;
1224
1225 /*
1226 * Process Range identifier
1227 */
1228 if (reply->elements > 1) {
1229 switch (reply->element[1]->type) {
1230 /*
1231 * Add range ID to request
1232 */
1233 case REDIS_REPLY_STRING:
1234 {
1235 tmpl_t range_rhs;
1236 map_t range_map = { .lhs = env->range_attr, .op = T_OP_SET, .rhs = &range_rhs };
1237
1238 tmpl_init_shallow(&range_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
1239 fr_value_box_bstrndup_shallow(&range_map.rhs->data.literal, NULL,
1240 reply->element[1]->str, reply->element[1]->len, true);
1241 if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) {
1242 update_rctx->ret = IPPOOL_RCODE_FAIL;
1243 return;
1244 }
1245 }
1246 break;
1247
1248 case REDIS_REPLY_NIL:
1249 break;
1250
1251 default:
1252 REDEBUG("Server returned unexpected type \"%s\" for range element (result[1])",
1253 fr_table_str_by_value(redis_reply_types, reply->element[0]->type, "<UNKNOWN>"));
1254 update_rctx->ret = IPPOOL_RCODE_FAIL;
1255 return;
1256 }
1257 }
1258
1259 /*
1260 * Copy expiry time to expires attribute (if set)
1261 */
1262 if (env->expiry_attr) {
1263 tmpl_t expiry_rhs;
1264 map_t expiry_map = {
1265 .lhs = env->expiry_attr,
1266 .op = T_OP_SET,
1267 .rhs = &expiry_rhs
1268 };
1269
1270
1271 tmpl_init_shallow(&expiry_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
1272
1273 fr_value_box(&expiry_map.rhs->data.literal, env->lease_time.vb_uint32, false);
1274 if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) {
1275 update_rctx->ret = IPPOOL_RCODE_FAIL;
1276 return;
1277 }
1278 }
1279}
1280
1281/** Callback to process results from release script
1282 */
1284 void *rctx)
1285{
1286 redis_ippool_release_rctx_t *release_rctx = talloc_get_type_abort(rctx, redis_ippool_release_rctx_t);
1287
1289
1290 if (reply->type != REDIS_REPLY_ARRAY) {
1291 REDEBUG("Expected result to be array got \"%s\"",
1292 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1293 release_rctx->ret = IPPOOL_RCODE_FAIL;
1294 return;
1295 }
1296
1297 if (reply->elements == 0) {
1298 REDEBUG("Got empty result array");
1299 release_rctx->ret = IPPOOL_RCODE_FAIL;
1300 return;
1301 }
1302
1303 /*
1304 * Process return code
1305 */
1306 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
1307 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
1308 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1309 release_rctx->ret = IPPOOL_RCODE_FAIL;
1310 return;
1311 }
1312 release_rctx->ret = ippool_rcode_from_reply(request, reply->element[0]->integer);
1313}
1314
1315#define CHECK_POOL_NAME \
1316 if (env->pool_name.vb_length > IPPOOL_MAX_KEY_PREFIX_SIZE) { \
1317 REDEBUG("Pool name too long. Expected %u bytes, got %zu bytes", \
1318 IPPOOL_MAX_KEY_PREFIX_SIZE, env->pool_name.vb_length); \
1319 RETURN_UNLANG_FAIL; \
1320 } \
1321 if (env->pool_name.vb_length == 0) { \
1322 RDEBUG2("Empty pool name. Doing nothing"); \
1323 RETURN_UNLANG_NOOP; \
1324 }
1325
1327{
1328 redis_ippool_load_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_load_rctx_t);
1329
1330 RDEBUG2("Forcibly cancelling Redis script load command");
1331
1333}
1334
1337{
1338 redis_ippool_load_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_load_rctx_t);
1339
1340 switch(fr_redis_command_set_rcode(rctx->cmds)) {
1342 break;
1343
1344 default:
1345 RERROR("Failed loading Redis lua script");
1347 }
1348
1350
1352}
1353
1355 redisReply *reply, UNUSED void *rctx)
1356{
1358
1359 if (reply->type != REDIS_REPLY_STRING) {
1360 ERROR("Unexpected reply type after loading function");
1361 return;
1362 }
1363 DEBUG2("Loaded lua function with hash \"%s\" onto node", reply->str);
1364}
1365
1366#define REDIS_IPPOOL_SCRIPT_LOAD(_cmds, _script, _script_len, _ret) do { \
1367 char const **argv; \
1368 size_t *argv_len; \
1369 MEM(argv = talloc_array(_cmds, char const *, 3)); \
1370 MEM(argv_len = talloc_array(_cmds, size_t, 3)); \
1371 argv[0] = "SCRIPT"; \
1372 argv[1] = "LOAD"; \
1373 argv[2] = _script; \
1374 argv_len[0] = (sizeof("SCRIPT") - 1); \
1375 argv_len[1] = (sizeof("LOAD") - 1); \
1376 argv_len[2] = _script_len; \
1377 if (fr_redis_command_argv_add(_cmds, 3, argv, argv_len, lua_script_load_results, NULL) != FR_REDIS_PIPELINE_OK) { \
1378 talloc_free(_cmds); \
1379 return _ret; \
1380 } \
1381} while (0)
1382
1383/** Check the return code from an async redis command
1384 *
1385 * Redirecting to another node in response to ASK / MOVED codes.
1386 */
1388 fr_redis_async_cmd_t *cmd, module_ctx_t const *mctx,
1390 char const *script, size_t script_len)
1391{
1392 switch (fr_redis_command_set_rcode(cmds)) {
1394 {
1395 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
1397 fr_redis_command_set_t *load_cmds;
1399
1400 /*
1401 * Script loading is done following trunk connection.
1402 * A NO-SCRIPT response means the command was enqueued before the script load or
1403 * the script has been cleared from the node.
1404 */
1406 MEM(load_cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
1407 REDIS_IPPOOL_SCRIPT_LOAD(load_cmds, script, script_len, UNLANG_ACTION_FAIL);
1408 *rctx = (redis_ippool_load_rctx_t) {
1409 .cmds = load_cmds,
1410 .eval_cmd = cmd,
1411 .resume = resume,
1412 .cancel = cancel,
1413 .eval_rctx = mctx->rctx
1414 };
1415
1416 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &ret, thread->rtcluster, NULL, 0, load_cmds,
1417 false, fr_redis_async_cmd_node(cmd));
1420 }
1421
1423 {
1425 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
1426
1427 if (inst->conf.use_cluster_map) fr_redis_ct_map_get(thread->rtcluster, thread->cw,
1428 inst->coord_pair_reg, false);
1429 }
1431
1434 return unlang_module_yield(request, resume, cancel, FR_SIGNAL_ALLOW(FR_SIGNAL_CANCEL), mctx->rctx);
1435
1438 return unlang_module_yield(request, resume, cancel, FR_SIGNAL_ALLOW(FR_SIGNAL_CANCEL), mctx->rctx);
1439
1440 default:
1441 break;
1442 }
1444}
1445
1447{
1448 redis_ippool_alloc_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_alloc_rctx_t);
1449
1450 RDEBUG2("Forcibly cancelling Redis alloc command");
1451
1453}
1454
1457{
1458 redis_ippool_alloc_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_alloc_rctx_t);
1459
1460 if (redis_ippool_rcode_check(request, rctx->cmds, rctx->cmd, mctx, mod_alloc_resume,
1463
1464 switch (rctx->ret) {
1467
1468 RDEBUG2("IP address lease allocated");
1470
1472 RWDEBUG("Pool contains no free addresses");
1474
1475 default:
1476 RPERROR("Allocating IP address failed");
1478 }
1479}
1480
1482{
1483 if (!rctx->cmd_str) return 0;
1484 redisFreeCommand(rctx->cmd_str);
1485 return 0;
1486}
1487
1489{
1491 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
1492 redis_ippool_alloc_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_alloc_call_env_t);
1493 uint32_t lease_time;
1494 struct timeval now;
1495 uint32_t assoc_time;
1497 int cmd_len;
1498
1499 fr_assert(env->pool_name.vb_length > 0);
1500 fr_assert(env->owner.vb_length > 0);
1501
1503
1504 /*
1505 * If offer_time is defined, it will be FR_TYPE_UINT32.
1506 * Fall back to lease_time otherwise.
1507 */
1508 lease_time = (env->offer_time.type == FR_TYPE_UINT32) ?
1509 env->offer_time.vb_uint32 : env->lease_time.vb_uint32;
1511 &env->owner, &env->gateway_id, lease_time);
1512
1514 rctx->env = env;
1515 rctx->ret = IPPOOL_RCODE_FAIL;
1516 rctx->wait_rctx.wait_num = inst->wait_num;
1517 talloc_set_destructor(rctx, _redis_ippool_alloc_ctx_free);
1518
1519 now = fr_time_to_timeval(fr_time());
1520
1521 assoc_time = (env->association_time.type == FR_TYPE_UINT32) &&
1522 (env->association_time.vb_uint32 > lease_time) ? env->association_time.vb_uint32 : lease_time;
1523
1524 if ((env->requested_address.datum.ip.af == AF_INET) && inst->ipv4_integer) {
1525 cmd_len = redisFormatCommand(&rctx->cmd_str, "EVALSHA %s 1 %b %u %u %b %u %b %u",
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 htonl(env->requested_address.datum.ip.addr.v4.s_addr));
1533 if (cmd_len < 0) {
1534 format_error:
1535 RERROR("Failed formatting redis command");
1536 return UNLANG_ACTION_FAIL;
1537 }
1538 } else {
1539 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
1540 if (env->requested_address.type == FR_TYPE_COMBO_IP_ADDR) {
1541 IPPOOL_SPRINT_IP(ip_buff, &env->requested_address.datum.ip, env->requested_address.datum.ip.prefix);
1542 } else {
1543 ip_buff[0] = '\0';
1544 }
1545
1546 cmd_len = redisFormatCommand(&rctx->cmd_str, "EVALSHA %s 1 %b %u %u %b %u %b %s",
1548 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1549 (unsigned int)now.tv_sec, lease_time,
1550 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length,
1551 assoc_time,
1552 (uint8_t const *)env->gateway_id.vb_strvalue, env->gateway_id.vb_length,
1553 ip_buff);
1554 if (cmd_len < 0) goto format_error;
1555 }
1556
1557 return ippool_script_enqueue(rctx, &rctx->cmds, &rctx->cmd, request, thread,
1558 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1560 mod_alloc_cancel, rctx, &rctx->wait_rctx);
1561}
1562
1564{
1565 redis_ippool_update_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_update_rctx_t);
1566
1567 RDEBUG2("Forcibly cancelling Redis update command");
1568
1570}
1571
1574{
1576 redis_ippool_update_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_update_rctx_t);
1577 redis_ippool_update_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_update_call_env_t);
1578
1582
1583 switch (rctx->ret) {
1586
1587 RDEBUG2("Requested IP address' \"%pV\" lease updated", &env->requested_address);
1588
1589 /*
1590 * Copy over the input IP address to the reply attribute
1591 */
1592 if (inst->copy_on_update) {
1593 tmpl_t ip_rhs = {
1594 .name = "",
1595 .type = TMPL_TYPE_DATA,
1596 .quote = T_BARE_WORD,
1597 };
1598 map_t ip_map = {
1600 .op = T_OP_SET,
1601 .rhs = &ip_rhs
1602 };
1603
1604 if (unlikely(fr_value_box_copy(NULL, &ip_rhs.data.literal, &env->requested_address) < 0)) {
1605 RPEDEBUG("Failed copying IP address to reply attribute");
1607 }
1608
1609 if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) RETURN_UNLANG_FAIL;
1610 }
1612
1613 /*
1614 * It's useful to be able to identify the 'not found' case
1615 * as we can relay to a server where the IP address might
1616 * be found. This extremely useful for migrations.
1617 */
1619 REDEBUG("Requested IP address \"%pV\" is not a member of the specified pool",
1620 &env->requested_address);
1622
1624 REDEBUG("Requested IP address' \"%pV\" lease already expired at time of renewal",
1625 &env->requested_address);
1627
1629 REDEBUG("Requested IP address' \"%pV\" lease allocated to another device",
1630 &env->requested_address);
1632
1633 default:
1634 RPERROR("Failed updating IP address");
1636 }
1637}
1638
1640{
1641 if (!rctx->cmd_str) return 0;
1642 redisFreeCommand(rctx->cmd_str);
1643 return 0;
1644}
1645
1647{
1649 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
1650 redis_ippool_update_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_update_call_env_t);
1651 struct timeval now;
1652 uint32_t assoc_time, expires;
1653 fr_ipaddr_t *ip = &env->requested_address.datum.ip;
1654 int cmd_len;
1656
1658
1660 &env->requested_address, &env->owner, &env->gateway_id, env->lease_time.vb_uint32);
1661
1663 rctx->env = env;
1664 rctx->ret = IPPOOL_RCODE_FAIL;
1665 rctx->wait_rctx.wait_num = inst->wait_num;
1666 talloc_set_destructor(rctx, _redis_ippool_update_rctx_free);
1667
1668 now = fr_time_to_timeval(fr_time());
1669 expires = env->lease_time.vb_uint32;
1670 assoc_time = (env->association_time.type == FR_TYPE_UINT32) &&
1671 (env->association_time.vb_uint32 > expires) ? env->association_time.vb_uint32 : expires;
1672
1673 if ((ip->af == AF_INET) && inst->ipv4_integer) {
1674 cmd_len = redisFormatCommand(&rctx->cmd_str, "EVALSHA %s 1 %b %u %u %u %b %u %b", lua_update_digest,
1675 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1676 (unsigned int)now.tv_sec, expires, htonl(ip->addr.v4.s_addr),
1677 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length, assoc_time,
1678 (uint8_t const *)env->gateway_id.vb_strvalue, env->gateway_id.vb_length);
1679 if (cmd_len < 0) {
1680 format_error:
1681 RERROR("Failed formatting redis command");
1682 return UNLANG_ACTION_FAIL;
1683 }
1684 } else {
1685 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
1686
1687 IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix);
1688 cmd_len = redisFormatCommand(&rctx->cmd_str, "EVALSHA %s 1 %b %u %u %s %b %u %b", lua_update_digest,
1689 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1690 (unsigned int)now.tv_sec, expires, ip_buff,
1691 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length, assoc_time,
1692 (uint8_t const *)env->gateway_id.vb_strvalue, env->gateway_id.vb_length);
1693 if (cmd_len < 0) goto format_error;
1694 }
1695
1696 return ippool_script_enqueue(rctx, &rctx->cmds, &rctx->cmd, request, thread,
1697 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1699 mod_update_cancel, rctx, &rctx->wait_rctx);
1700}
1701
1703{
1704 redis_ippool_release_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_release_rctx_t);
1705
1706 RDEBUG2("Forcibly cancelling Redis release command");
1707
1709}
1710
1713{
1714 redis_ippool_release_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_release_rctx_t);
1716
1720
1721 switch (rctx->ret) {
1724
1725 RDEBUG2("IP address \"%pV\" released", &env->requested_address);
1727
1728 /*
1729 * It's useful to be able to identify the 'not found' case
1730 * as we can relay to a server where the IP address might
1731 * be found. This extremely useful for migrations.
1732 */
1734 REDEBUG("Requested IP address \"%pV\" is not a member of the specified pool",
1735 &env->requested_address);
1737
1739 REDEBUG("Requested IP address' \"%pV\" lease allocated to another device",
1740 &env->requested_address);
1742
1743 default:
1744 RPERROR("Failed releasing IP address");
1746 }
1747}
1748
1750{
1751 if (!rctx->cmd_str) return 0;
1752 redisFreeCommand(rctx->cmd_str);
1753 return 0;
1754}
1755
1757{
1759 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
1761 struct timeval now;
1762 fr_ipaddr_t *ip = &env->requested_address.datum.ip;
1764 int cmd_len;
1765
1767
1769 &env->requested_address, &env->owner, &env->gateway_id, 0);
1770
1772 rctx->wait_rctx.wait_num = inst->wait_num;
1773 talloc_set_destructor(rctx, _redis_ippool_release_rctx_free);
1774
1775 now = fr_time_to_timeval(fr_time());
1776
1777 if ((ip->af == AF_INET) && inst->ipv4_integer) {
1778 cmd_len = redisFormatCommand(&rctx->cmd_str, "EVALSHA %s 1 %b %u %u %b %u", lua_release_digest,
1779 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1780 (unsigned int)now.tv_sec, htonl(ip->addr.v4.s_addr),
1781 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length,
1782 env->association_time.vb_uint32);
1783 if (cmd_len < 0) {
1784 format_error:
1785 RERROR("Failed formatting redis command");
1786 return UNLANG_ACTION_FAIL;
1787 }
1788 } else {
1789 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
1790
1791 IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix);
1792 cmd_len = redisFormatCommand(&rctx->cmd_str, "EVALSHA %s 1 %b %u %s %b %u", lua_release_digest,
1793 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1794 (unsigned int)now.tv_sec, ip_buff,
1795 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length,
1796 env->association_time.vb_uint32);
1797 if (cmd_len < 0) goto format_error;
1798 }
1799
1800 return ippool_script_enqueue(rctx, &rctx->cmds, &rctx->cmd, request, thread,
1801 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
1803 mod_release_cancel, rctx, &rctx->wait_rctx);
1804}
1805
1808{
1809 RDEBUG2("Bulk release not yet implemented");
1811}
1812
1813/** Parse the results from Redis SCAN command used to list pools
1814 */
1815static void mod_pools_list_result(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
1816{
1817 redis_pools_list_rctx_t *pools_rctx = talloc_get_type_abort(rctx, redis_pools_list_rctx_t);
1818 size_t k, len;
1819 char const *p;
1820
1822
1823 if (reply->type != REDIS_REPLY_ARRAY) {
1824 ERROR("Failed retrieving result, expected array got %s",
1825 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1826 return;
1827 }
1828
1829 if (reply->elements != 2) {
1830 ERROR("Failed retrieving result, expected array with two elements, got %zu elements",
1831 reply->elements);
1832 return;
1833 }
1834
1835 if (reply->element[0]->type != REDIS_REPLY_STRING) {
1836 ERROR("Failed retrieving result, expected string got %s",
1837 fr_table_str_by_value(redis_reply_types, reply->element[0]->type, "<UNKNOWN>"));
1838 return;
1839 }
1840
1841 if (reply->element[1]->type != REDIS_REPLY_ARRAY) {
1842 ERROR("Failed retrieving result, expected array got %s",
1843 fr_table_str_by_value(redis_reply_types, reply->element[1]->type, "<UNKNOWN>"));
1844 return;
1845 }
1846
1847 strlcpy(pools_rctx->cursor, reply->element[0]->str, sizeof(pools_rctx->cursor));
1848
1849 for (k = 0; k < reply->element[1]->elements; k++) {
1850 fr_value_box_t *vb;
1851 redisReply *pool_key = reply->element[1]->element[k];
1852
1853 /*
1854 * Skip over things which are not pool names
1855 */
1856 if (pool_key->len < 7) continue; /* { + [<name>] + }:pool */
1857
1858 if ((pool_key->str[0]) != '{') continue;
1859 p = memchr(pool_key->str + 1, '}', pool_key->len - 1);
1860 if (!p) continue;
1861
1862 len = (pool_key->len - ((p + 1) - pool_key->str));
1863 if (len != (sizeof(IPPOOL_POOL_KEY) - 1) + 1) continue;
1864 if (memcmp(p + 1, ":" IPPOOL_POOL_KEY, (sizeof(IPPOOL_POOL_KEY) - 1) + 1) != 0) continue;
1865
1866 /*
1867 * String between the curly braces is the pool name
1868 */
1869 MEM(vb = fr_value_box_alloc(pools_rctx->ctx, FR_TYPE_STRING, NULL));
1870 fr_value_box_bstrndup(vb, vb, NULL, pool_key->str + 1, (p - pool_key->str) - 1, false);
1871 fr_value_box_list_insert_tail(&pools_rctx->pools, vb);
1872 }
1873}
1874
1877
1879{
1880 redis_pools_list_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, redis_pools_list_rctx_t);
1881
1882 RDEBUG2("Forcefully cancelling Redis SCAN command");
1883
1885}
1886
1888 request_t *request, UNUSED fr_value_box_list_t *in)
1889{
1890 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
1891 redis_pools_list_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, redis_pools_list_rctx_t);
1892
1893 switch (fr_redis_command_set_rcode(rctx->cmds)) {
1898 fr_value_box_list_talloc_free(&rctx->pools);
1899 return XLAT_ACTION_FAIL;
1900
1905
1906 default:
1907 break;
1908 }
1909
1910 if ((rctx->cursor[0] == '0') && rctx->cursor[1] == '\0') {
1911 rctx->current_node++;
1912 if (rctx->current_node == rctx->node_count) {
1913 if (fr_value_box_list_num_elements(&rctx->pools) == 0) return XLAT_ACTION_DONE;
1914
1915 fr_value_box_list_foreach(&rctx->pools, vb) {
1916 fr_value_box_list_remove(&rctx->pools, vb);
1918 }
1919 return XLAT_ACTION_DONE;
1920 }
1921 }
1922
1923 return redis_ippool_list_next_scan(request, thread, rctx);
1924}
1925
1926/** Enqueue the next SCAN command
1927 *
1928 * This will either be on the same node, with the cursor value returned
1929 * by the last SCAN, or starting on a new node.
1930 */
1933{
1934 fr_redis_ct_node_t *node;
1935 int cmd_len;
1937
1938 do {
1939 node = fr_redis_ct_node_by_addr(thread->rtcluster, &rctx->nodes[rctx->current_node]);
1940 if (node) {
1941 RDEBUG3("Querying node %d - %s:%d", rctx->current_node,
1942 rctx->nodes[rctx->current_node].hostname,
1943 rctx->nodes[rctx->current_node].port);
1944 break;
1945 }
1946 RWARN("Unable to find node %s:%d", rctx->nodes[rctx->current_node].hostname,
1947 rctx->nodes[rctx->current_node].port);
1948 rctx->current_node++;
1949 } while (rctx->current_node < rctx->node_count);
1950 if (!node) return XLAT_ACTION_DONE;
1951
1952 if (rctx->cmd_str) {
1953 redisFreeCommand(rctx->cmd_str);
1954 rctx->cmd_str = NULL;
1955 if (fr_redis_command_set_clear(rctx->cmds) < 0) return XLAT_ACTION_FAIL;
1956 }
1957
1958 /*
1959 * Break up the scan so we don't block any single Redis node too long.
1960 */
1961 cmd_len = redisFormatCommand(&rctx->cmd_str, "SCAN %s MATCH {*}:"IPPOOL_POOL_KEY" COUNT 20", rctx->cursor);
1962 if (cmd_len < 0) return XLAT_ACTION_FAIL;
1963
1965 rctx) != FR_REDIS_PIPELINE_OK) return XLAT_ACTION_FAIL;
1966
1967 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &ret, thread->rtcluster, NULL, 0, rctx->cmds, false, node);
1968
1969 REDIS_ASYNC_START_RCODE_PROCESS(ret, thread->rtcluster, thread->cw, thread->inst->coord_pair_reg,
1970 "Failed enqueuing redis command", XLAT_ACTION_FAIL);
1971
1973}
1974
1976 request_t *request, UNUSED fr_value_box_list_t *in)
1977{
1978 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
1979 redis_pools_list_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, redis_pools_list_rctx_t);
1981
1982 /*
1983 * Fetch the list of master nodes in the cluster.
1984 */
1985 ret = fr_redis_ct_node_addr_by_role(rctx, &rctx->nodes, &rctx->node_count, thread->rtcluster, true, false);
1986
1987 switch (ret) {
1989 break;
1990
1992 fr_redis_ct_map_bootstrap(thread->rtcluster, thread->cw, thread->inst->coord_pair_reg);
1995
1996 default:
1997 return XLAT_ACTION_FAIL;
1998 }
1999
2000 return redis_ippool_list_next_scan(request, thread, rctx);
2001}
2002
2004{
2005 if (!rctx->cmd_str) return 0;
2006 redisFreeCommand(rctx->cmd_str);
2007 return 0;
2008}
2009
2011 request_t *request, fr_value_box_list_t *in)
2012{
2014
2016 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
2017 fr_value_box_list_init(&rctx->pools);
2018 rctx->ctx = ctx;
2019 talloc_set_destructor(rctx, _redis_pools_list_rctx_free);
2020 strcpy(rctx->cursor, "0");
2021
2022 return redis_ippool_list_start(ctx, out, XLAT_CTX(xctx->inst, NULL, xctx->ex, xctx->mctx, NULL, rctx),
2023 request, in);
2024}
2025
2027{
2028 char *cmd_str;
2029 size_t i = 0;
2030
2031 /*
2032 * Free any Redis commands
2033 */
2034 while (i < talloc_array_length(rctx->cmd_str)) {
2035 cmd_str = rctx->cmd_str[i];
2036 if (!cmd_str) break;
2037 redisFreeCommand(cmd_str);
2038 i++;
2039 }
2040
2041 return 0;
2042}
2043
2045{
2046 size_t i;
2047
2048 for (i = 0; i < talloc_array_length(rctx->cmd_str); i++) {
2049 if (!rctx->cmd_str[i]) return 0;
2050 redisFreeCommand(rctx->cmd_str[i]);
2051 }
2052
2053 return 0;
2054}
2055
2057{
2058 redis_ippool_info_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_info_rctx_t);
2059
2060 RDEBUG2("Forcibly cancelling Redis command");
2061
2063}
2064
2067{
2068 redis_ippool_info_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, redis_ippool_info_rctx_t);
2069
2070 if (redis_ippool_rcode_check(request, rctx->cmds, rctx->cmd, mctx, mod_show_resume,
2072
2074
2075 RDEBUG2("%pP", fr_pair_list_head(&rctx->results));
2076 fr_pair_list_append(&request->control_pairs, &rctx->results);
2077
2079}
2080
2081static void mod_show_result(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
2082{
2083 redis_ippool_info_rctx_t *show_rctx = talloc_get_type_abort(rctx, redis_ippool_info_rctx_t);
2084 redisReply *ip_reply;
2085 fr_pair_t *lease_vp, *vp;
2086 bool active, staticip = false;
2087 fr_value_box_t expires;
2088 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
2089
2091
2092 IPPOOL_SPRINT_IP(ip_buff, &show_rctx->lookup[show_rctx->lookup_no]->vb_ip,
2093 show_rctx->lookup[show_rctx->lookup_no]->vb_ip.prefix);
2094 show_rctx->lookup_no++;
2095
2096 if (reply->type != REDIS_REPLY_ARRAY) return;
2097 if (reply->elements < 2) return;
2098
2099 if (reply->element[0]->type == REDIS_REPLY_NIL) return; /* A nil result (IP didn't exist) */
2100 if (reply->element[0]->type != REDIS_REPLY_STRING) return; /* Something bad */
2101 if (reply->element[1]->type != REDIS_REPLY_ARRAY) return;
2102
2103 MEM(lease_vp = fr_pair_afrom_da_nested(request->control_ctx, &show_rctx->results, attr_ippool_lease));
2105 fr_value_box_strdup(vp, &vp->data, NULL, ip_buff, false);
2106 fr_pair_append(&lease_vp->vp_group, vp);
2107
2108 fr_value_box_from_str(NULL, &expires, FR_TYPE_UINT64, NULL, reply->element[0]->str, reply->element[0]->len, NULL);
2109 if (expires.vb_uint64 >= IPPOOL_STATIC_BIT) {
2110 staticip = true;
2111 expires.vb_uint64 -= IPPOOL_STATIC_BIT;
2112 }
2113
2115 if (fr_value_box_cast(vp, &vp->data, attr_ippool_lease_expires->type, NULL, &expires) < 0) {
2116 talloc_free(vp);
2117 return;
2118 }
2119 fr_pair_append(&lease_vp->vp_group, vp);
2120 active = fr_unix_time_to_sec(vp->vp_date) > fr_time_to_sec(fr_time()) ? true : false;
2121
2123 vp->vp_bool = active;
2124 fr_pair_append(&lease_vp->vp_group, vp);
2125
2127 vp->vp_bool = staticip;
2128 fr_pair_append(&lease_vp->vp_group, vp);
2129
2130 ip_reply = reply->element[1];
2131 if (ip_reply->element[0]->type == REDIS_REPLY_STRING) {
2133 fr_value_box_bstrndup(vp, &vp->data, NULL, ip_reply->element[0]->str, ip_reply->element[0]->len, false);
2134 fr_pair_append(&lease_vp->vp_group, vp);
2135 }
2136 if (ip_reply->element[1]->type == REDIS_REPLY_STRING) {
2138 fr_value_box_bstrndup(vp, &vp->data, NULL, ip_reply->element[1]->str, ip_reply->element[1]->len, false);
2139 fr_pair_append(&lease_vp->vp_group, vp);
2140 }
2141 if (ip_reply->element[2]->type == REDIS_REPLY_STRING) {
2143 fr_value_box_bstrndup(vp, &vp->data, NULL, ip_reply->element[2]->str, ip_reply->element[2]->len, false);
2144 fr_pair_append(&lease_vp->vp_group, vp);
2145 }
2146}
2147
2148#define ADD_REDIS_COMMAND(_fmt, ...) cmd_len = redisFormatCommand(&rctx->cmd_str[cmd_no], _fmt, __VA_ARGS__); \
2149if (cmd_len < 0) RETURN_UNLANG_FAIL; \
2150if (fr_redis_command_preformatted_add(cmds, rctx->cmd_str[cmd_no++], cmd_len, NULL, NULL) != FR_REDIS_PIPELINE_OK) goto error
2151
2152static unlang_action_t CC_HINT(nonnull) mod_show(unlang_result_t *p_result, module_ctx_t const *mctx,
2154{
2155 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
2156 redis_ippool_show_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_show_call_env_t);
2159 int cmd_len;
2160 size_t cmd_no = 0, lookup_no = 0;
2162 uint8_t *key_p = key;
2165
2166 IPPOOL_BUILD_KEY(key, key_p, env->pool_name.vb_strvalue, env->pool_name.vb_length);
2168
2170 MEM(cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
2171 rctx->cmds = cmds;
2172 MEM(rctx->lookup = talloc_zero_array(rctx, fr_value_box_t *, fr_value_box_list_num_elements(&env->requested_address)));
2173 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, fr_value_box_list_num_elements(&env->requested_address) * 2));
2174 fr_pair_list_init(&rctx->results);
2175 talloc_set_destructor(rctx, _redis_ippool_info_rctx_free);
2176
2178 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
2180 uint8_t *ip_key_p = ip_key;
2181
2182 IPPOOL_SPRINT_IP(ip_buff, &addr->vb_ip, addr->vb_ip.prefix);
2183 IPPOOL_BUILD_IP_KEY_FROM_STR(ip_key, ip_key_p, env->pool_name.vb_strvalue,
2184 env->pool_name.vb_length, ip_buff);
2185 if (ret == IPPOOL_RCODE_FAIL) {
2186 error:
2187 talloc_free(rctx);
2189 }
2190
2191 rctx->lookup[lookup_no++] = addr;
2192 if (fr_redis_command_literal_add(cmds, "MULTI", NULL, NULL) != FR_REDIS_PIPELINE_OK) goto error;
2193 ADD_REDIS_COMMAND("ZSCORE %b %s", key, key_p - key, ip_buff);
2194 ADD_REDIS_COMMAND("HMGET %b device gateway range", ip_key, ip_key_p - ip_key);
2195
2196 if (fr_redis_command_literal_add(cmds, "EXEC", mod_show_result, rctx) != FR_REDIS_PIPELINE_OK) goto error;
2197 }
2198
2199 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, thread->rtcluster,
2200 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
2201 rctx->cmds, false, NULL);
2202
2203 REDIS_ASYNC_START_RCODE_PROCESS(rcode, thread->rtcluster, thread->cw, thread->inst->coord_pair_reg,
2204 "Failed enqueueing Redis command", UNLANG_ACTION_FAIL);
2205
2207}
2208
2209static void mod_stats_result(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
2210{
2211 redis_ippool_info_rctx_t *stats_rctx = talloc_get_type_abort(rctx, redis_ippool_info_rctx_t);
2212 fr_pair_t *stats_vp, *dynamic_vp, *static_vp, *vp;
2213 size_t i;
2214
2216
2217 if (reply->type != REDIS_REPLY_ARRAY) return;
2218 if (reply->elements != 12) return;
2219
2220 for (i = 0; i < reply->elements; i++) if (reply->element[i]->type != REDIS_REPLY_INTEGER) return;
2221
2222 MEM(vp = fr_pair_afrom_da_nested(request->control_ctx, &stats_rctx->results, attr_ippool_name));
2223 if (fr_value_box_copy(vp, &vp->data, stats_rctx->lookup[0]) < 0) return;
2224 MEM(stats_vp = fr_pair_afrom_da_nested(request->control_ctx, &stats_rctx->results, attr_ippool_stats));
2225
2226#define POPULATE_PAIR(_da, _i, _parent) MEM(vp = fr_pair_afrom_da(_parent, _da)); \
2227 vp->vp_uint64 = reply->element[_i]->integer; \
2228 fr_pair_append(&_parent->vp_group, vp)
2229
2231
2232 MEM(dynamic_vp = fr_pair_afrom_da(stats_vp, attr_ippool_stats_dynamic));
2233 fr_pair_append(&stats_vp->vp_group, dynamic_vp);
2234
2236 vp->vp_uint64 = reply->element[0]->integer - reply->element[6]->integer;
2237 fr_pair_append(&dynamic_vp->vp_group, vp);
2238
2244
2245 MEM(static_vp = fr_pair_afrom_da(stats_vp, attr_ippool_stats_static));
2246 fr_pair_append(&stats_vp->vp_group, static_vp);
2253}
2254
2255static unlang_action_t CC_HINT(nonnull) mod_stats(unlang_result_t *p_result, module_ctx_t const *mctx,
2257{
2258 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
2259 redis_ippool_stats_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_stats_call_env_t);
2262 int cmd_len;
2263 size_t cmd_no = 0;
2265 uint8_t *key_p = key;
2268 fr_time_t now;
2269
2270 IPPOOL_BUILD_KEY(key, key_p, env->pool_name.vb_strvalue, env->pool_name.vb_length);
2272
2274 MEM(cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
2275 rctx->cmds = cmds;
2276 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, 12));
2277 MEM(rctx->lookup = talloc_zero_array(rctx, fr_value_box_t *, 1));
2278 fr_pair_list_init(&rctx->results);
2279 rctx->lookup[0] = &env->pool_name;
2280 talloc_set_destructor(rctx, _redis_ippool_info_rctx_free);
2281
2282 now = fr_time();
2283
2284 if (fr_redis_command_literal_add(cmds, "MULTI", NULL, NULL) != FR_REDIS_PIPELINE_OK) {
2285 error:
2286 talloc_free(rctx);
2288 }
2289 ADD_REDIS_COMMAND("ZCARD %b", key, key_p - key); /* Total */
2290 ADD_REDIS_COMMAND("ZCOUNT %b -inf %i", key, key_p - key, fr_time_to_sec(now)); /* Free */
2291 ADD_REDIS_COMMAND("ZCOUNT %b -inf %i", key, key_p - key, fr_time_to_sec(now) + 60); /* Free in next 60s */
2292 ADD_REDIS_COMMAND("ZCOUNT %b -inf %i", key, key_p - key, fr_time_to_sec(now) + (60 * 30)); /* Free in next 30 mins */
2293 ADD_REDIS_COMMAND("ZCOUNT %b -inf %i", key, key_p - key, fr_time_to_sec(now) + (60 * 60)); /* Free in next 60 mins */
2294 ADD_REDIS_COMMAND("ZCOUNT %b -inf %i", key, key_p - key, fr_time_to_sec(now) + (60 * 60 * 24)); /* Free in next day */
2295 ADD_REDIS_COMMAND("ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " inf", key, key_p - key); /* Total static */
2296 ADD_REDIS_COMMAND("ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
2297 key, key_p - key, IPPOOL_STATIC_BIT + fr_time_to_sec(now)); /* Static assignments 'free' */
2298 ADD_REDIS_COMMAND("ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
2299 key, key_p - key, IPPOOL_STATIC_BIT + fr_time_to_sec(now) + 60); /* Static renew in 60s */
2300 ADD_REDIS_COMMAND("ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
2301 key, key_p - key, IPPOOL_STATIC_BIT + fr_time_to_sec(now) + (60 * 30)); /* Static renew in 30 mins */
2302 ADD_REDIS_COMMAND("ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
2303 key, key_p - key, IPPOOL_STATIC_BIT + fr_time_to_sec(now) + (60 * 60)); /* Static renew in 60 mins */
2304 ADD_REDIS_COMMAND("ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
2305 key, key_p - key, IPPOOL_STATIC_BIT + fr_time_to_sec(now) + (60 * 60 * 24)); /* Static renew in 1 day */
2306 if (fr_redis_command_literal_add(cmds, "EXEC", mod_stats_result, rctx) != FR_REDIS_PIPELINE_OK) goto error;
2307
2308 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, thread->rtcluster,
2309 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
2310 rctx->cmds, false, NULL);
2311
2312 REDIS_ASYNC_START_RCODE_PROCESS(rcode, thread->rtcluster, thread->cw, thread->inst->coord_pair_reg,
2313 "Failed enqueueing Redis command", UNLANG_ACTION_FAIL);
2314
2316}
2317
2318/** Increment an IP address by a given number of addresses
2319 */
2320static void ipaddr_inc(fr_ipaddr_t *addr, size_t inc)
2321{
2322 switch (addr->af) {
2323 case AF_INET:
2324 addr->addr.v4.s_addr = htonl(ntohl(addr->addr.v4.s_addr) + inc);
2325 break;
2326 case AF_INET6:
2327 {
2328 uint128_t ip_curr;
2329
2330 /* Don't be tempted to cast */
2331 memcpy(&ip_curr, addr->addr.v6.s6_addr, sizeof(ip_curr));
2332 ip_curr = ntohlll(ip_curr);
2333
2334 /* Increment the prefix */
2335 ip_curr = uint128_add(ip_curr, inc);
2336 ip_curr = htonlll(ip_curr);
2337 memcpy(&addr->addr.v6.s6_addr, &ip_curr, sizeof(addr->addr.v6.s6_addr));
2338 break;
2339 }
2340 }
2341}
2342
2343/** Parse argments provided to IP pool manipulation xlats which work on subnets
2344 *
2345 * @param[in] request The current request, for debugging.
2346 * @param[out] start Where to write the start address.
2347 * @param[out] end Where to write the end address.
2348 * @param[out] prefix_out Where to write the parsed value of prefix.
2349 * @param[out] step Where to write the step size for multiple addresses.
2350 * @param[out] num_addr Where to write the number of addresses
2351 * @param[in] subnet to parse.
2352 * @param[in] prefix_in optinal prefix argument.
2353 */
2355 uint8_t *prefix_out, size_t *step, size_t *num_addr,
2356 fr_value_box_t *subnet, fr_value_box_t *prefix_in)
2357{
2358 uint8_t prefix, subnetlen = subnet->vb_ip.prefix;
2359
2360 switch (subnet->vb_ip.af) {
2361 case AF_INET:
2362 prefix = (prefix_in && prefix_in->type == FR_TYPE_UINT8) ? prefix_in->vb_uint8 : 32;
2363 if ((prefix < 1) || (prefix > 32)) {
2364 RERROR("Prefix %d out of range (1-32)", prefix);
2365 return -1;
2366 }
2367 *step = 1 << (32 - prefix);
2368 break;
2369
2370 case AF_INET6:
2371 prefix = (prefix_in && prefix_in->type == FR_TYPE_UINT8) ? prefix_in->vb_uint8 : 128;
2372 if ((prefix < 1) || (prefix > 128)) {
2373 RERROR("Prefix %d out of range (1-128)", prefix);
2374 return -1;
2375 }
2376 *step = 1 << (128 - prefix);
2377 break;
2378
2379 default:
2380 fr_assert(0);
2381 return -1;
2382 }
2383
2384 *prefix_out = prefix;
2385
2386 if (prefix < subnetlen) {
2387 ERROR("Prefix len must be greater than or equal to subnet length (%u)", subnetlen);
2388 return -1;
2389 }
2390
2391 switch (subnet->vb_ip.af) {
2392 case AF_INET:
2393 {
2394 uint32_t ip;
2395
2396#ifdef __clang_analyzer__
2397 /* cond assert to satisfy clang scan */
2398 if (!fr_cond_assert((prefix > 0) && (prefix <= 32))) return -1;
2399#endif
2400
2401 /* Set the input to /32 so cast works */
2402 subnet->vb_ip.prefix = 32;
2403 if (fr_value_box_cast(start, start, FR_TYPE_IPV4_ADDR, NULL, subnet) < 0) return -1;
2404 if (subnetlen == 32) {
2405 if (fr_value_box_copy(end, end, start) < 0) return -1;
2406 *num_addr = 1;
2407 return 0;
2408 }
2409
2410 ip = ntohl(start->vb_ip.addr.v4.s_addr);
2411 ip |= uint32_gen_mask(prefix - subnetlen) << (32 - prefix);
2412
2413 /*
2414 * Exclude the broadcast address if we are working with /32 addresses.
2415 */
2416 if (prefix == 32) ip--;
2417
2418 fr_value_box_init(end, FR_TYPE_IPV4_ADDR, NULL, start->tainted);
2419 end->vb_ipv4addr = htonl(ip);
2420 }
2421 break;
2422
2423 case AF_INET6:
2424 {
2425 uint128_t ip, p_mask;
2426
2427#ifdef __clang_analyzer__
2428 /* cond assert to satisfy clang scan */
2429 if (!fr_cond_assert((prefix > 0) && (prefix <= 128))) return -1;
2430#endif
2431
2432 /* Set the input to /128 so cast works */
2433 subnet->vb_ip.prefix = 128;
2434 if (fr_value_box_cast(start, start, FR_TYPE_IPV6_ADDR, NULL, subnet) < 0) return -1;
2435 if (subnetlen == 128) {
2436 if (fr_value_box_copy(end, end, start) < 0) return -1;
2437 *num_addr = 1;
2438 return 0;
2439 }
2440
2441 memcpy(&ip, start->vb_ipv6addr, sizeof(ip));
2442 ip = ntohlll(ip);
2443 p_mask = uint128_lshift(uint128_gen_mask(prefix - subnetlen), (128 - prefix));
2444 ip = htonlll(uint128_bor(p_mask, ip));
2445
2446 fr_value_box_init(end, FR_TYPE_IPV6_ADDR, NULL, start->tainted);
2447 memcpy(&end->vb_ipv6addr, &ip, sizeof(end->vb_ipv6addr));
2448 }
2449 break;
2450
2451 default:
2452 fr_assert(0);
2453 }
2454
2455 if (unlikely(!fr_cond_assert((prefix - subnetlen) < 128))) return -1;
2456 *num_addr = (size_t)1 << (prefix - subnetlen);
2457 return 0;
2458}
2459
2460/** Parse argments provided to IP pool manipulation xlats which work on start and end addresses
2461 *
2462 * @param[in] request The current request, for debugging.
2463 * @param[out] prefix_out Where to write the parsed value of prefix.
2464 * @param[out] step Where to write the step size for multiple addresses.
2465 * @param[out] num_addr Where to write the number of addresses
2466 * @param[in] start The provided start address.
2467 * @param[in] end The provided end address.
2468 * @param[in] prefix_in optinal prefix argument.
2469 */
2470static int redis_ippool_addresses_arg_parse(request_t *request, uint8_t *prefix_out, size_t *step, size_t *num_addr,
2471 fr_value_box_t *start, fr_value_box_t *end, fr_value_box_t *prefix_in)
2472{
2473 uint8_t prefix;
2474
2475 if (start->vb_ip.af != end->vb_ip.af) {
2476 RERROR("Mis-matched start and end IP address types");
2477 return XLAT_ACTION_FAIL;
2478 }
2479
2480 switch (start->vb_ip.af) {
2481 case AF_INET:
2482 {
2483 uint32_t start_ip, end_ip;
2484 start_ip = ntohl(start->vb_ipv4addr);
2485 end_ip = ntohl(end->vb_ipv4addr);
2486
2487 prefix = (prefix_in && prefix_in->type == FR_TYPE_UINT8) ? prefix_in->vb_uint8 : 32;
2488 if ((prefix < 1) || (prefix > 32)) {
2489 RERROR("Prefix %d out of range (1-32)", prefix);
2490 return -1;
2491 }
2492 *step = 1 << (32 - prefix);
2493 *num_addr = (end_ip - start_ip + 1) / *step;
2494 }
2495 break;
2496
2497 case AF_INET6:
2498 {
2499 uint128_t start_ip, end_ip;
2500
2501 memcpy(&start_ip, start->vb_ipv6addr, sizeof(start_ip));
2502 memcpy(&end_ip, end->vb_ipv6addr, sizeof(end_ip));
2503
2504 start_ip = ntohlll(start_ip);
2505 end_ip = ntohlll(end_ip);
2506
2507 prefix = (prefix_in && prefix_in->type == FR_TYPE_UINT8) ? prefix_in->vb_uint8 : 128;
2508 if ((prefix < 1) || (prefix > 128)) {
2509 RERROR("Prefix %d out of range (1-127)", prefix);
2510 return -1;
2511 }
2512 *step = 1 << (128 - prefix);
2513 *num_addr = uint128_sub(end_ip, start_ip) + 1 / *step;
2514 }
2515
2516 break;
2517
2518 default:
2519 fr_assert(0);
2520 return -1;
2521 }
2522
2523 *prefix_out = prefix;
2524 return 0;
2525}
2526
2527/** Common cancellation function for xlats using redi_ippool_tool_rctx_t
2528 *
2529 */
2531{
2532 redis_ippool_tool_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, redis_ippool_tool_rctx_t);
2533
2534 RDEBUG2("Forcibly cancelling pending IP pool command");
2535 if (rctx->cmd) fr_redis_async_cmd_cancel(rctx->cmd);
2536}
2537
2538/** Common resume function for pool manipulation xlats
2539 *
2540 * Where the number of changes is to be returned
2541 */
2543 request_t *request, UNUSED fr_value_box_list_t *in)
2544{
2545 redis_ippool_tool_rctx_t *rctx = talloc_get_type_abort(xctx->rctx, redis_ippool_tool_rctx_t);
2546 fr_value_box_t *vb;
2547
2548 switch (fr_redis_command_set_rcode(rctx->cmds)) {
2550 break;
2551
2553 {
2555 rlm_redis_ippool_thread_t *thread = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2556
2557 if (inst->conf.use_cluster_map) fr_redis_ct_map_get(thread->rtcluster, thread->cw,
2558 inst->coord_pair_reg, false);
2559 }
2561
2565
2569
2571 PERROR("Server returned error");
2572 return XLAT_ACTION_FAIL;
2573
2574 default:
2575 return XLAT_ACTION_FAIL;
2576 }
2577
2578 MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_UINT32, NULL));
2579 vb->vb_uint32 = rctx->changed;
2581
2582 MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_UINT32, NULL));
2583 vb->vb_uint32 = rctx->changes - rctx->changed;
2585
2586 return XLAT_ACTION_DONE;
2587}
2588
2589/** Callback to be used when Redis commands are expected to return a single integer
2590 *
2591 * With the value indicating the number of changes made.
2592 */
2593static void redis_xlat_common_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
2594{
2595 redis_ippool_tool_rctx_t *xlat_rctx = talloc_get_type_abort(rctx, redis_ippool_tool_rctx_t);
2596
2597 if (reply->type != REDIS_REPLY_INTEGER) {
2598 RERROR("Unexpected reply type");
2599 return;
2600 }
2601
2602 xlat_rctx->changed += reply->integer;
2603 return;
2604}
2605
2606/** Callback to be used when Redis commands are expected to return an array
2607 *
2608 * Where the first element is expected to be an integer indicating the number
2609 * of changes made.
2610 * Typically this is when a MULTI ... EXEC is used and this parses the
2611 * reply from EXEC.
2612 */
2613static void redis_xlat_array_results(request_t *request, UNUSED fr_redis_command_t *cmd, redisReply *reply, void *rctx)
2614{
2615 redis_ippool_tool_rctx_t *xlat_rctx = talloc_get_type_abort(rctx, redis_ippool_tool_rctx_t);
2616
2617 if (reply->type != REDIS_REPLY_ARRAY) {
2618 error:
2619 RERROR("Unexpected reply type");
2620 return;
2621 }
2622
2623 if ((reply->elements > 0) && (reply->element[0]->type == REDIS_REPLY_INTEGER)) {
2624 xlat_rctx->changed += reply->element[0]->integer;
2625 } else {
2626 goto error;
2627 }
2628}
2629
2630/** Common code for adding addresses to a pool.
2631 */
2633 fr_value_box_t *pool, fr_value_box_t *start, fr_value_box_t *end,
2634 fr_value_box_t *range, uint8_t prefix, size_t num_addr, size_t step)
2635{
2638 int cmd_len;
2639 uint8_t *p = key;
2642 bool use_range = false;
2643 size_t cmd_no = 0;
2644 fr_value_box_t curr_addr;
2645 char ipaddr[INET6_ADDRSTRLEN + 1];
2646
2647 if (fr_value_box_copy(request, &curr_addr, start) < 0) return XLAT_ACTION_FAIL;
2648
2649 if (prefix != (curr_addr.vb_ip.af == AF_INET ? 32 : 128)) {
2650 if (fr_value_box_cast_in_place(request, &curr_addr, curr_addr.vb_ip.af == AF_INET ?
2652 curr_addr.vb_ip.prefix = prefix;
2653 }
2654
2656 talloc_set_destructor(rctx, _redis_ippool_tool_rctx_free);
2657
2658 IPPOOL_BUILD_KEY(key, p, pool->vb_strvalue, pool->vb_length);
2659 if (ret == IPPOOL_RCODE_FAIL) return XLAT_ACTION_FAIL;
2660
2661 use_range = range && range->type == FR_TYPE_STRING;
2662
2663 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
2664 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, num_addr * (use_range ? 2 : 1)));
2665
2666 do {
2667 DEBUG3("Adding %pV to pool \"%pV\"", &curr_addr, pool);
2668
2669 if (fr_value_box_print(&FR_SBUFF_OUT(ipaddr, sizeof(ipaddr)), &curr_addr, NULL) < 0) return XLAT_ACTION_FAIL;
2670 cmd_len = redisFormatCommand(&rctx->cmd_str[cmd_no], "ZADD %b NX %u %s", key, p - key, 0,
2671 ipaddr);
2672 if (cmd_len < 0) return XLAT_ACTION_FAIL;
2673
2674 if (use_range) {
2676 uint8_t *ip_key_p = ip_key;
2677
2678 if (fr_redis_command_literal_add(rctx->cmds, "MULTI", NULL, NULL) != FR_REDIS_PIPELINE_OK) {
2679 error:
2680 talloc_free(rctx);
2681 return XLAT_ACTION_FAIL;
2682 }
2683 if (fr_redis_command_preformatted_add(rctx->cmds, rctx->cmd_str[cmd_no++], cmd_len, NULL,
2684 NULL) != FR_REDIS_PIPELINE_OK) goto error;
2685
2686 IPPOOL_BUILD_IP_KEY_FROM_STR(ip_key, ip_key_p, pool->vb_strvalue, pool->vb_length, ipaddr);
2687 if (ret == IPPOOL_RCODE_FAIL) return XLAT_ACTION_FAIL;
2688 cmd_len = redisFormatCommand(&rctx->cmd_str[cmd_no], "HSET %b range %b", ip_key, ip_key_p - ip_key,
2689 range->vb_strvalue, range->vb_length);
2690 if (cmd_len < 0) return XLAT_ACTION_FAIL;
2691
2692 if (fr_redis_command_preformatted_add(rctx->cmds, rctx->cmd_str[cmd_no++], cmd_len, NULL,
2693 NULL) != FR_REDIS_PIPELINE_OK) goto error;
2695 rctx) != FR_REDIS_PIPELINE_OK) goto error;
2696 } else {
2697 if (fr_redis_command_preformatted_add(rctx->cmds, rctx->cmd_str[cmd_no++], cmd_len,
2699 rctx) != FR_REDIS_PIPELINE_OK) goto error;
2700 }
2701 rctx->changes++;
2702
2703 ipaddr_inc(&curr_addr.vb_ip, step);
2704 } while (fr_ipaddr_cmp(&curr_addr.vb_ip, &end->vb_ip) != 1);
2705
2706 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, t->rtcluster, (uint8_t const *)pool->vb_strvalue,
2707 pool->vb_length, rctx->cmds, false, NULL);
2708
2709 REDIS_ASYNC_START_RCODE_PROCESS(rcode, t->rtcluster, t->cw, inst->coord_pair_reg,
2710 "Failed to launch Redis command", XLAT_ACTION_FAIL);
2711
2714}
2715
2717 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2718 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_PREFIX }, // IP subnet
2719 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2720 { .single = true, .type = FR_TYPE_STRING }, // Range
2722};
2723
2725 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
2726{
2727 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
2728 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2729 fr_value_box_t *pool, *subnet, start, end, *prefix_in, *range;
2730 uint8_t prefix;
2731 size_t num_addr, step;
2732
2733 XLAT_ARGS(in, &pool, &subnet, &prefix_in, &range);
2734
2735 if (redis_ippool_subnet_arg_parse(request, &start, &end, &prefix, &step, &num_addr,
2736 subnet, prefix_in) < 0) return XLAT_ACTION_FAIL;
2737
2738 return redis_ippool_add_common(request, inst, t, pool, &start, &end, range, prefix, num_addr, step);
2739}
2740
2742 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2743 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // Start address
2744 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // End address
2745 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2746 { .single = true, .type = FR_TYPE_STRING }, // Range
2748};
2749
2751 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
2752{
2753 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
2754 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2755 fr_value_box_t *pool, *start, *end, *prefix_in, *range;
2756 uint8_t prefix = 0;
2757 size_t num_addr = 1, step = 1;
2758
2759 XLAT_ARGS(in, &pool, &start, &end, &prefix_in, &range);
2760
2761 if (redis_ippool_addresses_arg_parse(request, &prefix, &step, &num_addr,
2762 start, end, prefix_in) < 0) return XLAT_ACTION_FAIL;
2763
2764 return redis_ippool_add_common(request, inst, t, pool, start, end, range, prefix, num_addr, step);
2765}
2766
2767/** Common code for removing addresses from a pool.
2768 */
2770 fr_value_box_t *pool, fr_value_box_t *start, fr_value_box_t *end,
2771 uint8_t prefix, size_t num_addr, size_t step)
2772{
2774 int cmd_len;
2776 size_t cmd_no = 0;
2777 fr_value_box_t curr_addr;
2778 char ipaddr[INET6_ADDRSTRLEN + 1];
2779
2780 if (fr_value_box_copy(request, &curr_addr, start) < 0) return XLAT_ACTION_FAIL;
2781
2782 if (prefix != (curr_addr.vb_ip.af == AF_INET ? 32 : 128)) {
2783 if (fr_value_box_cast_in_place(request, &curr_addr, curr_addr.vb_ip.af == AF_INET ?
2785 curr_addr.vb_ip.prefix = prefix;
2786 }
2787
2789 talloc_set_destructor(rctx, _redis_ippool_tool_rctx_free);
2790
2791 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
2792 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, num_addr));
2793
2794 do {
2795 DEBUG3("Removing %pV from pool \"%pV\"", &curr_addr, pool);
2796
2797 if (fr_value_box_print(&FR_SBUFF_OUT(ipaddr, sizeof(ipaddr)), &curr_addr, NULL) < 0) return XLAT_ACTION_FAIL;
2798 cmd_len = redisFormatCommand(&rctx->cmd_str[cmd_no], "EVAL %s 1 %b %s", lua_remove_cmd,
2799 pool->vb_strvalue, pool->vb_length, ipaddr);
2800 if (cmd_len < 0) return XLAT_ACTION_FAIL;
2801
2802 if (fr_redis_command_preformatted_add(rctx->cmds, rctx->cmd_str[cmd_no++], cmd_len,
2804 talloc_free(rctx);
2805 return XLAT_ACTION_FAIL;
2806 }
2807 rctx->changes++;
2808
2809 ipaddr_inc(&curr_addr.vb_ip, step);
2810 } while (fr_ipaddr_cmp(&curr_addr.vb_ip, &end->vb_ip) != 1);
2811
2812 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, t->rtcluster, (uint8_t const *)pool->vb_strvalue,
2813 pool->vb_length, rctx->cmds, false, NULL);
2814
2815 REDIS_ASYNC_START_RCODE_PROCESS(rcode, t->rtcluster, t->cw, inst->coord_pair_reg,
2816 "Failed to launch Redis command", XLAT_ACTION_FAIL);
2817
2820}
2821
2823 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2824 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_PREFIX }, // IP subnet
2825 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2827};
2828
2830 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
2831{
2832 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
2833 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2834 fr_value_box_t *pool, *subnet, start, end, *prefix_in;
2835 uint8_t prefix;
2836 size_t num_addr, step;
2837
2838 XLAT_ARGS(in, &pool, &subnet, &prefix_in);
2839
2840 if (redis_ippool_subnet_arg_parse(request, &start, &end, &prefix, &step, &num_addr,
2841 subnet, prefix_in) < 0) return XLAT_ACTION_FAIL;
2842
2843 return redis_ippool_remove_common(request, inst, t, pool, &start, &end, prefix, num_addr, step);
2844}
2845
2847 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2848 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // Start address
2849 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // End address
2850 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2852};
2853
2855 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
2856{
2857 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
2858 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2859 fr_value_box_t *pool, *start, *end, *prefix_in;
2860 uint8_t prefix = 0;
2861 size_t num_addr = 1, step = 1;
2862
2863 XLAT_ARGS(in, &pool, &start, &end, &prefix_in);
2864
2865 if (redis_ippool_addresses_arg_parse(request, &prefix, &step, &num_addr,
2866 start, end, prefix_in) < 0) return XLAT_ACTION_FAIL;
2867
2868 return redis_ippool_remove_common(request, inst, t, pool, start, end, prefix, num_addr, step);
2869}
2870
2871/** Common code for releasing address assignments.
2872 */
2874 fr_value_box_t *pool, fr_value_box_t *start, fr_value_box_t *end,
2875 uint8_t prefix, size_t num_addr, size_t step)
2876{
2878 int cmd_len;
2880 size_t cmd_no = 0;
2881 fr_value_box_t curr_addr;
2882 char ipaddr[INET6_ADDRSTRLEN + 1];
2883
2884 if (fr_value_box_copy(request, &curr_addr, start) < 0) return XLAT_ACTION_FAIL;
2885
2886 if (prefix != (curr_addr.vb_ip.af == AF_INET ? 32 : 128)) {
2887 if (fr_value_box_cast_in_place(request, &curr_addr, curr_addr.vb_ip.af == AF_INET ?
2889 curr_addr.vb_ip.prefix = prefix;
2890 }
2891
2893 talloc_set_destructor(rctx, _redis_ippool_tool_rctx_free);
2894
2895 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
2896 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, num_addr));
2897
2898 do {
2899 DEBUG3("Releasing %pV to pool \"%pV\"", &curr_addr, pool);
2900
2901 if (fr_value_box_print(&FR_SBUFF_OUT(ipaddr, sizeof(ipaddr)), &curr_addr, NULL) < 0) return XLAT_ACTION_FAIL;
2902 cmd_len = redisFormatCommand(&rctx->cmd_str[cmd_no], "EVAL %s 1 %b %s", lua_release_xlat_cmd,
2903 pool->vb_strvalue, pool->vb_length, ipaddr);
2904 if (cmd_len < 0) return XLAT_ACTION_FAIL;
2905
2906 if (fr_redis_command_preformatted_add(rctx->cmds, rctx->cmd_str[cmd_no++], cmd_len,
2908 talloc_free(rctx);
2909 return XLAT_ACTION_FAIL;
2910 }
2911 rctx->changes++;
2912
2913 ipaddr_inc(&curr_addr.vb_ip, step);
2914 } while (fr_ipaddr_cmp(&curr_addr.vb_ip, &end->vb_ip) != 1);
2915
2916 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, t->rtcluster, (uint8_t const *)pool->vb_strvalue,
2917 pool->vb_length, rctx->cmds, false, NULL);
2918
2919 REDIS_ASYNC_START_RCODE_PROCESS(rcode, t->rtcluster, t->cw, inst->coord_pair_reg,
2920 "Failed to launch Redis command", XLAT_ACTION_FAIL);
2921
2924}
2925
2927 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2928 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_PREFIX }, // IP subnet
2929 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2931};
2932
2934 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
2935{
2936 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
2937 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2938 fr_value_box_t *pool, *subnet, start, end, *prefix_in;
2939 uint8_t prefix;
2940 size_t num_addr, step;
2941
2942 XLAT_ARGS(in, &pool, &subnet, &prefix_in);
2943
2944 if (redis_ippool_subnet_arg_parse(request, &start, &end, &prefix, &step, &num_addr,
2945 subnet, prefix_in) < 0) return XLAT_ACTION_FAIL;
2946
2947 return redis_ippool_release_common(request, inst, t, pool, &start, &end, prefix, num_addr, step);
2948}
2949
2951 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
2952 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // Start address
2953 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // End address
2954 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
2956};
2957
2959 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
2960{
2961 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
2962 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
2963 fr_value_box_t *pool, *start, *end, *prefix_in;
2964 uint8_t prefix = 0;
2965 size_t num_addr = 1, step = 1;
2966
2967 XLAT_ARGS(in, &pool, &start, &end, &prefix_in);
2968
2969 if (redis_ippool_addresses_arg_parse(request, &prefix, &step, &num_addr,
2970 start, end, prefix_in) < 0) return XLAT_ACTION_FAIL;
2971
2972 return redis_ippool_release_common(request, inst, t, pool, start, end, prefix, num_addr, step);
2973}
2974
2975/** Common code for modifying the range assigned to addresses
2976 */
2978 fr_value_box_t *pool, fr_value_box_t *start, fr_value_box_t *end,
2979 fr_value_box_t *range, uint8_t prefix, size_t num_addr, size_t step)
2980{
2982 int cmd_len;
2985 size_t cmd_no = 0;
2986 fr_value_box_t curr_addr;
2987
2988 if (fr_value_box_copy(request, &curr_addr, start) < 0) return XLAT_ACTION_FAIL;
2989
2990 if (prefix != (curr_addr.vb_ip.af == AF_INET ? 32 : 128)) {
2991 if (fr_value_box_cast_in_place(request, &curr_addr, curr_addr.vb_ip.af == AF_INET ?
2993 curr_addr.vb_ip.prefix = prefix;
2994 }
2995
2997 talloc_set_destructor(rctx, _redis_ippool_tool_rctx_free);
2998
2999 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
3000 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, num_addr));
3001
3002 do {
3004 uint8_t *ip_key_p = ip_key;
3005
3006 DEBUG3("Modifying %pV in pool \"%pV\"", &curr_addr, pool);
3007
3008 IPPOOL_BUILD_IP_KEY(ip_key, ip_key_p, pool->vb_strvalue, pool->vb_length, &curr_addr);
3009 if (ret == IPPOOL_RCODE_FAIL) return XLAT_ACTION_FAIL;
3010
3011 cmd_len = redisFormatCommand(&rctx->cmd_str[cmd_no], "HSET %b range %b", ip_key, ip_key_p - ip_key,
3012 (uint8_t const *)range->vb_strvalue, range->vb_length);
3013 if (cmd_len < 0) return XLAT_ACTION_FAIL;
3014
3015 if (fr_redis_command_preformatted_add(rctx->cmds, rctx->cmd_str[cmd_no++], cmd_len,
3017 talloc_free(rctx);
3018 return XLAT_ACTION_FAIL;
3019 }
3020 rctx->changes++;
3021
3022 ipaddr_inc(&curr_addr.vb_ip, step);
3023 } while (fr_ipaddr_cmp(&curr_addr.vb_ip, &end->vb_ip) != 1);
3024
3025 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, t->rtcluster, (uint8_t const *)pool->vb_strvalue,
3026 pool->vb_length, rctx->cmds, false, NULL);
3027
3028 REDIS_ASYNC_START_RCODE_PROCESS(rcode, t->rtcluster, t->cw, inst->coord_pair_reg,
3029 "Failed to launch Redis command", XLAT_ACTION_FAIL);
3030
3033}
3034
3036 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
3037 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_PREFIX }, // IP subnet
3038 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Range
3039 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
3041};
3042
3044 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
3045{
3046 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
3047 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
3048 fr_value_box_t *pool, *subnet, *range, start, end, *prefix_in;
3049 uint8_t prefix;
3050 size_t num_addr, step;
3051
3052 XLAT_ARGS(in, &pool, &subnet, &range, &prefix_in);
3053
3054 if (redis_ippool_subnet_arg_parse(request, &start, &end, &prefix, &step, &num_addr,
3055 subnet, prefix_in) < 0) return XLAT_ACTION_FAIL;
3056
3057 return redis_ippool_modify_common(request, inst, t, pool, &start, &end, range, prefix, num_addr, step);
3058}
3059
3061 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
3062 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // Start address
3063 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR }, // End address
3064 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Range
3065 { .single = true, .type = FR_TYPE_UINT8 }, // Prefix length
3067};
3068
3070 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
3071{
3072 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
3073 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
3074 fr_value_box_t *pool, *start, *end, *range, *prefix_in;
3075 uint8_t prefix = 0;
3076 size_t num_addr = 1, step = 1;
3077
3078 XLAT_ARGS(in, &pool, &start, &end, &range, &prefix_in);
3079
3080 if (redis_ippool_addresses_arg_parse(request, &prefix, &step, &num_addr,
3081 start, end, prefix_in) < 0) return XLAT_ACTION_FAIL;
3082
3083 return redis_ippool_modify_common(request, inst, t, pool, start, end, range, prefix, num_addr, step);
3084}
3085
3087 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
3088 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_PREFIX }, // IP / prefix
3089 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Owner
3090 { .concat = true, .type = FR_TYPE_STRING }, // Range
3092};
3093
3095 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
3096{
3097 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
3098 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
3099 fr_value_box_t *pool, *ipaddr, *owner, *range;
3101 int cmd_len;
3103 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
3104 uint8_t const *range_str = NULL;
3105 size_t range_len = 0;
3106
3107 XLAT_ARGS(in, &pool, &ipaddr, &owner, &range);
3108
3109 if (range && (range->type == FR_TYPE_STRING)) {
3110 range_str = (uint8_t const *)range->vb_strvalue;
3111 range_len = range->vb_length;
3112 }
3113
3115 talloc_set_destructor(rctx, _redis_ippool_tool_rctx_free);
3116
3117 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
3118 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, 1));
3119
3120 IPPOOL_SPRINT_IP(ip_buff, &ipaddr->vb_ip, ipaddr->vb_ip.prefix);
3121 cmd_len = redisFormatCommand(&rctx->cmd_str[0], "EVAL %s 1 %b %s %b %b %i", lua_assign_cmd,
3122 (uint8_t const *)pool->vb_strvalue, pool->vb_length, ip_buff,
3123 (uint8_t const *)owner->vb_strvalue, owner->vb_length,
3124 range_str, range_len, fr_time_to_sec(fr_time()));
3125 if (cmd_len < 0) return XLAT_ACTION_FAIL;
3126
3128 rctx) != FR_REDIS_PIPELINE_OK) {
3129 talloc_free(rctx);
3130 return XLAT_ACTION_FAIL;
3131 }
3132 rctx->changes++;
3133
3134 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, t->rtcluster, (uint8_t const *)pool->vb_strvalue,
3135 pool->vb_length, rctx->cmds, false, NULL);
3136
3137 REDIS_ASYNC_START_RCODE_PROCESS(rcode, t->rtcluster, t->cw, inst->coord_pair_reg,
3138 "Failed to launch Redis command", XLAT_ACTION_FAIL);
3139
3142}
3143
3145 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Pool name
3146 { .required = true, .single = true, .type = FR_TYPE_COMBO_IP_PREFIX }, // IP / prefix
3147 { .required = true, .concat = true, .type = FR_TYPE_STRING }, // Owner
3149};
3150
3152 xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *in)
3153{
3154 rlm_redis_ippool_t *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_redis_ippool_t);
3155 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(xctx->mctx->thread, rlm_redis_ippool_thread_t);
3156 fr_value_box_t *pool, *ipaddr, *owner;
3158 int cmd_len;
3160 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
3161
3162 XLAT_ARGS(in, &pool, &ipaddr, &owner);
3163
3165 talloc_set_destructor(rctx, _redis_ippool_tool_rctx_free);
3166
3167 MEM(rctx->cmds = fr_redis_command_set_alloc(rctx, request, NULL, NULL, NULL, false));
3168 MEM(rctx->cmd_str = talloc_zero_array(rctx, char *, 1));
3169
3170 IPPOOL_SPRINT_IP(ip_buff, &ipaddr->vb_ip, ipaddr->vb_ip.prefix);
3171 cmd_len = redisFormatCommand(&rctx->cmd_str[0], "EVAL %s 1 %b %s %b %i", lua_unassign_cmd,
3172 (uint8_t const *)pool->vb_strvalue, pool->vb_length, ip_buff,
3173 (uint8_t const *)owner->vb_strvalue, owner->vb_length,
3175 if (cmd_len < 0) return XLAT_ACTION_FAIL;
3176
3178 rctx) != FR_REDIS_PIPELINE_OK) {
3179 talloc_free(rctx);
3180 return XLAT_ACTION_FAIL;
3181 }
3182 rctx->changes++;
3183
3184 rctx->cmd = fr_redis_async_cmd_start(rctx, request, &rcode, t->rtcluster, (uint8_t const *)pool->vb_strvalue,
3185 pool->vb_length, rctx->cmds, false, NULL);
3186
3187 REDIS_ASYNC_START_RCODE_PROCESS(rcode, t->rtcluster, t->cw, inst->coord_pair_reg,
3188 "Failed to launch Redis command", XLAT_ACTION_FAIL);
3189
3192}
3193
3194static void lua_script_load(fr_redis_trunk_t *rtrunk, UNUSED void *uctx)
3195{
3197
3198 MEM(cmds = fr_redis_command_set_alloc(rtrunk, NULL, NULL, NULL, NULL, true));
3199
3203
3204 if (redis_command_set_enqueue(rtrunk, cmds) != FR_REDIS_PIPELINE_OK) {
3205 ERROR("Failed to enqueue lua function loading");
3206 talloc_free(cmds);
3207 }
3208}
3209
3211{
3212 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
3213 rlm_redis_ippool_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_ippool_t);
3214
3215 t->rtcluster = fr_redis_ct_alloc(t, mctx->el, &inst->conf, lua_script_load, t, true);
3216
3217 if (!t->rtcluster) return -1;
3218 t->inst = inst;
3219
3220 return 0;
3221}
3222
3224{
3225 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
3226 rlm_redis_ippool_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_ippool_t);
3227
3228 if (!inst->conf.use_cluster_map) return 0;
3229
3230 t->cw = fr_coord_attach(t, mctx->el, inst->coord_reg);
3231
3232 if (!t->cw) {
3233 ERROR("Failed to attach to coordinator");
3234 return -1;
3235 }
3236
3237 if ((inst->conf.trunk_conf.start == 0) || (fr_schedule_worker_id() != 0)) return 0;
3238
3239 return fr_redis_ct_map_bootstrap(t->rtcluster, t->cw, inst->coord_pair_reg);
3240}
3241
3243
3244static int mod_instantiate(module_inst_ctx_t const *mctx)
3245{
3246 static bool done_hash = false;
3247 CONF_SECTION *subcs = cf_section_find(mctx->mi->conf, "redis", NULL);
3248 rlm_redis_ippool_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_ippool_t);
3249
3250 fr_assert(subcs);
3251
3252 inst->conf.log_prefix = mctx->mi->name;
3253 inst->conf.module_name = mctx->mi->module->name;
3254 inst->conf.inst_name = mctx->mi->name;
3255
3256#ifdef HAVE_REDIS_SSL
3257 if (inst->conf.use_tls && !inst->conf.tls) {
3258 cf_log_err(mctx->mi->conf, "Missing tls section");
3259 return -1;
3260 }
3261#endif
3262
3263 if (!inst->conf.use_cluster_map) goto cmds;
3264
3265 if (inst->conf.database) {
3266 cf_log_err(mctx->mi->conf, "Cannot set Redis database number when cluster in use");
3267 return -1;
3268 }
3269
3271 .name = mctx->mi->name,
3272 .worker_cb = worker_pair_callbacks,
3273 .cb_id = REDIS_COORD_PAIR_CALLBACK_ID,
3274 .root = fr_dict_root(dict_redis),
3275 .cs = subcs,
3276 }
3277 );
3278 if (!inst->coord_pair_reg) return -1;
3279
3280 FR_COORD_PAIR_CB_CTX_SET(coord_callbacks, worker_callbacks, inst->coord_pair_reg);
3281
3282 inst->coord_reg = fr_coord_register(&(fr_coord_reg_ctx_t) {
3283 .name = mctx->mi->name,
3284 .coord_cb = coord_callbacks,
3285 .worker_cb = worker_callbacks,
3286 .mi = mctx->mi
3287 });
3288
3289 if (!inst->coord_reg) return -1;
3290
3291cmds:
3292 if (inst->wait_num) {
3293 inst->wait_cmd_len = redisFormatCommand(&inst->wait_cmd, "WAIT %i %i", inst->wait_num,
3294 fr_time_delta_to_msec(inst->wait_timeout));
3295 if (inst->wait_cmd_len < 0) return -1;
3296 }
3297
3298 /*
3299 * Pre-Compute the SHA1 hashes of the Lua scripts
3300 */
3301 if (!done_hash) {
3302 fr_sha1_ctx sha1_ctx;
3304
3305 fr_sha1_init(&sha1_ctx);
3306 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_alloc_cmd, sizeof(lua_alloc_cmd) - 1);
3307 fr_sha1_final(digest, &sha1_ctx);
3308 fr_base16_encode(&FR_SBUFF_OUT(lua_alloc_digest, sizeof(lua_alloc_digest)), &FR_DBUFF_TMP(digest, sizeof(digest)));
3309
3310 fr_sha1_init(&sha1_ctx);
3311 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_update_cmd, sizeof(lua_update_cmd) - 1);
3312 fr_sha1_final(digest, &sha1_ctx);
3314
3315 fr_sha1_init(&sha1_ctx);
3316 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_release_cmd, sizeof(lua_release_cmd) - 1);
3317 fr_sha1_final(digest, &sha1_ctx);
3319 done_hash = true;
3320 }
3321
3322 return 0;
3323}
3324
3326{
3327 rlm_redis_ippool_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_redis_ippool_thread_t);
3328
3329 if (!t->cw) return 0;
3330
3331 fr_coord_detach(t->cw, true);
3332 t->cw = NULL;
3333 return 0;
3334}
3335
3336static int mod_detach(module_detach_ctx_t const *mctx)
3337{
3338 rlm_redis_ippool_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_ippool_t);
3339
3340 fr_coord_deregister(inst->coord_reg);
3341 talloc_free(inst->coord_pair_reg);
3342
3343 if (inst->wait_cmd) redisFreeCommand(inst->wait_cmd);
3344
3345 return 0;
3346}
3347
3348static int mod_bootstrap(module_inst_ctx_t const *mctx)
3349{
3350 xlat_t *xlat;
3351
3352 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "subnet.add", redis_ippool_subnet_add_xlat,
3353 FR_TYPE_UINT32)) == NULL)) return -1;
3356
3357 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "addresses.add", redis_ippool_addresses_add_xlat,
3358 FR_TYPE_UINT32)) == NULL)) return -1;
3361
3362 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "subnet.remove", redis_ippool_subnet_remove_xlat,
3363 FR_TYPE_UINT32)) == NULL)) return -1;
3366
3367 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "addresses.remove", redis_ippool_addresses_remove_xlat,
3368 FR_TYPE_UINT32)) == NULL)) return -1;
3371
3372 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "subnet.release", redis_ippool_subnet_release_xlat,
3373 FR_TYPE_UINT32)) == NULL)) return -1;
3376
3377 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "addresses.release", redis_ippool_addresses_release_xlat,
3378 FR_TYPE_UINT32)) == NULL)) return -1;
3381
3382 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "subnet.modify", redis_ippool_subnet_modify_xlat,
3383 FR_TYPE_UINT32)) == NULL)) return -1;
3386
3387 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "addresses.modify", redis_ippool_addresses_modify_xlat,
3388 FR_TYPE_UINT32)) == NULL)) return -1;
3391
3392 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "address.assign", redis_ippool_assign_xlat,
3393 FR_TYPE_UINT32)) == NULL)) return -1;
3396
3397 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "address.unassign", redis_ippool_unassign_xlat,
3398 FR_TYPE_UINT32)) == NULL)) return -1;
3401
3402 if (unlikely((xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "pools.list", redis_ippool_list_xlat,
3403 FR_TYPE_STRING)) == NULL)) return -1;
3405
3406 return 0;
3407}
3408
3409static int mod_load(void)
3410{
3412
3413 return 0;
3414}
3415
3418 .common = {
3419 .magic = MODULE_MAGIC_INIT,
3420 .name = "redis_ippool",
3421 .inst_size = sizeof(rlm_redis_ippool_t),
3423 .onload = mod_load,
3424 .bootstrap = mod_bootstrap,
3425 .instantiate = mod_instantiate,
3426 .coord_attach = mod_coord_attach,
3427 .coord_detach = mod_coord_detach,
3428 .detach = mod_detach,
3430 .thread_instantiate = mod_thread_instantiate,
3431 },
3432 .method_group = {
3433 .bindings = (module_method_binding_t[]){
3434 { .section = SECTION_NAME("recv", "Access-Request"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* radius */
3435 { .section = SECTION_NAME("accounting", "Start"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* radius */
3436 { .section = SECTION_NAME("accounting", "Interim-Update"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* radius */
3437 { .section = SECTION_NAME("accounting", "Stop"), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* radius */
3438 { .section = SECTION_NAME("accounting", "Accounting-On"), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* radius */
3439 { .section = SECTION_NAME("accounting", "Accounting-Off"), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* radius */
3440
3441 { .section = SECTION_NAME("recv", "Discover"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* dhcpv4 */
3442 { .section = SECTION_NAME("recv", "Release"), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* dhcpv4 */
3443 { .section = SECTION_NAME("send", "Ack"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* dhcpv4 */
3444
3445 { .section = SECTION_NAME("recv", "Solicit"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* dhcpv6 */
3446
3447 { .section = SECTION_NAME("recv", CF_IDENT_ANY), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* generic */
3448 { .section = SECTION_NAME("send", CF_IDENT_ANY), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* generic */
3449
3450 { .section = SECTION_NAME("allocate", NULL), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* verb */
3451 { .section = SECTION_NAME("update", NULL), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* verb */
3452 { .section = SECTION_NAME("renew", NULL), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* verb */
3453 { .section = SECTION_NAME("release", NULL), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* verb */
3454 { .section = SECTION_NAME("bulk-release", NULL), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* verb */
3455
3456 { .section = SECTION_NAME("show", CF_IDENT_ANY), .method = mod_show, .method_env = &redis_ippool_show_method_env }, /* verb */
3457 { .section = SECTION_NAME("stats", CF_IDENT_ANY), .method = mod_stats, .method_env = &redis_ippool_stats_method_env }, /* verb */
3459 }
3460 }
3461};
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:38
@ UNLANG_ACTION_YIELD
Temporarily pause execution until an event occurs.
Definition action.h:42
strcpy(log_entry->msg, buffer)
#define fr_base16_encode(_out, _in)
Definition base16.h:71
static uint8_t request[4]
static uint8_t reply[4]
#define RCSID(id)
Definition build.h:603
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:422
#define STRINGIFY(x)
Definition build.h:233
#define unlikely(_x)
Definition build.h:486
#define UNUSED
Definition build.h:415
#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_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
call_env_parser_t const * env
Parsing rules for call method env.
Definition call_env.h:247
#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:722
#define FR_CONF_V3_DEPRECATED(_name, _struct, _field)
conf_parser_t entry which raises an error if a deprecated v3 item is found
Definition cf_parse.h:446
#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
@ CONF_FLAG_SUBSECTION
Instead of putting the information into a configuration structure, the configuration file routines MA...
Definition cf_parse.h:460
fr_token_t quote
Quoting around the default value. Only used for templates.
Definition cf_parse.h:704
#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:350
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:649
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:1247
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:340
#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_ct_t * fr_redis_ct_alloc(TALLOC_CTX *ctx, 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.
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.
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:145
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:689
void fr_coord_deregister(fr_coord_reg_t *coord_reg)
De-register a coordinator.
Definition coord.c:180
int fr_coord_detach(fr_coord_worker_t *cw, bool exiting)
Signal a coordinator that a worker wants to detach.
Definition coord.c:650
A coordinator registration.
Definition coord.c:93
The worker end of worker <-> coordinator communication.
Definition coord.c:82
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:556
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:177
#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:2717
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:316
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:329
#define DICT_AUTOLOAD_TERMINATOR
Definition dict.h:335
Specifies an attribute which must be present for the module to function.
Definition dict.h:315
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:328
#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
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
union fr_ipaddr_t::@162 addr
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:2062
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:1566
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:1846
#define fr_time()
Definition event.c:58
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:1297
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:284
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:47
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:477
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:1040
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:482
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:535
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:252
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:404
int fr_redis_command_set_clear(fr_redis_command_set_t *cmds)
Reinitialise a command set so that it can be used again.
Definition pipeline.c:1106
Represents a single command.
Definition pipeline.c:62
Represents a collection of pipelined commands.
Definition pipeline.c:94
@ FR_REDIS_PIPELINE_OK
No failure.
Definition pipeline.h:44
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
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
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:76
#define RETURN_UNLANG_INVALID
Definition rcode.h:72
#define RETURN_UNLANG_NOTFOUND
Definition rcode.h:74
#define RETURN_UNLANG_FAIL
Definition rcode.h:69
#define RETURN_UNLANG_NOOP
Definition rcode.h:75
@ REDIS_RCODE_SUCCESS
Operation was successful.
Definition base.h:80
void fr_redis_reply_print(fr_log_lvl_t lvl, redisReply *reply, request_t *request, size_t 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:171
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
fr_redis_async_rcode_t
Definition base.h:90
@ REDIS_ASYNC_RCODE_BOOTSTRAP
The caller should issue a request to bootstrap the cluster map.
Definition base.h:93
@ REDIS_ASYNC_RCODE_MOVE
Attempt operation on an alternative node with remap.
Definition base.h:98
@ REDIS_ASYNC_RCODE_ERROR
Unrecoverable error.
Definition base.h:92
@ REDIS_ASYNC_RCODE_ASK
Attempt operation on an alternative node.
Definition base.h:97
@ REDIS_ASYNC_RCODE_FAIL
The command set trunk request has been failed.
Definition base.h:100
@ REDIS_ASYNC_RCODE_TRY_AGAIN
Try the operation again.
Definition base.h:96
@ REDIS_ASYNC_RCODE_NO_SCRIPT
Script doesn't exist.
Definition base.h:99
@ REDIS_ASYNC_RCODE_SUCCESS
Operation was successful.
Definition base.h:91
Configuration parameters for a redis connection.
Definition base.h:124
#define IPPOOL_STATIC_BIT
#define _IPPOOL_RCODE_NOT_FOUND
ippool_rcode_t
@ IPPOOL_RCODE_EXPIRED
@ IPPOOL_RCODE_DEVICE_MISMATCH
@ IPPOOL_RCODE_NOT_FOUND
@ IPPOOL_RCODE_POOL_EMPTY
@ IPPOOL_RCODE_SUCCESS
@ IPPOOL_RCODE_FAIL
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
#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 unlang_action_t mod_stats(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
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
static xlat_action_t redis_ippool_list_resume(UNUSED TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, UNUSED fr_value_box_list_t *in)
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.
static unlang_action_t mod_show(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
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.
uint32_t changed
Number of changes reported by redis.
#define POPULATE_PAIR(_da, _i, _parent)
fr_value_box_t lease_time
How long an IP address should be allocated for.
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.
static xlat_action_t redis_ippool_common_resume(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, UNUSED fr_value_box_list_t *in)
Common resume function for pool manipulation xlats.
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
How many changes are submitted.
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
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.
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)
static int mod_coord_detach(module_thread_inst_ctx_t const *mctx)
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
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)
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 _redis_ippool_alloc_ctx_free(redis_ippool_alloc_rctx_t *rctx)
static int mod_instantiate(module_inst_ctx_t const *mctx)
static fr_dict_attr_t const * attr_ippool_lease_static
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.
static ippool_rcode_t ippool_rcode_from_reply(request_t *request, long long rcode)
Convert the rcode element of a Lua script reply to an ippool_rcode_t.
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:111
#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:261
char const * name
Instance name e.g. user_database.
Definition module.h:361
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:355
size_t inst_size
Size of the module's instance data.
Definition module.h:213
void * data
Module's instance data.
Definition module.h:297
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:70
void * boot
Data allocated during the boostrap phase.
Definition module.h:300
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:153
Named methods exported by a module.
Definition module.h:175
#define tmpl_value(_tmpl)
Definition tmpl.h:962
@ TMPL_TYPE_DATA
Value in native boxed format.
Definition tmpl.h:145
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition tmpl.h:826
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
#define FR_SIGNAL_ALLOW(_signals)
Signal mask which blocks every signal except the ones in _signals.
Definition signal.h:58
fr_signal_t
Signals that can be generated/processed by request signal handlers.
Definition signal.h:38
@ FR_SIGNAL_NONE
No signals.
Definition signal.h:39
@ 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:749
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:760
static int64_t fr_time_delta_to_msec(fr_time_delta_t delta)
Definition time.h:646
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
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
#define XLAT_ARGS(_list,...)
Populate local variables with value boxes from the input list.
Definition xlat.h:373
unsigned int required
Argument must be present, and non-empty.
Definition xlat.h:136
#define XLAT_ARG_PARSER_TERMINATOR
Definition xlat.h:160
Definition for a single argument consumed by an xlat function.
Definition xlat.h:135
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.
fr_slen_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:6256
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:6293
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:4071
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:4526
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:4326
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:4763
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:5014
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:5101
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition value.h:668
static fr_sbuff_err_t char size_t fr_sbuff_t * in
Definition value.h:1061
static fr_sbuff_err_t char ** out
Definition value.h:1061
static fr_sbuff_err_t char size_t * len
Definition value.h:1061
#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:935
int nonnull(2, 5))
#define fr_value_box_init(_vb, _type, _enumv, _tainted)
Initialise a fr_value_box_t.
Definition value.h:634
#define fr_value_box_list_foreach(_list_head, _iter)
Definition value.h:253
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