The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_redis_ippool.c
Go to the documentation of this file.
1/*
2 * This program is 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: 6880a4ab7cf900174202758169877ac2b4c1d2a1 $
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: 6880a4ab7cf900174202758169877ac2b4c1d2a1 $")
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/util/debug.h>
48#include <freeradius-devel/util/base16.h>
49#include <freeradius-devel/util/token.h>
50
51#include <freeradius-devel/redis/base.h>
52#include <freeradius-devel/redis/cluster.h>
53
54
55#include "redis_ippool.h"
56
57/** rlm_redis module instance
58 *
59 */
60typedef struct {
61 fr_redis_conf_t conf; //!< Connection parameters for the Redis server.
62 //!< Must be first field in this struct.
63
64 char const *name; //!< Instance name.
65
66 uint32_t wait_num; //!< How many slaves we want to acknowledge allocations
67 //!< or updates.
68
69 fr_time_delta_t wait_timeout; //!< How long we wait for slaves to acknowledge writing.
70
71 bool ipv4_integer; //!< Whether IPv4 addresses should be cast to integers,
72 //!< for renew operations.
73
74 bool copy_on_update; //!< Copy the address provided by ip_address to the
75 //!< allocated_address_attr if updates are successful.
76
77 fr_redis_cluster_t *cluster; //!< Redis cluster.
79
84
86 { FR_CONF_OFFSET("wait_num", rlm_redis_ippool_t, wait_num) },
87 { FR_CONF_OFFSET("wait_timeout", rlm_redis_ippool_t, wait_timeout) },
88
89 { FR_CONF_DEPRECATED("ip_address", rlm_redis_ippool_t, NULL) },
90
91 { FR_CONF_DEPRECATED("reply_attr", rlm_redis_ippool_t, NULL) },
92
93 { FR_CONF_OFFSET("ipv4_integer", rlm_redis_ippool_t, ipv4_integer) },
94 { FR_CONF_OFFSET("copy_on_update", rlm_redis_ippool_t, copy_on_update), .dflt = "yes", .quote = T_BARE_WORD },
95
96 /*
97 * Split out to allow conversion to universal ippool module with
98 * minimum of config changes.
99 */
100 { FR_CONF_POINTER("redis", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = redis_config },
102};
103
104/** Call environment used when calling redis_ippool allocate method.
105 *
106 */
107typedef struct {
108 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
109
110 fr_value_box_t offer_time; //!< How long we should reserve a lease for during
111 ///< the pre-allocation stage (typically responding
112 ///< to DHCP discover).
113
114 fr_value_box_t lease_time; //!< How long an IP address should be allocated for.
115
116 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
117 ///< or a combination of User-Name and something
118 ///< unique to the device.
119
120 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
121 ///< Option 82 gateway. Used for bulk lease cleanups.
122
123 fr_value_box_t requested_address; //!< Attribute to read the IP for renewal from.
124
125 tmpl_t *allocated_address_attr; //!< Attribute to populate with allocated IP.
126
127 tmpl_t *range_attr; //!< Attribute to write the range ID to.
128
129 tmpl_t *expiry_attr; //!< Time at which the lease will expire.
131
132/** Call environment used when calling redis_ippool update method.
133 *
134 */
135typedef struct {
136 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
137
138 fr_value_box_t lease_time; //!< How long an IP address should be allocated for.
139
140 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
141 ///< or a combination of User-Name and something
142 ///< unique to the device.
143
144 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
145 ///< Option 82 gateway. Used for bulk lease cleanups.
146
147 fr_value_box_t requested_address; //!< Attribute to read the IP for renewal from.
148
149 tmpl_t *allocated_address_attr; //!< Attribute to populate with allocated IP.
150
151 tmpl_t *range_attr; //!< Attribute to write the range ID to.
152
153 tmpl_t *expiry_attr; //!< Time at which the lease will expire.
155
156/** Call environment used when calling redis_ippool release method.
157 *
158 */
159typedef struct {
160 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
161
162 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
163 ///< or a combination of User-Name and something
164 ///< unique to the device.
165
166 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
167 ///< Option 82 gateway. Used for bulk lease cleanups.
168
169 fr_value_box_t requested_address; //!< Attribute to read the IP for renewal from.
170
172
173/** Call environment used when calling redis_ippool bulk release method.
174 *
175 */
176typedef struct {
177 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
178
179 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
180 ///< Option 82 gateway. Used for bulk lease cleanups.
182
185 .env = (call_env_parser_t[]){
187 redis_ippool_alloc_call_env_t, pool_name) },
191 redis_ippool_alloc_call_env_t, gateway_id ), .pair.dflt = "", .pair.dflt_quote = T_SINGLE_QUOTED_STRING },
195 .pair.dflt = "%{%{Requested-IP-Address} || %{Net.Src.IP}}", .pair.dflt_quote = T_DOUBLE_QUOTED_STRING },
198 .pair.dflt = "reply.IP-Pool.Range", .pair.dflt_quote = T_BARE_WORD },
201 }
202};
203
206 .env = (call_env_parser_t[]) {
210 .pair.dflt = "", .pair.dflt_quote = T_SINGLE_QUOTED_STRING },
213 .pair.dflt = "%{Requested-IP-Address || Net.Src.IP}", .pair.dflt_quote = T_DOUBLE_QUOTED_STRING },
216 .pair.dflt = "reply.IP-Pool.Range", .pair.dflt_quote = T_BARE_WORD },
219 }
220};
221
234
244
245#define EOL "\n"
246
247/** Lua script for allocating new leases
248 *
249 * - KEYS[1] The pool name.
250 * - ARGV[1] Wall time (seconds since epoch).
251 * - ARGV[2] Expires in (seconds).
252 * - ARGV[3] Lease owner identifier (administratively configured).
253 * - ARGV[4] (optional) Gateway identifier.
254 * - ARGV[5] (optional) Requested address.
255 *
256 * Returns @verbatim { <rcode>[, <ip>][, <range>][, <lease time>][, <counter>] } @endverbatim
257 * - IPPOOL_RCODE_SUCCESS lease updated..
258 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
259 */
260static char lua_alloc_cmd[] =
261 "local ip" EOL /* 1 */
262 "local exists" EOL /* 2 */
263
264 "local pool_key" EOL /* 3 */
265 "local address_key" EOL /* 4 */
266 "local owner_key" EOL /* 5 */
267
268 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 6 */
269 "owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[3]" EOL /* 7 */
270
271 "local wall_time = tonumber(ARGV[1])" EOL /* 8* */
272
273 /*
274 * Check to see if the client already has a lease,
275 * and if it does return that.
276 *
277 * The additional sanity checks are to allow for the record
278 * of device/ip binding to persist for longer than the lease.
279 */
280 "exists = redis.call('GET', owner_key);" EOL /* 9 */
281 "if exists then" EOL /* 10 */
282 " local expires = tonumber(redis.call('ZSCORE', pool_key, exists))" EOL /* 11 */
283 " local static = expires >= " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 12 */
284 " local expires_in = expires - (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0) - ARGV[1]" EOL /* 13 */
285 " if expires_in > 0 or static then" EOL /* 14 */
286 " ip = redis.call('HMGET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. exists, 'device', 'range', 'counter', 'gateway')" EOL /* 15 */
287 " if ip and (ip[1] == ARGV[3]) then" EOL /* 16 */
288 " if expires_in < tonumber(ARGV[2]) then" EOL /* 17 */
289 " redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2] + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), exists)" EOL /* 18 */
290 " expires_in = tonumber(ARGV[2])" EOL /* 19 */
291 " if not static then" EOL /* 20 */
292 " redis.call('EXPIRE', owner_key, ARGV[2])" EOL /* 21 */
293 " end" EOL /* 22 */
294 " end" EOL /* 23 */
295
296 /*
297 * Ensure gateway is set correctly
298 */
299 " if ARGV[4] ~= ip[4] then" EOL /* 24 */
300 " redis.call('HSET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":', 'gateway', ARGV[4])" EOL /* 25 */
301 " end" EOL /* 26 */
302 " return {" STRINGIFY(_IPPOOL_RCODE_SUCCESS) ", exists, ip[2], expires_in, ip[3] }" EOL /* 27 */
303 " end" EOL /* 28 */
304 " end" EOL /* 29 */
305 "end" EOL /* 30 */
306
307 /*
308 * If there's a requested address, check if that is available i.e. not statically
309 * assigned, nor already allocated.
310 */
311 "if ARGV[5] and ARGV[5] ~= '' then" EOL /* 31 */
312 " local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[5]))" EOL /* 32 */
313 " if expires and tonumber(expires) < wall_time then" EOL /* 33 */
314 " ip = { ARGV[5] }" EOL /* 34 */
315 " end" EOL /* 35 */
316 "end" EOL /* 36 */
317
318 /*
319 * Else, get the IP address which expired the longest time ago.
320 */
321 "if not ip then" EOL /* 37 */
322 " ip = redis.call('ZREVRANGE', pool_key, -1, -1, 'WITHSCORES')" EOL /* 38 */
323 " if not ip or not ip[1] then" EOL /* 39 */
324 " return {" STRINGIFY(_IPPOOL_RCODE_POOL_EMPTY) "}" EOL /* 40 */
325 " end" EOL /* 41 */
326 " if tonumber(ip[2]) >= wall_time then" EOL /* 42 */
327 " return {" STRINGIFY(_IPPOOL_RCODE_POOL_EMPTY) "}" EOL /* 43 */
328 " end" EOL /* 44 */
329 "end" EOL /* 45 */
330 "redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2], ip[1])" EOL /* 46 */
331
332 /*
333 * Set the device/gateway keys
334 */
335 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ip[1]" EOL /* 47 */
336 "redis.call('HMSET', address_key, 'device', ARGV[3], 'gateway', ARGV[4])" EOL /* 48 */
337 "redis.call('SET', owner_key, ip[1])" EOL /* 49 */
338 "redis.call('EXPIRE', owner_key, ARGV[2])" EOL /* 50 */
339 "return { " EOL /* 51 */
340 " " STRINGIFY(_IPPOOL_RCODE_SUCCESS) "," EOL /* 52 */
341 " ip[1], " EOL /* 53 */
342 " redis.call('HGET', address_key, 'range'), " EOL /* 54 */
343 " tonumber(ARGV[2]), " EOL /* 55 */
344 " redis.call('HINCRBY', address_key, 'counter', 1)" EOL /* 56 */
345 "}" EOL; /* 57 */
347
348/** Lua script for updating leases
349 *
350 * - KEYS[1] The pool name.
351 * - ARGV[1] Wall time (seconds since epoch).
352 * - ARGV[2] Expires in (seconds).
353 * - ARGV[3] IP address to update.
354 * - ARGV[4] Lease owner identifier.
355 * - ARGV[5] (optional) Gateway identifier.
356 *
357 * Returns @verbatim array { <rcode>[, <range>] } @endverbatim
358 * - IPPOOL_RCODE_SUCCESS lease updated..
359 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
360 * - IPPOOL_RCODE_EXPIRED lease has already expired.
361 * - IPPOOL_RCODE_DEVICE_MISMATCH lease was allocated to a different client.
362 */
363static char lua_update_cmd[] =
364 "local ret" EOL /* 1 */
365 "local found" EOL /* 2 */
366
367 "local pool_key" EOL /* 3 */
368 "local address_key" EOL /* 4 */
369 "local owner_key" EOL /* 5 */
370
371 /*
372 * We either need to know that the IP was last allocated to the
373 * same device, or that the lease on the IP has NOT expired.
374 */
375 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[3]" EOL /* 6 */
376 "found = redis.call('HMGET', address_key, 'range', 'device', 'gateway', 'counter' )" EOL /* 7 */
377 /*
378 * Range may be nil (if not used), so we use the device key
379 */
380 "if not found[2] then" EOL /* 8 */
381 " return {" STRINGIFY(_IPPOOL_RCODE_NOT_FOUND) "}" EOL /* 9 */
382 "end" EOL /* 10 */
383 "if found[2] ~= ARGV[4] then" EOL /* 11 */
384 " return {" STRINGIFY(_IPPOOL_RCODE_DEVICE_MISMATCH) ", found[2]}" EOL /* 12 */
385 "end" EOL /* 13 */
386
387 /*
388 * Update the expiry time
389 */
390 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 14 */
391 "local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[3]))" EOL /* 15 */
392 "local static = expires > " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 16 */
393 "redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2] + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), ARGV[3])" EOL /* 17 */
394
395 /*
396 * The device key should usually exist, but
397 * theoretically, if we were right on the cusp
398 * of a lease being expired, it may have been
399 * removed.
400 */
401 "owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[4]" EOL /* 18 */
402 "if not static and (redis.call('EXPIRE', owner_key, ARGV[2]) == 0) then" EOL /* 19 */
403 " redis.call('SET', owner_key, ARGV[3])" EOL /* 20 */
404 " redis.call('EXPIRE', owner_key, ARGV[2])" EOL /* 21 */
405 "end" EOL /* 22 */
406
407 /*
408 * Update the gateway address
409 */
410 "if ARGV[5] ~= found[3] then" EOL /* 23 */
411 " redis.call('HSET', address_key, 'gateway', ARGV[5])" EOL /* 24 */
412 "end" EOL /* 25 */
413 "return { " STRINGIFY(_IPPOOL_RCODE_SUCCESS) ", found[1], found[4] }"EOL; /* 26 */
415
416/** Lua script for releasing leases
417 *
418 * - KEYS[1] The pool name.
419 * - ARGV[1] Wall time (seconds since epoch).
420 * - ARGV[2] IP address to release.
421 * - ARGV[3] Client identifier.
422 *
423 * Sets the expiry time to be NOW() - 1 to maximise time between
424 * IP address allocations.
425 *
426 * Returns @verbatim array { <rcode>[, <counter>] } @endverbatim
427 * - IPPOOL_RCODE_SUCCESS lease updated..
428 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
429 * - IPPOOL_RCODE_DEVICE_MISMATCH lease was allocated to a different client..
430 */
431static char lua_release_cmd[] =
432 "local ret" EOL /* 1 */
433 "local found" EOL /* 2 */
434
435 "local pool_key" EOL /* 3 */
436 "local address_key" EOL /* 4 */
437 "local owner_key" EOL /* 5 */
438
439 /*
440 * Check that the device releasing was the one
441 * the IP address is allocated to.
442 */
443 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[2]" EOL /* 6 */
444 "found = redis.call('HGET', address_key, 'device')" EOL /* 7 */
445 "if not found then" EOL /* 8 */
446 " return { " STRINGIFY(_IPPOOL_RCODE_NOT_FOUND) "}" EOL /* 9 */
447 "end" EOL /* 10 */
448 "if found and found ~= ARGV[3] then" EOL /* 11 */
449 " return { " STRINGIFY(_IPPOOL_RCODE_DEVICE_MISMATCH) ", found }" EOL /* 12 */
450 "end" EOL /* 13 */
451
452 /*
453 * Set expiry time to now() - 1
454 */
455 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 14 */
456 "found = tonumber(redis.call('ZSCORE', pool_key, ARGV[2]))" EOL /* 15 */
457 "local static = found > " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 16 */
458 "redis.call('ZADD', pool_key, 'XX', ARGV[1] - 1 + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), ARGV[2])" EOL /* 17 */
459
460 /*
461 * Remove the association between the device and a lease
462 */
463 "if not static then" EOL /* 18 */
464 " owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[3]" EOL /* 19 */
465 " redis.call('DEL', owner_key)" EOL /* 20 */
466 "end" EOL /* 21 */
467 "return { " EOL /* 22 */
468 " " STRINGIFY(_IPPOOL_RCODE_SUCCESS) "," EOL /* 23 */
469 " redis.call('HINCRBY', address_key, 'counter', 1) - 1" EOL /* 24 */
470 "}"; /* 25 */
472
473/** Check the requisite number of slaves replicated the lease info
474 *
475 * @param request The current request.
476 * @param wait_num Number of slaves required.
477 * @param reply we got from the server.
478 * @return
479 * - 0 if enough slaves replicated the data.
480 * - -1 if too few slaves replicated the data, or another error.
481 */
482static inline int ippool_wait_check(request_t *request, uint32_t wait_num, redisReply *reply)
483{
484 if (!wait_num) return 0;
485
486 if (reply->type != REDIS_REPLY_INTEGER) {
487 REDEBUG("WAIT result is wrong type, expected integer got %s",
488 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
489 return -1;
490 }
491 if (reply->integer < wait_num) {
492 REDEBUG("Too few slaves acknowledged allocation, needed %i, got %lli",
493 wait_num, reply->integer);
494 return -1;
495 }
496 return 0;
497}
498
499static void ippool_action_print(request_t *request, ippool_action_t action,
500 fr_log_lvl_t lvl,
501 fr_value_box_t const *key_prefix,
502 fr_value_box_t const *ip,
503 fr_value_box_t const *owner,
504 fr_value_box_t const *gateway_id,
505 uint32_t expires)
506{
507 char *device_str = NULL, *gateway_str = NULL;
508
509 if (gateway_id && gateway_id->vb_length > 0) gateway_str = fr_asprint(request, gateway_id->vb_strvalue,
510 gateway_id->vb_length, '"');
511 if (owner && owner->vb_length > 0) device_str = fr_asprint(request, owner->vb_strvalue, owner->vb_length, '"');
512
513 switch (action) {
515 RDEBUGX(lvl, "Allocating lease from pool \"%pV\"%s%s%s%s%s%s, expires in %us",
516 key_prefix,
517 device_str ? ", to \"" : "", device_str ? device_str : "",
518 device_str ? "\"" : "",
519 gateway_str ? ", on \"" : "", gateway_str ? gateway_str : "",
520 gateway_str ? "\"" : "",
521 expires);
522 break;
523
525 RDEBUGX(lvl, "Updating %pV in pool \"%pV\"%s%s%s%s%s%s, expires in %us",
526 ip, key_prefix,
527 device_str ? ", device \"" : "", device_str ? device_str : "",
528 device_str ? "\"" : "",
529 gateway_str ? ", gateway \"" : "", gateway_str ? gateway_str : "",
530 gateway_str ? "\"" : "",
531 expires);
532 break;
533
535 RDEBUGX(lvl, "Releasing %pV%s%s%s to pool \"%pV\"",
536 ip,
537 device_str ? " leased by \"" : "", device_str ? device_str : "",
538 device_str ? "\"" : "",
539 key_prefix);
540 break;
541
542 default:
543 break;
544 }
545
546 /*
547 * Ordering is important, needs to be LIFO
548 * for proper talloc pool reuse.
549 */
550 talloc_free(device_str);
551 talloc_free(gateway_str);
552}
553
554/** Execute a script against Redis cluster
555 *
556 * Handles uploading the script to the server if required.
557 *
558 * @note All replies will be freed on error.
559 *
560 * @param[out] out Where to write Redis reply object resulting from the command.
561 * @param[in] request The current request.
562 * @param[in] cluster configuration.
563 * @param[in] key to use to determine the cluster node.
564 * @param[in] key_len length of the key.
565 * @param[in] wait_num If > 0 wait until this many slaves have replicated the data
566 * from the last command.
567 * @param[in] wait_timeout How long to wait for slaves to replicate the data.
568 * @param[in] digest of script.
569 * @param[in] script to upload.
570 * @param[in] cmd EVALSHA command to execute.
571 * @param[in] ... Arguments for the eval command.
572 * @return status of the command.
573 */
574static fr_redis_rcode_t ippool_script(redisReply **out, request_t *request, fr_redis_cluster_t *cluster,
575 uint8_t const *key, size_t key_len,
576 uint32_t wait_num, fr_time_delta_t wait_timeout,
577 char const digest[], char const *script,
578 char const *cmd, ...)
579{
580 fr_redis_conn_t *conn;
581 redisReply *replies[5]; /* Must be equal to the maximum number of pipelined commands */
582 size_t reply_cnt = 0, i;
583
585 fr_redis_rcode_t s_ret, status;
586 unsigned int pipelined = 0;
587
588 va_list ap;
589
590 *out = NULL;
591
592#ifndef NDEBUG
593 memset(replies, 0, sizeof(replies));
594#endif
595
596 va_start(ap, cmd);
597
598 for (s_ret = fr_redis_cluster_state_init(&state, &conn, cluster, request, key, key_len, false);
599 s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */
600 s_ret = fr_redis_cluster_state_next(&state, &conn, cluster, request, status, &replies[0])) {
601 va_list copy;
602
603 RDEBUG3("Calling script 0x%s", digest);
604 va_copy(copy, ap); /* copy or segv */
605 redisvAppendCommand(conn->handle, cmd, copy);
606 va_end(copy);
607 pipelined = 1;
608 if (wait_num) {
609 redisAppendCommand(conn->handle, "WAIT %i %i", wait_num, fr_time_delta_to_msec(wait_timeout));
610 pipelined++;
611 }
612 reply_cnt = fr_redis_pipeline_result(&pipelined, &status,
613 replies, NUM_ELEMENTS(replies),
614 conn);
615 if (status != REDIS_RCODE_NO_SCRIPT) continue;
616
617 /*
618 * Clear out the existing reply
619 */
620 fr_redis_pipeline_free(replies, reply_cnt);
621
622 /*
623 * Last command failed with NOSCRIPT, this means
624 * we have to send the Lua script up to the node
625 * so it can be cached.
626 */
627 RDEBUG3("Loading script 0x%s", digest);
628 redisAppendCommand(conn->handle, "MULTI");
629 redisAppendCommand(conn->handle, "SCRIPT LOAD %s", script);
630 va_copy(copy, ap); /* copy or segv */
631 redisvAppendCommand(conn->handle, cmd, copy);
632 va_end(copy);
633 redisAppendCommand(conn->handle, "EXEC");
634 pipelined = 4;
635 if (wait_num) {
636 redisAppendCommand(conn->handle, "WAIT %i %i", wait_num, fr_time_delta_to_msec(wait_timeout));
637 pipelined++;
638 }
639
640 reply_cnt = fr_redis_pipeline_result(&pipelined, &status,
641 replies, NUM_ELEMENTS(replies),
642 conn);
643 if (status == REDIS_RCODE_SUCCESS) {
644 if (RDEBUG_ENABLED3) for (i = 0; i < reply_cnt; i++) {
645 fr_redis_reply_print(L_DBG_LVL_3, replies[i], request, i, status);
646 }
647
648 if (replies[3]->type != REDIS_REPLY_ARRAY) {
649 RERROR("Bad response to EXEC, expected array got %s",
650 fr_table_str_by_value(redis_reply_types, replies[3]->type, "<UNKNOWN>"));
651 error:
652 fr_redis_pipeline_free(replies, reply_cnt);
653 status = REDIS_RCODE_ERROR;
654 goto finish;
655 }
656 if (replies[3]->elements != 2) {
657 RERROR("Bad response to EXEC, expected 2 result elements, got %zu",
658 replies[3]->elements);
659 goto error;
660 }
661 if (replies[3]->element[0]->type != REDIS_REPLY_STRING) {
662 RERROR("Bad response to SCRIPT LOAD, expected string got %s",
663 fr_table_str_by_value(redis_reply_types, replies[3]->element[0]->type, "<UNKNOWN>"));
664 goto error;
665 }
666 if (strcmp(replies[3]->element[0]->str, digest) != 0) {
667 RWDEBUG("Incorrect SHA1 from SCRIPT LOAD, expected %s, got %s",
668 digest, replies[3]->element[0]->str);
669 goto error;
670 }
671 }
672 }
673 if (s_ret != REDIS_RCODE_SUCCESS) goto error;
674
675 switch (reply_cnt) {
676 case 2: /* EVALSHA with wait */
677 if (ippool_wait_check(request, wait_num, replies[1]) < 0) goto error;
678 fr_redis_reply_free(&replies[1]); /* Free the wait response */
680
681 case 1: /* EVALSHA */
682 *out = replies[0];
683 break;
684
685 case 5: /* LOADSCRIPT + EVALSHA + WAIT */
686 if (ippool_wait_check(request, wait_num, replies[4]) < 0) goto error;
687 fr_redis_reply_free(&replies[4]); /* Free the wait response */
689
690 case 4: /* LOADSCRIPT + EVALSHA */
691 fr_redis_reply_free(&replies[2]); /* Free the queued cmd response*/
692 fr_redis_reply_free(&replies[1]); /* Free the queued script load response */
693 fr_redis_reply_free(&replies[0]); /* Free the queued multi response */
694 *out = replies[3]->element[1];
695 replies[3]->element[1] = NULL; /* Prevent double free */
696 fr_redis_reply_free(&replies[3]); /* This works because hiredis checks for NULL elements */
697 break;
698
699 case 0:
700 break;
701 }
702
703finish:
704 va_end(ap);
705 return s_ret;
706}
707
708/** Allocate a new IP address from a pool
709 *
710 */
713{
714 struct timeval now;
715 redisReply *reply = NULL;
716
717 fr_redis_rcode_t status;
719
720 fr_assert(env->pool_name.vb_length > 0);
721 fr_assert(env->owner.vb_length > 0);
722
724
725 if ((env->requested_address.datum.ip.af == AF_INET) && inst->ipv4_integer) {
726 status = ippool_script(&reply, request, inst->cluster,
727 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
728 inst->wait_num, inst->wait_timeout,
730 "EVALSHA %s 1 %b %u %u %b %b %u",
732 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
733 (unsigned int)now.tv_sec, lease_time,
734 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length,
735 (uint8_t const *)env->gateway_id.vb_strvalue, env->gateway_id.vb_length,
736 htonl(env->requested_address.datum.ip.addr.v4.s_addr));
737 } else {
738 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
739 if (env->requested_address.type == FR_TYPE_COMBO_IP_ADDR) {
740 IPPOOL_SPRINT_IP(ip_buff, &env->requested_address.datum.ip, env->requested_address.datum.ip.prefix);
741 } else {
742 ip_buff[0] = '\0';
743 }
744
745 status = ippool_script(&reply, request, inst->cluster,
746 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
747 inst->wait_num, inst->wait_timeout,
749 "EVALSHA %s 1 %b %u %u %b %b %s",
751 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
752 (unsigned int)now.tv_sec, lease_time,
753 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length,
754 (uint8_t const *)env->gateway_id.vb_strvalue, env->gateway_id.vb_length,
755 ip_buff);
756 }
757 if (status != REDIS_RCODE_SUCCESS) {
758 ret = IPPOOL_RCODE_FAIL;
759 goto finish;
760 }
761
762 fr_assert(reply);
763 if (reply->type != REDIS_REPLY_ARRAY) {
764 REDEBUG("Expected result to be array got \"%s\"",
765 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
766 ret = IPPOOL_RCODE_FAIL;
767 goto finish;
768 }
769
770 if (reply->elements == 0) {
771 REDEBUG("Got empty result array");
772 ret = IPPOOL_RCODE_FAIL;
773 goto finish;
774 }
775
776 /*
777 * Process return code
778 */
779 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
780 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
781 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
782 ret = IPPOOL_RCODE_FAIL;
783 goto finish;
784 }
785 ret = reply->element[0]->integer;
786 if (ret < 0) goto finish;
787
788 /*
789 * Process IP address
790 */
791 if (reply->elements > 1) {
792 tmpl_t ip_rhs;
793 map_t ip_map = {
795 .op = T_OP_SET,
796 .rhs = &ip_rhs
797 };
798
799 tmpl_init_shallow(&ip_rhs, TMPL_TYPE_DATA, T_BARE_WORD, "", 0, NULL);
800 switch (reply->element[1]->type) {
801 /*
802 * Destination attribute may not be IPv4, in which case
803 * we want to pre-convert the integer value to an IPv4
804 * address before casting it once more to the type of
805 * the destination attribute.
806 */
807 case REDIS_REPLY_INTEGER:
808 {
809 if (tmpl_attr_tail_da(ip_map.lhs)->type != FR_TYPE_IPV4_ADDR) {
810 fr_value_box_t tmp;
811
812 fr_value_box(&tmp, (uint32_t)ntohl((uint32_t)reply->element[1]->integer), true);
814 NULL, &tmp)) {
815 RPEDEBUG("Failed converting integer to IPv4 address");
816 ret = IPPOOL_RCODE_FAIL;
817 goto finish;
818 }
819 } else {
820 fr_value_box(&ip_map.rhs->data.literal,
821 (uint32_t)ntohl((uint32_t)reply->element[1]->integer), true);
822 }
823 }
824 goto do_ip_map;
825
826 case REDIS_REPLY_STRING:
827 fr_value_box_bstrndup_shallow(&ip_map.rhs->data.literal,
828 NULL, reply->element[1]->str, reply->element[1]->len, false);
829 do_ip_map:
830 if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) {
831 ret = IPPOOL_RCODE_FAIL;
832 goto finish;
833 }
834 break;
835
836 default:
837 REDEBUG("Server returned unexpected type \"%s\" for IP element (result[1])",
838 fr_table_str_by_value(redis_reply_types, reply->element[1]->type, "<UNKNOWN>"));
839 ret = IPPOOL_RCODE_FAIL;
840 goto finish;
841 }
842 }
843
844 /*
845 * Process Range identifier
846 */
847 if (reply->elements > 2) {
848 switch (reply->element[2]->type) {
849 /*
850 * Add range ID to request
851 */
852 case REDIS_REPLY_STRING:
853 {
854 tmpl_t range_rhs;
855 map_t range_map = {
856 .lhs = env->range_attr,
857 .op = T_OP_SET,
858 .rhs = &range_rhs
859 };
860
862 fr_value_box_bstrndup_shallow(&range_map.rhs->data.literal,
863 NULL, reply->element[2]->str, reply->element[2]->len, true);
864 if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) {
865 ret = IPPOOL_RCODE_FAIL;
866 goto finish;
867 }
868 }
869 break;
870
871 case REDIS_REPLY_NIL:
872 break;
873
874 default:
875 REDEBUG("Server returned unexpected type \"%s\" for range element (result[2])",
876 fr_table_str_by_value(redis_reply_types, reply->element[2]->type, "<UNKNOWN>"));
877 ret = IPPOOL_RCODE_FAIL;
878 goto finish;
879 }
880 }
881
882 /*
883 * Process Expiry time
884 */
885 if (env->expiry_attr && (reply->elements > 3)) {
886 tmpl_t expiry_rhs;
887 map_t expiry_map = {
888 .lhs = env->expiry_attr,
889 .op = T_OP_SET,
890 .rhs = &expiry_rhs
891 };
892
893 tmpl_init_shallow(&expiry_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
894 if (reply->element[3]->type != REDIS_REPLY_INTEGER) {
895 REDEBUG("Server returned unexpected type \"%s\" for expiry element (result[3])",
896 fr_table_str_by_value(redis_reply_types, reply->element[3]->type, "<UNKNOWN>"));
897 ret = IPPOOL_RCODE_FAIL;
898 goto finish;
899 }
900
901 fr_value_box(&expiry_map.rhs->data.literal, (uint32_t)reply->element[3]->integer, true);
902 if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) {
903 ret = IPPOOL_RCODE_FAIL;
904 goto finish;
905 }
906 }
907finish:
908 fr_redis_reply_free(&reply);
909 return ret;
910}
911
912/** Update an existing IP address in a pool
913 *
914 */
917 fr_ipaddr_t *ip,
918 fr_value_box_t const *owner,
919 fr_value_box_t const *gateway_id,
920 uint32_t expires)
921{
922 struct timeval now;
923 redisReply *reply = NULL;
924
925 fr_redis_rcode_t status;
927
929
930 if ((ip->af == AF_INET) && inst->ipv4_integer) {
931 status = ippool_script(&reply, request, inst->cluster,
932 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
933 inst->wait_num, inst->wait_timeout,
935 "EVALSHA %s 1 %b %u %u %u %b %b",
937 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
938 (unsigned int)now.tv_sec, expires,
939 htonl(ip->addr.v4.s_addr),
940 (uint8_t const *)owner->vb_strvalue, owner->vb_length,
941 (uint8_t const *)gateway_id->vb_strvalue, gateway_id->vb_length);
942 } else {
943 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
944
945 IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix);
946 status = ippool_script(&reply, request, inst->cluster,
947 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
948 inst->wait_num, inst->wait_timeout,
950 "EVALSHA %s 1 %b %u %u %s %b %b",
952 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
953 (unsigned int)now.tv_sec, expires,
954 ip_buff,
955 (uint8_t const *)owner->vb_strvalue, owner->vb_length,
956 (uint8_t const *)gateway_id->vb_strvalue, gateway_id->vb_length);
957 }
958 if (status != REDIS_RCODE_SUCCESS) {
959 ret = IPPOOL_RCODE_FAIL;
960 goto finish;
961 }
962
963 if (reply->type != REDIS_REPLY_ARRAY) {
964 REDEBUG("Expected result to be array got \"%s\"",
965 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
966 ret = IPPOOL_RCODE_FAIL;
967 goto finish;
968 }
969
970 if (reply->elements == 0) {
971 REDEBUG("Got empty result array");
972 ret = IPPOOL_RCODE_FAIL;
973 goto finish;
974 }
975
976 /*
977 * Process return code
978 */
979 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
980 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
981 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
982 ret = IPPOOL_RCODE_FAIL;
983 goto finish;
984 }
985 ret = reply->element[0]->integer;
986 if (ret < 0) goto finish;
987
988 /*
989 * Process Range identifier
990 */
991 if (reply->elements > 1) {
992 switch (reply->element[1]->type) {
993 /*
994 * Add range ID to request
995 */
996 case REDIS_REPLY_STRING:
997 {
998 tmpl_t range_rhs;
999 map_t range_map = { .lhs = env->range_attr, .op = T_OP_SET, .rhs = &range_rhs };
1000
1001 tmpl_init_shallow(&range_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
1002 fr_value_box_bstrndup_shallow(&range_map.rhs->data.literal, NULL,
1003 reply->element[1]->str, reply->element[1]->len, true);
1004 if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) {
1005 ret = IPPOOL_RCODE_FAIL;
1006 goto finish;
1007 }
1008 }
1009 break;
1010
1011 case REDIS_REPLY_NIL:
1012 break;
1013
1014 default:
1015 REDEBUG("Server returned unexpected type \"%s\" for range element (result[1])",
1016 fr_table_str_by_value(redis_reply_types, reply->element[0]->type, "<UNKNOWN>"));
1017 ret = IPPOOL_RCODE_FAIL;
1018 goto finish;
1019 }
1020 }
1021
1022 /*
1023 * Copy expiry time to expires attribute (if set)
1024 */
1025 if (env->expiry_attr) {
1026 tmpl_t expiry_rhs;
1027 map_t expiry_map = {
1028 .lhs = env->expiry_attr,
1029 .op = T_OP_SET,
1030 .rhs = &expiry_rhs
1031 };
1032
1033
1034 tmpl_init_shallow(&expiry_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
1035
1036 fr_value_box(&expiry_map.rhs->data.literal, expires, false);
1037 if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) {
1038 ret = IPPOOL_RCODE_FAIL;
1039 goto finish;
1040 }
1041 }
1042
1043finish:
1044 fr_redis_reply_free(&reply);
1045
1046 return ret;
1047}
1048
1049/** Release an existing IP address in a pool
1050 *
1051 */
1053 fr_value_box_t const *key_prefix,
1054 fr_ipaddr_t *ip,
1055 fr_value_box_t const *owner)
1056{
1057 struct timeval now;
1058 redisReply *reply = NULL;
1059
1060 fr_redis_rcode_t status;
1062
1063 now = fr_time_to_timeval(fr_time());
1064
1065 if ((ip->af == AF_INET) && inst->ipv4_integer) {
1066 status = ippool_script(&reply, request, inst->cluster,
1067 (uint8_t const *)key_prefix->vb_strvalue, key_prefix->vb_length,
1068 inst->wait_num, inst->wait_timeout,
1070 "EVALSHA %s 1 %b %u %u %b",
1072 (uint8_t const *)key_prefix->vb_strvalue, key_prefix->vb_length,
1073 (unsigned int)now.tv_sec,
1074 htonl(ip->addr.v4.s_addr),
1075 (uint8_t const *)owner->vb_strvalue, owner->vb_length);
1076 } else {
1077 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
1078
1079 IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix);
1080 status = ippool_script(&reply, request, inst->cluster,
1081 (uint8_t const *)key_prefix->vb_strvalue, key_prefix->vb_length,
1082 inst->wait_num, inst->wait_timeout,
1084 "EVALSHA %s 1 %b %u %s %b",
1086 (uint8_t const *)key_prefix->vb_strvalue, key_prefix->vb_length,
1087 (unsigned int)now.tv_sec,
1088 ip_buff,
1089 (uint8_t const *)owner->vb_strvalue, owner->vb_length);
1090 }
1091 if (status != REDIS_RCODE_SUCCESS) {
1092 ret = IPPOOL_RCODE_FAIL;
1093 goto finish;
1094 }
1095
1096 if (reply->type != REDIS_REPLY_ARRAY) {
1097 REDEBUG("Expected result to be array got \"%s\"",
1098 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1099 ret = IPPOOL_RCODE_FAIL;
1100 goto finish;
1101 }
1102
1103 if (reply->elements == 0) {
1104 REDEBUG("Got empty result array");
1105 ret = IPPOOL_RCODE_FAIL;
1106 goto finish;
1107 }
1108
1109 /*
1110 * Process return code
1111 */
1112 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
1113 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
1114 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1115 ret = IPPOOL_RCODE_FAIL;
1116 goto finish;
1117 }
1118 ret = reply->element[0]->integer;
1119 if (ret < 0) goto finish;
1120
1121finish:
1122 fr_redis_reply_free(&reply);
1123
1124 return ret;
1125}
1126
1127#define CHECK_POOL_NAME \
1128 if (env->pool_name.vb_length > IPPOOL_MAX_KEY_PREFIX_SIZE) { \
1129 REDEBUG("Pool name too long. Expected %u bytes, got %ld bytes", \
1130 IPPOOL_MAX_KEY_PREFIX_SIZE, env->pool_name.vb_length); \
1131 RETURN_UNLANG_FAIL; \
1132 } \
1133 if (env->pool_name.vb_length == 0) { \
1134 RDEBUG2("Empty pool name. Doing nothing"); \
1135 RETURN_UNLANG_NOOP; \
1136 }
1137
1138static unlang_action_t CC_HINT(nonnull) mod_alloc(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
1139{
1141 redis_ippool_alloc_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_alloc_call_env_t);
1142 uint32_t lease_time;
1143
1145
1146 /*
1147 * If offer_time is defined, it will be FR_TYPE_UINT32.
1148 * Fall back to lease_time otherwise.
1149 */
1150 lease_time = (env->offer_time.type == FR_TYPE_UINT32) ?
1151 env->offer_time.vb_uint32 : env->lease_time.vb_uint32;
1153 &env->owner, &env->gateway_id, lease_time);
1154 switch (redis_ippool_allocate(inst, request, env, lease_time)) {
1156 RDEBUG2("IP address lease allocated");
1158
1160 RWDEBUG("Pool contains no free addresses");
1162
1163 default:
1165 }
1166}
1167
1168static unlang_action_t CC_HINT(nonnull) mod_update(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
1169{
1171 redis_ippool_update_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_update_call_env_t);
1172
1174
1176 &env->requested_address, &env->owner, &env->gateway_id, env->lease_time.vb_uint32);
1177 switch (redis_ippool_update(inst, request, env,
1178 &env->requested_address.datum.ip, &env->owner,
1179 &env->gateway_id,
1180 env->lease_time.vb_uint32)) {
1182 RDEBUG2("Requested IP address' \"%pV\" lease updated", &env->requested_address);
1183
1184 /*
1185 * Copy over the input IP address to the reply attribute
1186 */
1187 if (inst->copy_on_update) {
1188 tmpl_t ip_rhs = {
1189 .name = "",
1190 .type = TMPL_TYPE_DATA,
1191 .quote = T_BARE_WORD,
1192 };
1193 map_t ip_map = {
1195 .op = T_OP_SET,
1196 .rhs = &ip_rhs
1197 };
1198
1199 if (unlikely(fr_value_box_copy(NULL, &ip_rhs.data.literal, &env->requested_address) < 0)) {
1200 RPEDEBUG("Failed copying IP address to reply attribute");
1202 }
1203
1204 if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) RETURN_UNLANG_FAIL;
1205 }
1207
1208 /*
1209 * It's useful to be able to identify the 'not found' case
1210 * as we can relay to a server where the IP address might
1211 * be found. This extremely useful for migrations.
1212 */
1214 REDEBUG("Requested IP address \"%pV\" is not a member of the specified pool",
1215 &env->requested_address);
1217
1219 REDEBUG("Requested IP address' \"%pV\" lease already expired at time of renewal",
1220 &env->requested_address);
1222
1224 REDEBUG("Requested IP address' \"%pV\" lease allocated to another device",
1225 &env->requested_address);
1227
1228 default:
1230 }
1231}
1232
1233static unlang_action_t CC_HINT(nonnull) mod_release(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
1234{
1237
1239
1241 &env->requested_address, &env->owner, &env->gateway_id, 0);
1242 switch (redis_ippool_release(inst, request, &env->pool_name, &env->requested_address.datum.ip, &env->owner)) {
1244 RDEBUG2("IP address \"%pV\" released", &env->requested_address);
1246
1247 /*
1248 * It's useful to be able to identify the 'not found' case
1249 * as we can relay to a server where the IP address might
1250 * be found. This extremely useful for migrations.
1251 */
1253 REDEBUG("Requested IP address \"%pV\" is not a member of the specified pool",
1254 &env->requested_address);
1256
1258 REDEBUG("Requested IP address' \"%pV\" lease allocated to another device",
1259 &env->requested_address);
1261
1262 default:
1264 }
1265}
1266
1268 request_t *request)
1269{
1270 RDEBUG2("Bulk release not yet implemented");
1272}
1273
1274static int mod_instantiate(module_inst_ctx_t const *mctx)
1275{
1276 static bool done_hash = false;
1277 CONF_SECTION *subcs = cf_section_find(mctx->mi->conf, "redis", NULL);
1278
1279 rlm_redis_ippool_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_ippool_t);
1280
1281 fr_assert(subcs);
1282
1283 inst->cluster = fr_redis_cluster_alloc(inst, subcs, &inst->conf, NULL, NULL, NULL);
1284 if (!inst->cluster) return -1;
1285
1286 if (!fr_redis_cluster_min_version(inst->cluster, "3.0.2")) {
1287 PERROR("Cluster error");
1288 return -1;
1289 }
1290
1291 /*
1292 * Pre-Compute the SHA1 hashes of the Lua scripts
1293 */
1294 if (!done_hash) {
1295 fr_sha1_ctx sha1_ctx;
1297
1298 fr_sha1_init(&sha1_ctx);
1299 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_alloc_cmd, sizeof(lua_alloc_cmd) - 1);
1300 fr_sha1_final(digest, &sha1_ctx);
1301 fr_base16_encode(&FR_SBUFF_OUT(lua_alloc_digest, sizeof(lua_alloc_digest)), &FR_DBUFF_TMP(digest, sizeof(digest)));
1302
1303 fr_sha1_init(&sha1_ctx);
1304 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_update_cmd, sizeof(lua_update_cmd) - 1);
1305 fr_sha1_final(digest, &sha1_ctx);
1307
1308 fr_sha1_init(&sha1_ctx);
1309 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_release_cmd, sizeof(lua_release_cmd) - 1);
1310 fr_sha1_final(digest, &sha1_ctx);
1312 }
1313
1314 return 0;
1315}
1316
1317static int mod_load(void)
1318{
1320
1321 return 0;
1322}
1323
1326 .common = {
1327 .magic = MODULE_MAGIC_INIT,
1328 .name = "redis",
1329 .inst_size = sizeof(rlm_redis_ippool_t),
1331 .onload = mod_load,
1332 .instantiate = mod_instantiate
1333 },
1334 .method_group = {
1335 .bindings = (module_method_binding_t[]){
1336 { .section = SECTION_NAME("recv", "Access-Request"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* radius */
1337 { .section = SECTION_NAME("accounting", "Start"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* radius */
1338 { .section = SECTION_NAME("accounting", "Interim-Update"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* radius */
1339 { .section = SECTION_NAME("accounting", "Stop"), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* radius */
1340 { .section = SECTION_NAME("accounting", "Accounting-On"), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* radius */
1341 { .section = SECTION_NAME("accounting", "Accounting-Off"), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* radius */
1342
1343 { .section = SECTION_NAME("recv", "Discover"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* dhcpv4 */
1344 { .section = SECTION_NAME("recv", "Release"), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* dhcpv4 */
1345 { .section = SECTION_NAME("send", "Ack"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* dhcpv4 */
1346
1347 { .section = SECTION_NAME("recv", "Solicit"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* dhcpv6 */
1348
1349 { .section = SECTION_NAME("recv", CF_IDENT_ANY), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* generic */
1350 { .section = SECTION_NAME("send", CF_IDENT_ANY), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* generic */
1351
1352 { .section = SECTION_NAME("allocate", NULL), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* verb */
1353 { .section = SECTION_NAME("update", NULL), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* verb */
1354 { .section = SECTION_NAME("renew", NULL), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* verb */
1355 { .section = SECTION_NAME("release", NULL), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* verb */
1356 { .section = SECTION_NAME("bulk-release", NULL), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* verb */
1358 }
1359 }
1360};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
va_end(args)
va_start(args, fmt)
#define fr_base16_encode(_out, _in)
Definition base16.h:54
#define RCSID(id)
Definition build.h:506
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:343
#define STRINGIFY(x)
Definition build.h:216
#define unlikely(_x)
Definition build.h:402
#define UNUSED
Definition build.h:336
#define NUM_ELEMENTS(_t)
Definition build.h:358
#define CALL_ENV_TERMINATOR
Definition call_env.h:236
#define FR_CALL_ENV_METHOD_OUT(_inst)
Helper macro for populating the size/type fields of a call_env_method_t from the output structure typ...
Definition call_env.h:240
call_env_parser_t const * env
Parsing rules for call method env.
Definition call_env.h:247
@ CALL_ENV_FLAG_CONCAT
If the tmpl produced multiple boxes they should be concatenated.
Definition call_env.h:76
@ CALL_ENV_FLAG_ATTRIBUTE
Tmpl MUST contain an attribute reference.
Definition call_env.h:86
@ CALL_ENV_FLAG_NONE
Definition call_env.h:74
@ CALL_ENV_FLAG_REQUIRED
Associated conf pair or section is required.
Definition call_env.h:75
@ CALL_ENV_FLAG_BARE_WORD_ATTRIBUTE
bare words are treated as an attribute, but strings may be xlats.
Definition call_env.h:92
@ CALL_ENV_FLAG_NULLABLE
Tmpl expansions are allowed to produce no output.
Definition call_env.h:80
#define FR_CALL_ENV_OFFSET(_name, _cast_type, _flags, _struct, _field)
Specify a call_env_parser_t which writes out runtime results to the specified field.
Definition call_env.h:340
#define FR_CALL_ENV_PARSE_ONLY_OFFSET(_name, _cast_type, _flags, _struct, _parse_field)
Specify a call_env_parser_t which writes out the result of the parsing phase to the field specified.
Definition call_env.h:389
Per method call config.
Definition call_env.h:180
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:657
#define FR_CONF_DEPRECATED(_name, _struct, _field)
conf_parser_t entry which raises an error if a matching CONF_PAIR is found
Definition cf_parse.h:409
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:280
fr_token_t quote
Quoting around the default value. Only used for templates.
Definition cf_parse.h:649
#define FR_CONF_POINTER(_name, _type, _flags, _res_p)
conf_parser_t which parses a single CONF_PAIR producing a single global result
Definition cf_parse.h:334
@ CONF_FLAG_SUBSECTION
Instead of putting the information into a configuration structure, the configuration file routines MA...
Definition cf_parse.h:423
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:594
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:101
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:1027
#define CF_IDENT_ANY
Definition cf_util.h:75
fr_redis_rcode_t fr_redis_cluster_state_next(fr_redis_cluster_state_t *state, fr_redis_conn_t **conn, fr_redis_cluster_t *cluster, request_t *request, fr_redis_rcode_t status, redisReply **reply)
Get the next connection to attempt a command against.
Definition cluster.c:1862
fr_redis_cluster_t * fr_redis_cluster_alloc(TALLOC_CTX *ctx, CONF_SECTION *module, fr_redis_conf_t *conf, char const *log_prefix, char const *trigger_prefix, fr_pair_list_t *trigger_args)
Allocate and initialise a new cluster structure.
Definition cluster.c:2259
fr_redis_rcode_t fr_redis_cluster_state_init(fr_redis_cluster_state_t *state, fr_redis_conn_t **conn, fr_redis_cluster_t *cluster, request_t *request, uint8_t const *key, size_t key_len, bool read_only)
Resolve a key to a pool, and reserve a connection in that pool.
Definition cluster.c:1740
bool fr_redis_cluster_min_version(fr_redis_cluster_t *cluster, char const *min_version)
Check if members of the cluster are above a certain version.
Definition cluster.c:2201
A redis cluster.
Definition cluster.c:250
Redis connection sequence state.
Definition cluster.h:49
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:522
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
talloc_free(hp)
uint8_t prefix
Prefix length - Between 0-32 for IPv4 and 0-128 for IPv6.
Definition inet.h:68
int af
Address family.
Definition inet.h:63
union fr_ipaddr_t::@137 addr
#define FR_IPADDR_PREFIX_STRLEN
Like FR_IPADDR_STRLEN but with space for a prefix.
Definition inet.h:92
IPv4/6 prefix.
#define PERROR(_fmt,...)
Definition log.h:228
#define RWDEBUG(fmt,...)
Definition log.h:373
#define RDEBUG_ENABLED3
True if request debug level 1-3 messages are enabled.
Definition log.h:347
#define RDEBUG3(fmt,...)
Definition log.h:355
#define RDEBUGX(_l, fmt,...)
Definition log.h:352
#define RERROR(fmt,...)
Definition log.h:310
#define RPEDEBUG(fmt,...)
Definition log.h:388
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:1604
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:1884
#define fr_time()
Definition event.c:60
fr_log_lvl_t
Definition log.h:64
@ L_DBG_LVL_3
3rd highest priority debug messages (-xxx | -Xx).
Definition log.h:69
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition log.h:68
@ FR_TYPE_IPV4_ADDR
32 Bit IPv4 Address.
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_VOID
User data.
@ FR_TYPE_COMBO_IP_ADDR
IPv4 or IPv6 address depending on length.
unsigned int uint32_t
unsigned char uint8_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
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 instantiation calls.
Definition module_ctx.h:50
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
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:163
#define fr_assert(_expr)
Definition rad_assert.h:37
#define REDEBUG(fmt,...)
#define RDEBUG2(fmt,...)
#define RETURN_UNLANG_UPDATED
Definition rcode.h:70
#define RETURN_UNLANG_INVALID
Definition rcode.h:66
#define RETURN_UNLANG_NOTFOUND
Definition rcode.h:68
#define RETURN_UNLANG_FAIL
Definition rcode.h:63
#define RETURN_UNLANG_NOOP
Definition rcode.h:69
void fr_redis_reply_print(fr_log_lvl_t lvl, redisReply *reply, request_t *request, int idx, fr_redis_rcode_t status)
Print the response data in a useful treelike form.
Definition redis.c:142
static void fr_redis_pipeline_free(redisReply *reply[], size_t num)
Definition base.h:70
redisContext * handle
Hiredis context used when issuing commands.
Definition base.h:101
#define REDIS_COMMON_CONFIG
Definition base.h:135
static void fr_redis_reply_free(redisReply **reply)
Wrap freeReplyObject so we consistently check for NULL pointers.
Definition base.h:64
void fr_redis_version_print(void)
Print the version of libhiredis the server was built against.
Definition redis.c:53
fr_table_num_sorted_t const redis_reply_types[]
Definition redis.c:30
fr_redis_rcode_t fr_redis_pipeline_result(unsigned int *pipelined, fr_redis_rcode_t *rcode, redisReply *out[], size_t out_len, fr_redis_conn_t *conn)
Simplifies handling of pipelined commands with Redis cluster.
Definition redis.c:539
fr_redis_rcode_t
Codes are ordered inversely by priority.
Definition base.h:87
@ REDIS_RCODE_SUCCESS
Operation was successful.
Definition base.h:88
@ REDIS_RCODE_TRY_AGAIN
Try the operation again.
Definition base.h:90
@ REDIS_RCODE_NO_SCRIPT
Script doesn't exist.
Definition base.h:95
@ REDIS_RCODE_ERROR
Unrecoverable library/server error.
Definition base.h:89
Configuration parameters for a redis connection.
Definition base.h:109
Connection handle, holding a redis context.
Definition base.h:100
#define IPPOOL_STATIC_BIT
#define _IPPOOL_RCODE_NOT_FOUND
ippool_action_t
@ POOL_ACTION_RELEASE
@ POOL_ACTION_ALLOCATE
@ POOL_ACTION_UPDATE
#define IPPOOL_OWNER_KEY
#define _IPPOOL_RCODE_POOL_EMPTY
ippool_rcode_t
@ IPPOOL_RCODE_EXPIRED
@ IPPOOL_RCODE_DEVICE_MISMATCH
@ IPPOOL_RCODE_NOT_FOUND
@ IPPOOL_RCODE_POOL_EMPTY
@ IPPOOL_RCODE_SUCCESS
@ IPPOOL_RCODE_FAIL
#define IPPOOL_POOL_KEY
#define _IPPOOL_RCODE_DEVICE_MISMATCH
#define _IPPOOL_RCODE_SUCCESS
#define IPPOOL_SPRINT_IP(_buff, _ip, _prefix)
If the prefix is as wide as the AF data size then print it without CIDR notation.
#define IPPOOL_ADDRESS_KEY
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)
tmpl_t * expiry_attr
Time at which the lease will expire.
fr_redis_cluster_t * cluster
Redis cluster.
static int mod_load(void)
static char lua_update_cmd[]
Lua script for updating leases.
fr_value_box_t pool_name
Name of the pool we're allocating IP addresses from.
static char lua_release_digest[(SHA1_DIGEST_LENGTH *2)+1]
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_value_box_t offer_time
How long we should reserve a lease for during the pre-allocation stage (typically responding to DHCP ...
static ippool_rcode_t redis_ippool_release(rlm_redis_ippool_t const *inst, request_t *request, fr_value_box_t const *key_prefix, fr_ipaddr_t *ip, fr_value_box_t const *owner)
Release an existing IP address in a pool.
fr_value_box_t gateway_id
Gateway identifier, usually NAS-Identifier or Option 82 gateway.
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)
#define EOL
static const call_env_method_t redis_ippool_update_method_env
fr_value_box_t owner
Unique lease owner identifier.
static ippool_rcode_t redis_ippool_update(rlm_redis_ippool_t const *inst, request_t *request, redis_ippool_update_call_env_t *env, fr_ipaddr_t *ip, fr_value_box_t const *owner, fr_value_box_t const *gateway_id, uint32_t expires)
Update an existing IP address in a pool.
fr_value_box_t pool_name
Name of the pool we're allocating IP addresses from.
static unlang_action_t mod_release(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
fr_value_box_t pool_name
Name of the pool we're allocating IP addresses from.
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
Attribute to read the IP for renewal from.
fr_value_box_t requested_address
Attribute to read the IP for renewal from.
static char lua_release_cmd[]
Lua script for releasing leases.
fr_redis_conf_t conf
Connection parameters for the Redis server.
static fr_redis_rcode_t ippool_script(redisReply **out, request_t *request, fr_redis_cluster_t *cluster, uint8_t const *key, size_t key_len, uint32_t wait_num, fr_time_delta_t wait_timeout, char const digest[], char const *script, char const *cmd,...)
Execute a script against Redis cluster.
uint32_t wait_num
How many slaves we want to acknowledge allocations or updates.
static const call_env_method_t redis_ippool_release_method_env
fr_time_delta_t wait_timeout
How long we wait for slaves to acknowledge writing.
bool copy_on_update
Copy the address provided by ip_address to the allocated_address_attr if updates are successful.
tmpl_t * expiry_attr
Time at which the lease will expire.
static char lua_alloc_cmd[]
Lua script for allocating new leases.
tmpl_t * range_attr
Attribute to write the range ID to.
tmpl_t * allocated_address_attr
Attribute to populate with allocated IP.
static ippool_rcode_t redis_ippool_allocate(rlm_redis_ippool_t const *inst, request_t *request, redis_ippool_alloc_call_env_t *env, uint32_t lease_time)
Allocate a new IP address from a pool.
bool ipv4_integer
Whether IPv4 addresses should be cast to integers, for renew operations.
fr_value_box_t owner
Unique lease owner identifier.
tmpl_t * allocated_address_attr
Attribute to populate with allocated IP.
fr_value_box_t gateway_id
Gateway identifier, usually NAS-Identifier or Option 82 gateway.
#define CHECK_POOL_NAME
fr_value_box_t lease_time
How long an IP address should be allocated for.
static int mod_instantiate(module_inst_ctx_t const *mctx)
fr_value_box_t pool_name
Name of the pool we're allocating IP addresses from.
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 int ippool_wait_check(request_t *request, uint32_t wait_num, redisReply *reply)
Check the requisite number of slaves replicated the lease info.
static conf_parser_t module_config[]
fr_value_box_t requested_address
Attribute to read the IP for renewal from.
static const call_env_method_t redis_ippool_alloc_method_env
Call environment used when calling redis_ippool allocate method.
Call environment used when calling redis_ippool bulk release method.
Call environment used when calling redis_ippool release method.
Call environment used when calling redis_ippool update method.
rlm_redis module instance
#define FR_SBUFF_OUT(_start, _len_or_end)
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition section.h:39
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:351
size_t inst_size
Size of the module's instance data.
Definition module.h:212
void * data
Module's instance data.
Definition module.h:293
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:152
Named methods exported by a module.
Definition module.h:174
#define tmpl_value(_tmpl)
Definition tmpl.h:937
@ TMPL_TYPE_DATA
Value in native boxed format.
Definition tmpl.h:138
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition tmpl.h:801
tmpl_t * tmpl_init_shallow(tmpl_t *vpt, tmpl_type_t type, fr_token_t quote, char const *name, ssize_t len, tmpl_rules_t const *t_rules))
Initialise a tmpl without copying the input name string.
void fr_sha1_init(fr_sha1_ctx *context)
Definition sha1.c:93
void fr_sha1_final(uint8_t digest[static SHA1_DIGEST_LENGTH], fr_sha1_ctx *context)
Definition sha1.c:141
void fr_sha1_update(fr_sha1_ctx *context, uint8_t const *in, size_t len)
Definition sha1.c:105
#define SHA1_DIGEST_LENGTH
Definition sha1.h:29
eap_aka_sim_process_conf_t * inst
fr_aka_sim_id_type_t type
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
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition table.h:772
#define talloc_get_type_abort_const
Definition talloc.h:110
#define fr_time_to_timeval(_when)
Convert server epoch time to unix epoch time.
Definition time.h:742
static int64_t fr_time_delta_to_msec(fr_time_delta_t delta)
Definition time.h:637
A time delta, a difference in time measured in nanoseconds.
Definition time.h:80
@ 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
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:3931
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:4379
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:4910
#define fr_value_box(_box, _var, _tainted)
Automagically fill in a box, determining the value type from the type of the C variable.
Definition value.h:904
int nonnull(2, 5))
static size_t char ** out
Definition value.h:1030