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 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: 28dbbe241c104e650ab2b8b19be42a6522cfa437 $
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: 28dbbe241c104e650ab2b8b19be42a6522cfa437 $")
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 association_time; //!< How log should a device be associated with an IP address.
117 ///< This allows for "sticky" addressing, where the device -> IP
118 ///< association lasts longer than the lease time.
119
120 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
121 ///< or a combination of User-Name and something
122 ///< unique to the device.
123
124 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
125 ///< Option 82 gateway. Used for bulk lease cleanups.
126
127 fr_value_box_t requested_address; //!< Attribute to read the IP for renewal from.
128
129 tmpl_t *allocated_address_attr; //!< Attribute to populate with allocated IP.
130
131 tmpl_t *range_attr; //!< Attribute to write the range ID to.
132
133 tmpl_t *expiry_attr; //!< Time at which the lease will expire.
135
136/** Call environment used when calling redis_ippool update method.
137 *
138 */
139typedef struct {
140 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
141
142 fr_value_box_t lease_time; //!< How long an IP address should be allocated for.
143
144 fr_value_box_t association_time; //!< How long should a device be associated with an IP address.
145
146 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
147 ///< or a combination of User-Name and something
148 ///< unique to the device.
149
150 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
151 ///< Option 82 gateway. Used for bulk lease cleanups.
152
153 fr_value_box_t requested_address; //!< Attribute to read the IP for renewal from.
154
155 tmpl_t *allocated_address_attr; //!< Attribute to populate with allocated IP.
156
157 tmpl_t *range_attr; //!< Attribute to write the range ID to.
158
159 tmpl_t *expiry_attr; //!< Time at which the lease will expire.
161
162/** Call environment used when calling redis_ippool release method.
163 *
164 */
165typedef struct {
166 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
167
168 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
169 ///< or a combination of User-Name and something
170 ///< unique to the device.
171
172 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
173 ///< Option 82 gateway. Used for bulk lease cleanups.
174
175 fr_value_box_t requested_address; //!< Attribute to read the IP for renewal from.
176
177 fr_value_box_t association_time; //!< How long should a device be associated with an IP address.
178
180
181/** Call environment used when calling redis_ippool bulk release method.
182 *
183 */
184typedef struct {
185 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
186
187 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
188 ///< Option 82 gateway. Used for bulk lease cleanups.
190
193 .env = (call_env_parser_t[]){
195 redis_ippool_alloc_call_env_t, pool_name) },
199 redis_ippool_alloc_call_env_t, gateway_id ), .pair.dflt = "", .pair.dflt_quote = T_SINGLE_QUOTED_STRING },
204 .pair.dflt = "%{%{Requested-IP-Address} || %{Net.Src.IP}}", .pair.dflt_quote = T_DOUBLE_QUOTED_STRING },
207 .pair.dflt = "reply.IP-Pool.Range", .pair.dflt_quote = T_BARE_WORD },
210 }
211};
212
215 .env = (call_env_parser_t[]) {
219 .pair.dflt = "", .pair.dflt_quote = T_SINGLE_QUOTED_STRING },
223 .pair.dflt = "%{Requested-IP-Address || Net.Src.IP}", .pair.dflt_quote = T_DOUBLE_QUOTED_STRING },
226 .pair.dflt = "reply.IP-Pool.Range", .pair.dflt_quote = T_BARE_WORD },
229 }
230};
231
245
255
256#define EOL "\n"
257
258/** Lua script for allocating new leases
259 *
260 * - KEYS[1] The pool name.
261 * - ARGV[1] Wall time (seconds since epoch).
262 * - ARGV[2] Expires in (seconds).
263 * - ARGV[3] Lease owner identifier (administratively configured).
264 * - ARGV[4] Device -> IP association time (seconds).
265 * - ARGV[5] (optional) Gateway identifier.
266 * - ARGV[6] (optional) Requested address.
267 *
268 * Returns @verbatim { <rcode>[, <ip>][, <range>][, <lease time>][, <counter>] } @endverbatim
269 * - IPPOOL_RCODE_SUCCESS lease updated..
270 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
271 */
272static char lua_alloc_cmd[] =
273 "local ip" EOL /* 1 */
274 "local exists" EOL /* 2 */
275
276 "local pool_key" EOL /* 3 */
277 "local address_key" EOL /* 4 */
278 "local owner_key" EOL /* 5 */
279
280 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 6 */
281 "owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[3]" EOL /* 7 */
282
283 "local wall_time = tonumber(ARGV[1])" EOL /* 8* */
284
285 /*
286 * Check to see if the client already has a lease,
287 * and if it does return that.
288 *
289 * The additional sanity checks are to allow for the record
290 * of device/ip binding to persist for longer than the lease.
291 */
292 "exists = redis.call('GET', owner_key);" EOL /* 9 */
293 "if exists then" EOL /* 10 */
294 " local expires = tonumber(redis.call('ZSCORE', pool_key, exists))" EOL /* 11 */
295 " local static = expires >= " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 12 */
296 " local expires_in = expires - (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0) - ARGV[1]" EOL /* 13 */
297 " ip = redis.call('HMGET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. exists, 'device', 'range', 'counter', 'gateway')" EOL /* 14 */
298 " if ip and (ip[1] == ARGV[3]) then" EOL /* 15 */
299 " if expires_in < tonumber(ARGV[2]) then" EOL /* 16 */
300 " redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2] + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), exists)" EOL /* 17 */
301 " expires_in = tonumber(ARGV[2])" EOL /* 18 */
302 " if not static then" EOL /* 19 */
303 " redis.call('EXPIRE', owner_key, ARGV[4])" EOL /* 20 */
304 " end" EOL /* 21 */
305 " end" EOL /* 22 */
306
307 /*
308 * Ensure gateway is set correctly
309 */
310 " if ARGV[5] ~= ip[5] then" EOL /* 23 */
311 " redis.call('HSET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":', 'gateway', ARGV[5])" EOL /* 24 */
312 " end" EOL /* 25 */
313 " return {" STRINGIFY(_IPPOOL_RCODE_SUCCESS) ", exists, ip[2], expires_in, ip[3] }" EOL /* 26 */
314 " end" EOL /* 27 */
315 "end" EOL /* 28 */
316
317 /*
318 * If there's a requested address, check if that is available i.e. not statically
319 * assigned, nor already allocated.
320 */
321 "if ARGV[6] and ARGV[6] ~= '' then" EOL /* 29 */
322 " local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[6]))" EOL /* 30 */
323 " if expires and tonumber(expires) < wall_time then" EOL /* 31 */
324 " ip = { ARGV[6] }" EOL /* 32 */
325 " end" EOL /* 33 */
326 "end" EOL /* 34 */
327
328 /*
329 * Else, get the IP address which expired the longest time ago.
330 */
331 "if not ip then" EOL /* 35 */
332 " ip = redis.call('ZREVRANGE', pool_key, -1, -1, 'WITHSCORES')" EOL /* 36 */
333 " if not ip or not ip[1] then" EOL /* 37 */
334 " return {" STRINGIFY(_IPPOOL_RCODE_POOL_EMPTY) "}" EOL /* 38 */
335 " end" EOL /* 39 */
336 " if tonumber(ip[2]) >= wall_time then" EOL /* 40 */
337 " return {" STRINGIFY(_IPPOOL_RCODE_POOL_EMPTY) "}" EOL /* 41 */
338 " end" EOL /* 42 */
339 "end" EOL /* 43 */
340 "redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2], ip[1])" EOL /* 44 */
341
342 /*
343 * Set the device/gateway keys
344 */
345 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ip[1]" EOL /* 45 */
346 "redis.call('HMSET', address_key, 'device', ARGV[3], 'gateway', ARGV[5])" EOL /* 46 */
347 "redis.call('SET', owner_key, ip[1])" EOL /* 47 */
348 "redis.call('EXPIRE', owner_key, ARGV[4])" EOL /* 48 */
349 "return { " EOL /* 49 */
350 " " STRINGIFY(_IPPOOL_RCODE_SUCCESS) "," EOL /* 50 */
351 " ip[1], " EOL /* 51 */
352 " redis.call('HGET', address_key, 'range'), " EOL /* 52 */
353 " tonumber(ARGV[2]), " EOL /* 53 */
354 " redis.call('HINCRBY', address_key, 'counter', 1)" EOL /* 54 */
355 "}" EOL; /* 55 */
357
358/** Lua script for updating leases
359 *
360 * - KEYS[1] The pool name.
361 * - ARGV[1] Wall time (seconds since epoch).
362 * - ARGV[2] Expires in (seconds).
363 * - ARGV[3] IP address to update.
364 * - ARGV[4] Lease owner identifier.
365 * - ARGV[5] Device -> IP association time (seconds).
366 * - ARGV[6] (optional) Gateway identifier.
367 *
368 * Returns @verbatim array { <rcode>[, <range>] } @endverbatim
369 * - IPPOOL_RCODE_SUCCESS lease updated..
370 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
371 * - IPPOOL_RCODE_EXPIRED lease has already expired.
372 * - IPPOOL_RCODE_DEVICE_MISMATCH lease was allocated to a different client.
373 */
374static char lua_update_cmd[] =
375 "local ret" EOL /* 1 */
376 "local found" EOL /* 2 */
377
378 "local pool_key" EOL /* 3 */
379 "local address_key" EOL /* 4 */
380 "local owner_key" EOL /* 5 */
381
382 /*
383 * We either need to know that the IP was last allocated to the
384 * same device, or that the lease on the IP has NOT expired.
385 */
386 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[3]" EOL /* 6 */
387 "found = redis.call('HMGET', address_key, 'range', 'device', 'gateway', 'counter' )" EOL /* 7 */
388 /*
389 * Range may be nil (if not used), so we use the device key
390 */
391 "if not found[2] then" EOL /* 8 */
392 " return {" STRINGIFY(_IPPOOL_RCODE_NOT_FOUND) "}" EOL /* 9 */
393 "end" EOL /* 10 */
394 "if found[2] ~= ARGV[4] then" EOL /* 11 */
395 " return {" STRINGIFY(_IPPOOL_RCODE_DEVICE_MISMATCH) ", found[2]}" EOL /* 12 */
396 "end" EOL /* 13 */
397
398 /*
399 * Update the expiry time
400 */
401 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 14 */
402 "local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[3]))" EOL /* 15 */
403 "local static = expires > " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 16 */
404 "redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2] + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), ARGV[3])" EOL /* 17 */
405
406 /*
407 * The device key should usually exist, but
408 * theoretically, if we were right on the cusp
409 * of a lease being expired, it may have been
410 * removed.
411 */
412 "owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[4]" EOL /* 18 */
413 "if not static and (redis.call('EXPIRE', owner_key, ARGV[2]) == 0) then" EOL /* 19 */
414 " redis.call('SET', owner_key, ARGV[3])" EOL /* 20 */
415 " redis.call('EXPIRE', owner_key, ARGV[5])" EOL /* 21 */
416 "end" EOL /* 22 */
417
418 /*
419 * Update the gateway address
420 */
421 "if ARGV[6] ~= found[3] then" EOL /* 23 */
422 " redis.call('HSET', address_key, 'gateway', ARGV[6])" EOL /* 24 */
423 "end" EOL /* 25 */
424 "return { " STRINGIFY(_IPPOOL_RCODE_SUCCESS) ", found[1], found[4] }"EOL; /* 26 */
426
427/** Lua script for releasing leases
428 *
429 * - KEYS[1] The pool name.
430 * - ARGV[1] Wall time (seconds since epoch).
431 * - ARGV[2] IP address to release.
432 * - ARGV[3] Client identifier.
433 * - ARGV[4] Device -> IP association time (seconds).
434 *
435 * Sets the expiry time to be NOW() - 1 to maximise time between
436 * IP address allocations.
437 *
438 * Returns @verbatim array { <rcode>[, <counter>] } @endverbatim
439 * - IPPOOL_RCODE_SUCCESS lease updated..
440 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
441 * - IPPOOL_RCODE_DEVICE_MISMATCH lease was allocated to a different client..
442 */
443static char lua_release_cmd[] =
444 "local ret" EOL /* 1 */
445 "local found" EOL /* 2 */
446
447 "local pool_key" EOL /* 3 */
448 "local address_key" EOL /* 4 */
449 "local owner_key" EOL /* 5 */
450
451 /*
452 * Check that the device releasing was the one
453 * the IP address is allocated to.
454 */
455 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[2]" EOL /* 6 */
456 "found = redis.call('HGET', address_key, 'device')" EOL /* 7 */
457 "if not found then" EOL /* 8 */
458 " return { " STRINGIFY(_IPPOOL_RCODE_NOT_FOUND) "}" EOL /* 9 */
459 "end" EOL /* 10 */
460 "if found and found ~= ARGV[3] then" EOL /* 11 */
461 " return { " STRINGIFY(_IPPOOL_RCODE_DEVICE_MISMATCH) ", found }" EOL /* 12 */
462 "end" EOL /* 13 */
463
464 /*
465 * Set expiry time to now() - 1
466 */
467 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 14 */
468 "found = tonumber(redis.call('ZSCORE', pool_key, ARGV[2]))" EOL /* 15 */
469 "local static = found > " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 16 */
470 "redis.call('ZADD', pool_key, 'XX', ARGV[1] - 1 + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), ARGV[2])" EOL /* 17 */
471
472 /*
473 * Remove the association between the device and a lease
474 * unless "sticky" addressing is in place where association_time
475 * is set, in which case use that to set the expiry of the association.
476 */
477 "if not static then" EOL /* 18 */
478 " owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[3]" EOL /* 19 */
479 " if tonumber(ARGV[4]) > 0 then" EOL /* 20 */
480 " redis.call('EXPIRE', owner_key, ARGV[4])" EOL /* 21 */
481 " else" EOL /* 22 */
482 " redis.call('DEL', owner_key)" EOL /* 24 */
483 " end" EOL /* 25 */
484 "end" EOL /* 26 */
485 "return { " EOL /* 27 */
486 " " STRINGIFY(_IPPOOL_RCODE_SUCCESS) "," EOL /* 28 */
487 " redis.call('HINCRBY', address_key, 'counter', 1) - 1" EOL /* 29 */
488 "}"; /* 30 */
490
491/** Check the requisite number of slaves replicated the lease info
492 *
493 * @param request The current request.
494 * @param wait_num Number of slaves required.
495 * @param reply we got from the server.
496 * @return
497 * - 0 if enough slaves replicated the data.
498 * - -1 if too few slaves replicated the data, or another error.
499 */
500static inline int ippool_wait_check(request_t *request, uint32_t wait_num, redisReply *reply)
501{
502 if (!wait_num) return 0;
503
504 if (reply->type != REDIS_REPLY_INTEGER) {
505 REDEBUG("WAIT result is wrong type, expected integer got %s",
506 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
507 return -1;
508 }
509 if (reply->integer < wait_num) {
510 REDEBUG("Too few slaves acknowledged allocation, needed %i, got %lli",
511 wait_num, reply->integer);
512 return -1;
513 }
514 return 0;
515}
516
517static void ippool_action_print(request_t *request, ippool_action_t action,
518 fr_log_lvl_t lvl,
519 fr_value_box_t const *key_prefix,
520 fr_value_box_t const *ip,
521 fr_value_box_t const *owner,
522 fr_value_box_t const *gateway_id,
523 uint32_t expires)
524{
525 char *device_str = NULL, *gateway_str = NULL;
526
527 if (gateway_id && gateway_id->vb_length > 0) gateway_str = fr_asprint(request, gateway_id->vb_strvalue,
528 gateway_id->vb_length, '"');
529 if (owner && owner->vb_length > 0) device_str = fr_asprint(request, owner->vb_strvalue, owner->vb_length, '"');
530
531 switch (action) {
533 RDEBUGX(lvl, "Allocating lease from pool \"%pV\"%s%s%s%s%s%s, expires in %us",
534 key_prefix,
535 device_str ? ", to \"" : "", device_str ? device_str : "",
536 device_str ? "\"" : "",
537 gateway_str ? ", on \"" : "", gateway_str ? gateway_str : "",
538 gateway_str ? "\"" : "",
539 expires);
540 break;
541
543 RDEBUGX(lvl, "Updating %pV in pool \"%pV\"%s%s%s%s%s%s, expires in %us",
544 ip, key_prefix,
545 device_str ? ", device \"" : "", device_str ? device_str : "",
546 device_str ? "\"" : "",
547 gateway_str ? ", gateway \"" : "", gateway_str ? gateway_str : "",
548 gateway_str ? "\"" : "",
549 expires);
550 break;
551
553 RDEBUGX(lvl, "Releasing %pV%s%s%s to pool \"%pV\"",
554 ip,
555 device_str ? " leased by \"" : "", device_str ? device_str : "",
556 device_str ? "\"" : "",
557 key_prefix);
558 break;
559
560 default:
561 break;
562 }
563
564 /*
565 * Ordering is important, needs to be LIFO
566 * for proper talloc pool reuse.
567 */
568 talloc_free(device_str);
569 talloc_free(gateway_str);
570}
571
572/** Execute a script against Redis cluster
573 *
574 * Handles uploading the script to the server if required.
575 *
576 * @note All replies will be freed on error.
577 *
578 * @param[out] out Where to write Redis reply object resulting from the command.
579 * @param[in] request The current request.
580 * @param[in] cluster configuration.
581 * @param[in] key to use to determine the cluster node.
582 * @param[in] key_len length of the key.
583 * @param[in] wait_num If > 0 wait until this many slaves have replicated the data
584 * from the last command.
585 * @param[in] wait_timeout How long to wait for slaves to replicate the data.
586 * @param[in] digest of script.
587 * @param[in] script to upload.
588 * @param[in] cmd EVALSHA command to execute.
589 * @param[in] ... Arguments for the eval command.
590 * @return status of the command.
591 */
592static fr_redis_rcode_t ippool_script(redisReply **out, request_t *request, fr_redis_cluster_t *cluster,
593 uint8_t const *key, size_t key_len,
594 uint32_t wait_num, fr_time_delta_t wait_timeout,
595 char const digest[], char const *script,
596 char const *cmd, ...)
597{
598 fr_redis_conn_t *conn;
599 redisReply *replies[5]; /* Must be equal to the maximum number of pipelined commands */
600 size_t reply_cnt = 0, i;
601
603 fr_redis_rcode_t s_ret, status;
604 unsigned int pipelined = 0;
605
606 va_list ap;
607
608 *out = NULL;
609
610#ifndef NDEBUG
611 memset(replies, 0, sizeof(replies));
612#endif
613
614 va_start(ap, cmd);
615
616 for (s_ret = fr_redis_cluster_state_init(&state, &conn, cluster, request, key, key_len, false);
617 s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */
618 s_ret = fr_redis_cluster_state_next(&state, &conn, cluster, request, status, &replies[0])) {
619 va_list copy;
620
621 RDEBUG3("Calling script 0x%s", digest);
622 va_copy(copy, ap); /* copy or segv */
623 redisvAppendCommand(conn->handle, cmd, copy);
624 va_end(copy);
625 pipelined = 1;
626 if (wait_num) {
627 redisAppendCommand(conn->handle, "WAIT %i %i", wait_num, fr_time_delta_to_msec(wait_timeout));
628 pipelined++;
629 }
630 reply_cnt = fr_redis_pipeline_result(&pipelined, &status,
631 replies, NUM_ELEMENTS(replies),
632 conn);
633 if (status != REDIS_RCODE_NO_SCRIPT) continue;
634
635 /*
636 * Clear out the existing reply
637 */
638 fr_redis_pipeline_free(replies, reply_cnt);
639
640 /*
641 * Last command failed with NOSCRIPT, this means
642 * we have to send the Lua script up to the node
643 * so it can be cached.
644 */
645 RDEBUG3("Loading script 0x%s", digest);
646 redisAppendCommand(conn->handle, "MULTI");
647 redisAppendCommand(conn->handle, "SCRIPT LOAD %s", script);
648 va_copy(copy, ap); /* copy or segv */
649 redisvAppendCommand(conn->handle, cmd, copy);
650 va_end(copy);
651 redisAppendCommand(conn->handle, "EXEC");
652 pipelined = 4;
653 if (wait_num) {
654 redisAppendCommand(conn->handle, "WAIT %i %i", wait_num, fr_time_delta_to_msec(wait_timeout));
655 pipelined++;
656 }
657
658 reply_cnt = fr_redis_pipeline_result(&pipelined, &status,
659 replies, NUM_ELEMENTS(replies),
660 conn);
661 if (status == REDIS_RCODE_SUCCESS) {
662 if (RDEBUG_ENABLED3) for (i = 0; i < reply_cnt; i++) {
663 fr_redis_reply_print(L_DBG_LVL_3, replies[i], request, i, status);
664 }
665
666 if (replies[3]->type != REDIS_REPLY_ARRAY) {
667 RERROR("Bad response to EXEC, expected array got %s",
668 fr_table_str_by_value(redis_reply_types, replies[3]->type, "<UNKNOWN>"));
669 error:
670 fr_redis_pipeline_free(replies, reply_cnt);
671 status = REDIS_RCODE_ERROR;
672 goto finish;
673 }
674 if (replies[3]->elements != 2) {
675 RERROR("Bad response to EXEC, expected 2 result elements, got %zu",
676 replies[3]->elements);
677 goto error;
678 }
679 if (replies[3]->element[0]->type != REDIS_REPLY_STRING) {
680 RERROR("Bad response to SCRIPT LOAD, expected string got %s",
681 fr_table_str_by_value(redis_reply_types, replies[3]->element[0]->type, "<UNKNOWN>"));
682 goto error;
683 }
684 if (strcmp(replies[3]->element[0]->str, digest) != 0) {
685 RWDEBUG("Incorrect SHA1 from SCRIPT LOAD, expected %s, got %s",
686 digest, replies[3]->element[0]->str);
687 goto error;
688 }
689 }
690 }
691 if (s_ret != REDIS_RCODE_SUCCESS) goto error;
692
693 switch (reply_cnt) {
694 case 2: /* EVALSHA with wait */
695 if (ippool_wait_check(request, wait_num, replies[1]) < 0) goto error;
696 fr_redis_reply_free(&replies[1]); /* Free the wait response */
698
699 case 1: /* EVALSHA */
700 *out = replies[0];
701 break;
702
703 case 5: /* LOADSCRIPT + EVALSHA + WAIT */
704 if (ippool_wait_check(request, wait_num, replies[4]) < 0) goto error;
705 fr_redis_reply_free(&replies[4]); /* Free the wait response */
707
708 case 4: /* LOADSCRIPT + EVALSHA */
709 fr_redis_reply_free(&replies[2]); /* Free the queued cmd response*/
710 fr_redis_reply_free(&replies[1]); /* Free the queued script load response */
711 fr_redis_reply_free(&replies[0]); /* Free the queued multi response */
712 *out = replies[3]->element[1];
713 replies[3]->element[1] = NULL; /* Prevent double free */
714 fr_redis_reply_free(&replies[3]); /* This works because hiredis checks for NULL elements */
715 break;
716
717 case 0:
718 break;
719 }
720
721finish:
722 va_end(ap);
723 return s_ret;
724}
725
726/** Allocate a new IP address from a pool
727 *
728 */
731{
732 struct timeval now;
733 redisReply *reply = NULL;
734 uint32_t assoc_time;
735
736 fr_redis_rcode_t status;
738
739 fr_assert(env->pool_name.vb_length > 0);
740 fr_assert(env->owner.vb_length > 0);
741
743
744 assoc_time = (env->association_time.type == FR_TYPE_UINT32) &&
745 (env->association_time.vb_uint32 > lease_time) ? env->association_time.vb_uint32 : lease_time;
746
747 if ((env->requested_address.datum.ip.af == AF_INET) && inst->ipv4_integer) {
748 status = ippool_script(&reply, request, inst->cluster,
749 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
750 inst->wait_num, inst->wait_timeout,
752 "EVALSHA %s 1 %b %u %u %b %u %b %u",
754 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
755 (unsigned int)now.tv_sec, lease_time,
756 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length,
757 assoc_time,
758 (uint8_t const *)env->gateway_id.vb_strvalue, env->gateway_id.vb_length,
759 htonl(env->requested_address.datum.ip.addr.v4.s_addr));
760 } else {
761 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
762 if (env->requested_address.type == FR_TYPE_COMBO_IP_ADDR) {
763 IPPOOL_SPRINT_IP(ip_buff, &env->requested_address.datum.ip, env->requested_address.datum.ip.prefix);
764 } else {
765 ip_buff[0] = '\0';
766 }
767
768 status = ippool_script(&reply, request, inst->cluster,
769 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
770 inst->wait_num, inst->wait_timeout,
772 "EVALSHA %s 1 %b %u %u %b %u %b %s",
774 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
775 (unsigned int)now.tv_sec, lease_time,
776 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length,
777 assoc_time,
778 (uint8_t const *)env->gateway_id.vb_strvalue, env->gateway_id.vb_length,
779 ip_buff);
780 }
781 if (status != REDIS_RCODE_SUCCESS) {
782 ret = IPPOOL_RCODE_FAIL;
783 goto finish;
784 }
785
786 fr_assert(reply);
787 if (reply->type != REDIS_REPLY_ARRAY) {
788 REDEBUG("Expected result to be array got \"%s\"",
789 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
790 ret = IPPOOL_RCODE_FAIL;
791 goto finish;
792 }
793
794 if (reply->elements == 0) {
795 REDEBUG("Got empty result array");
796 ret = IPPOOL_RCODE_FAIL;
797 goto finish;
798 }
799
800 /*
801 * Process return code
802 */
803 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
804 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
805 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
806 ret = IPPOOL_RCODE_FAIL;
807 goto finish;
808 }
809 ret = reply->element[0]->integer;
810 if (ret < 0) goto finish;
811
812 /*
813 * Process IP address
814 */
815 if (reply->elements > 1) {
816 tmpl_t ip_rhs;
817 map_t ip_map = {
819 .op = T_OP_SET,
820 .rhs = &ip_rhs
821 };
822
823 tmpl_init_shallow(&ip_rhs, TMPL_TYPE_DATA, T_BARE_WORD, "", 0, NULL);
824 switch (reply->element[1]->type) {
825 /*
826 * Destination attribute may not be IPv4, in which case
827 * we want to pre-convert the integer value to an IPv4
828 * address before casting it once more to the type of
829 * the destination attribute.
830 */
831 case REDIS_REPLY_INTEGER:
832 {
833 if (tmpl_attr_tail_da(ip_map.lhs)->type != FR_TYPE_IPV4_ADDR) {
834 fr_value_box_t tmp;
835
836 fr_value_box(&tmp, (uint32_t)ntohl((uint32_t)reply->element[1]->integer), true);
838 NULL, &tmp)) {
839 RPEDEBUG("Failed converting integer to IPv4 address");
840 ret = IPPOOL_RCODE_FAIL;
841 goto finish;
842 }
843 } else {
844 fr_value_box(&ip_map.rhs->data.literal,
845 (uint32_t)ntohl((uint32_t)reply->element[1]->integer), true);
846 }
847 }
848 goto do_ip_map;
849
850 case REDIS_REPLY_STRING:
851 fr_value_box_bstrndup_shallow(&ip_map.rhs->data.literal,
852 NULL, reply->element[1]->str, reply->element[1]->len, false);
853 do_ip_map:
854 if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) {
855 ret = IPPOOL_RCODE_FAIL;
856 goto finish;
857 }
858 break;
859
860 default:
861 REDEBUG("Server returned unexpected type \"%s\" for IP element (result[1])",
862 fr_table_str_by_value(redis_reply_types, reply->element[1]->type, "<UNKNOWN>"));
863 ret = IPPOOL_RCODE_FAIL;
864 goto finish;
865 }
866 }
867
868 /*
869 * Process Range identifier
870 */
871 if (reply->elements > 2) {
872 switch (reply->element[2]->type) {
873 /*
874 * Add range ID to request
875 */
876 case REDIS_REPLY_STRING:
877 {
878 tmpl_t range_rhs;
879 map_t range_map = {
880 .lhs = env->range_attr,
881 .op = T_OP_SET,
882 .rhs = &range_rhs
883 };
884
886 fr_value_box_bstrndup_shallow(&range_map.rhs->data.literal,
887 NULL, reply->element[2]->str, reply->element[2]->len, true);
888 if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) {
889 ret = IPPOOL_RCODE_FAIL;
890 goto finish;
891 }
892 }
893 break;
894
895 case REDIS_REPLY_NIL:
896 break;
897
898 default:
899 REDEBUG("Server returned unexpected type \"%s\" for range element (result[2])",
900 fr_table_str_by_value(redis_reply_types, reply->element[2]->type, "<UNKNOWN>"));
901 ret = IPPOOL_RCODE_FAIL;
902 goto finish;
903 }
904 }
905
906 /*
907 * Process Expiry time
908 */
909 if (env->expiry_attr && (reply->elements > 3)) {
910 tmpl_t expiry_rhs;
911 map_t expiry_map = {
912 .lhs = env->expiry_attr,
913 .op = T_OP_SET,
914 .rhs = &expiry_rhs
915 };
916
917 tmpl_init_shallow(&expiry_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
918 if (reply->element[3]->type != REDIS_REPLY_INTEGER) {
919 REDEBUG("Server returned unexpected type \"%s\" for expiry element (result[3])",
920 fr_table_str_by_value(redis_reply_types, reply->element[3]->type, "<UNKNOWN>"));
921 ret = IPPOOL_RCODE_FAIL;
922 goto finish;
923 }
924
925 fr_value_box(&expiry_map.rhs->data.literal, (uint32_t)reply->element[3]->integer, true);
926 if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) {
927 ret = IPPOOL_RCODE_FAIL;
928 goto finish;
929 }
930 }
931finish:
932 fr_redis_reply_free(&reply);
933 return ret;
934}
935
936/** Update an existing IP address in a pool
937 *
938 */
941 fr_ipaddr_t *ip,
942 fr_value_box_t const *owner,
943 fr_value_box_t const *gateway_id,
944 uint32_t expires)
945{
946 struct timeval now;
947 redisReply *reply = NULL;
948
949 fr_redis_rcode_t status;
951 uint32_t assoc_time;
952
954
955 assoc_time = (env->association_time.type == FR_TYPE_UINT32) &&
956 (env->association_time.vb_uint32 > expires) ? env->association_time.vb_uint32 : expires;
957
958 if ((ip->af == AF_INET) && inst->ipv4_integer) {
959 status = ippool_script(&reply, request, inst->cluster,
960 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
961 inst->wait_num, inst->wait_timeout,
963 "EVALSHA %s 1 %b %u %u %u %b %u %b",
965 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
966 (unsigned int)now.tv_sec, expires,
967 htonl(ip->addr.v4.s_addr),
968 (uint8_t const *)owner->vb_strvalue, owner->vb_length,
969 assoc_time,
970 (uint8_t const *)gateway_id->vb_strvalue, gateway_id->vb_length);
971 } else {
972 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
973
974 IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix);
975 status = ippool_script(&reply, request, inst->cluster,
976 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
977 inst->wait_num, inst->wait_timeout,
979 "EVALSHA %s 1 %b %u %u %s %b %u %b",
981 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
982 (unsigned int)now.tv_sec, expires,
983 ip_buff,
984 (uint8_t const *)owner->vb_strvalue, owner->vb_length,
985 assoc_time,
986 (uint8_t const *)gateway_id->vb_strvalue, gateway_id->vb_length);
987 }
988 if (status != REDIS_RCODE_SUCCESS) {
989 ret = IPPOOL_RCODE_FAIL;
990 goto finish;
991 }
992
993 if (reply->type != REDIS_REPLY_ARRAY) {
994 REDEBUG("Expected result to be array got \"%s\"",
995 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
996 ret = IPPOOL_RCODE_FAIL;
997 goto finish;
998 }
999
1000 if (reply->elements == 0) {
1001 REDEBUG("Got empty result array");
1002 ret = IPPOOL_RCODE_FAIL;
1003 goto finish;
1004 }
1005
1006 /*
1007 * Process return code
1008 */
1009 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
1010 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
1011 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1012 ret = IPPOOL_RCODE_FAIL;
1013 goto finish;
1014 }
1015 ret = reply->element[0]->integer;
1016 if (ret < 0) goto finish;
1017
1018 /*
1019 * Process Range identifier
1020 */
1021 if (reply->elements > 1) {
1022 switch (reply->element[1]->type) {
1023 /*
1024 * Add range ID to request
1025 */
1026 case REDIS_REPLY_STRING:
1027 {
1028 tmpl_t range_rhs;
1029 map_t range_map = { .lhs = env->range_attr, .op = T_OP_SET, .rhs = &range_rhs };
1030
1031 tmpl_init_shallow(&range_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
1032 fr_value_box_bstrndup_shallow(&range_map.rhs->data.literal, NULL,
1033 reply->element[1]->str, reply->element[1]->len, true);
1034 if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) {
1035 ret = IPPOOL_RCODE_FAIL;
1036 goto finish;
1037 }
1038 }
1039 break;
1040
1041 case REDIS_REPLY_NIL:
1042 break;
1043
1044 default:
1045 REDEBUG("Server returned unexpected type \"%s\" for range element (result[1])",
1046 fr_table_str_by_value(redis_reply_types, reply->element[0]->type, "<UNKNOWN>"));
1047 ret = IPPOOL_RCODE_FAIL;
1048 goto finish;
1049 }
1050 }
1051
1052 /*
1053 * Copy expiry time to expires attribute (if set)
1054 */
1055 if (env->expiry_attr) {
1056 tmpl_t expiry_rhs;
1057 map_t expiry_map = {
1058 .lhs = env->expiry_attr,
1059 .op = T_OP_SET,
1060 .rhs = &expiry_rhs
1061 };
1062
1063
1064 tmpl_init_shallow(&expiry_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
1065
1066 fr_value_box(&expiry_map.rhs->data.literal, expires, false);
1067 if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) {
1068 ret = IPPOOL_RCODE_FAIL;
1069 goto finish;
1070 }
1071 }
1072
1073finish:
1074 fr_redis_reply_free(&reply);
1075
1076 return ret;
1077}
1078
1079/** Release an existing IP address in a pool
1080 *
1081 */
1083 fr_value_box_t const *key_prefix,
1084 fr_ipaddr_t *ip,
1085 fr_value_box_t const *owner, uint32_t assoc_time)
1086{
1087 struct timeval now;
1088 redisReply *reply = NULL;
1089
1090 fr_redis_rcode_t status;
1092
1093 now = fr_time_to_timeval(fr_time());
1094
1095 if ((ip->af == AF_INET) && inst->ipv4_integer) {
1096 status = ippool_script(&reply, request, inst->cluster,
1097 (uint8_t const *)key_prefix->vb_strvalue, key_prefix->vb_length,
1098 inst->wait_num, inst->wait_timeout,
1100 "EVALSHA %s 1 %b %u %u %b %u",
1102 (uint8_t const *)key_prefix->vb_strvalue, key_prefix->vb_length,
1103 (unsigned int)now.tv_sec,
1104 htonl(ip->addr.v4.s_addr),
1105 (uint8_t const *)owner->vb_strvalue, owner->vb_length, assoc_time);
1106 } else {
1107 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
1108
1109 IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix);
1110 status = ippool_script(&reply, request, inst->cluster,
1111 (uint8_t const *)key_prefix->vb_strvalue, key_prefix->vb_length,
1112 inst->wait_num, inst->wait_timeout,
1114 "EVALSHA %s 1 %b %u %s %b %u",
1116 (uint8_t const *)key_prefix->vb_strvalue, key_prefix->vb_length,
1117 (unsigned int)now.tv_sec,
1118 ip_buff,
1119 (uint8_t const *)owner->vb_strvalue, owner->vb_length, assoc_time);
1120 }
1121 if (status != REDIS_RCODE_SUCCESS) {
1122 ret = IPPOOL_RCODE_FAIL;
1123 goto finish;
1124 }
1125
1126 if (reply->type != REDIS_REPLY_ARRAY) {
1127 REDEBUG("Expected result to be array got \"%s\"",
1128 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1129 ret = IPPOOL_RCODE_FAIL;
1130 goto finish;
1131 }
1132
1133 if (reply->elements == 0) {
1134 REDEBUG("Got empty result array");
1135 ret = IPPOOL_RCODE_FAIL;
1136 goto finish;
1137 }
1138
1139 /*
1140 * Process return code
1141 */
1142 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
1143 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
1144 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1145 ret = IPPOOL_RCODE_FAIL;
1146 goto finish;
1147 }
1148 ret = reply->element[0]->integer;
1149 if (ret < 0) goto finish;
1150
1151finish:
1152 fr_redis_reply_free(&reply);
1153
1154 return ret;
1155}
1156
1157#define CHECK_POOL_NAME \
1158 if (env->pool_name.vb_length > IPPOOL_MAX_KEY_PREFIX_SIZE) { \
1159 REDEBUG("Pool name too long. Expected %u bytes, got %ld bytes", \
1160 IPPOOL_MAX_KEY_PREFIX_SIZE, env->pool_name.vb_length); \
1161 RETURN_UNLANG_FAIL; \
1162 } \
1163 if (env->pool_name.vb_length == 0) { \
1164 RDEBUG2("Empty pool name. Doing nothing"); \
1165 RETURN_UNLANG_NOOP; \
1166 }
1167
1168static unlang_action_t CC_HINT(nonnull) mod_alloc(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
1169{
1171 redis_ippool_alloc_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_alloc_call_env_t);
1172 uint32_t lease_time;
1173
1175
1176 /*
1177 * If offer_time is defined, it will be FR_TYPE_UINT32.
1178 * Fall back to lease_time otherwise.
1179 */
1180 lease_time = (env->offer_time.type == FR_TYPE_UINT32) ?
1181 env->offer_time.vb_uint32 : env->lease_time.vb_uint32;
1183 &env->owner, &env->gateway_id, lease_time);
1184 switch (redis_ippool_allocate(inst, request, env, lease_time)) {
1186 RDEBUG2("IP address lease allocated");
1188
1190 RWDEBUG("Pool contains no free addresses");
1192
1193 default:
1195 }
1196}
1197
1198static unlang_action_t CC_HINT(nonnull) mod_update(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
1199{
1201 redis_ippool_update_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_update_call_env_t);
1202
1204
1206 &env->requested_address, &env->owner, &env->gateway_id, env->lease_time.vb_uint32);
1207 switch (redis_ippool_update(inst, request, env,
1208 &env->requested_address.datum.ip, &env->owner,
1209 &env->gateway_id,
1210 env->lease_time.vb_uint32)) {
1212 RDEBUG2("Requested IP address' \"%pV\" lease updated", &env->requested_address);
1213
1214 /*
1215 * Copy over the input IP address to the reply attribute
1216 */
1217 if (inst->copy_on_update) {
1218 tmpl_t ip_rhs = {
1219 .name = "",
1220 .type = TMPL_TYPE_DATA,
1221 .quote = T_BARE_WORD,
1222 };
1223 map_t ip_map = {
1225 .op = T_OP_SET,
1226 .rhs = &ip_rhs
1227 };
1228
1229 if (unlikely(fr_value_box_copy(NULL, &ip_rhs.data.literal, &env->requested_address) < 0)) {
1230 RPEDEBUG("Failed copying IP address to reply attribute");
1232 }
1233
1234 if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) RETURN_UNLANG_FAIL;
1235 }
1237
1238 /*
1239 * It's useful to be able to identify the 'not found' case
1240 * as we can relay to a server where the IP address might
1241 * be found. This extremely useful for migrations.
1242 */
1244 REDEBUG("Requested IP address \"%pV\" is not a member of the specified pool",
1245 &env->requested_address);
1247
1249 REDEBUG("Requested IP address' \"%pV\" lease already expired at time of renewal",
1250 &env->requested_address);
1252
1254 REDEBUG("Requested IP address' \"%pV\" lease allocated to another device",
1255 &env->requested_address);
1257
1258 default:
1260 }
1261}
1262
1263static unlang_action_t CC_HINT(nonnull) mod_release(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
1264{
1267
1269
1271 &env->requested_address, &env->owner, &env->gateway_id, 0);
1272 switch (redis_ippool_release(inst, request, &env->pool_name, &env->requested_address.datum.ip, &env->owner,
1273 env->association_time.type == FR_TYPE_UINT32 ? env->association_time.vb_uint32 : 0)) {
1275 RDEBUG2("IP address \"%pV\" released", &env->requested_address);
1277
1278 /*
1279 * It's useful to be able to identify the 'not found' case
1280 * as we can relay to a server where the IP address might
1281 * be found. This extremely useful for migrations.
1282 */
1284 REDEBUG("Requested IP address \"%pV\" is not a member of the specified pool",
1285 &env->requested_address);
1287
1289 REDEBUG("Requested IP address' \"%pV\" lease allocated to another device",
1290 &env->requested_address);
1292
1293 default:
1295 }
1296}
1297
1299 request_t *request)
1300{
1301 RDEBUG2("Bulk release not yet implemented");
1303}
1304
1305static int mod_instantiate(module_inst_ctx_t const *mctx)
1306{
1307 static bool done_hash = false;
1308 CONF_SECTION *subcs = cf_section_find(mctx->mi->conf, "redis", NULL);
1309
1310 rlm_redis_ippool_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_ippool_t);
1311
1312 fr_assert(subcs);
1313
1314 inst->cluster = fr_redis_cluster_alloc(inst, subcs, &inst->conf, NULL, NULL, NULL);
1315 if (!inst->cluster) return -1;
1316
1317 if (!fr_redis_cluster_min_version(inst->cluster, "3.0.2")) {
1318 PERROR("Cluster error");
1319 return -1;
1320 }
1321
1322 /*
1323 * Pre-Compute the SHA1 hashes of the Lua scripts
1324 */
1325 if (!done_hash) {
1326 fr_sha1_ctx sha1_ctx;
1328
1329 fr_sha1_init(&sha1_ctx);
1330 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_alloc_cmd, sizeof(lua_alloc_cmd) - 1);
1331 fr_sha1_final(digest, &sha1_ctx);
1332 fr_base16_encode(&FR_SBUFF_OUT(lua_alloc_digest, sizeof(lua_alloc_digest)), &FR_DBUFF_TMP(digest, sizeof(digest)));
1333
1334 fr_sha1_init(&sha1_ctx);
1335 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_update_cmd, sizeof(lua_update_cmd) - 1);
1336 fr_sha1_final(digest, &sha1_ctx);
1338
1339 fr_sha1_init(&sha1_ctx);
1340 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_release_cmd, sizeof(lua_release_cmd) - 1);
1341 fr_sha1_final(digest, &sha1_ctx);
1343 }
1344
1345 return 0;
1346}
1347
1348static int mod_load(void)
1349{
1351
1352 return 0;
1353}
1354
1357 .common = {
1358 .magic = MODULE_MAGIC_INIT,
1359 .name = "redis",
1360 .inst_size = sizeof(rlm_redis_ippool_t),
1362 .onload = mod_load,
1363 .instantiate = mod_instantiate
1364 },
1365 .method_group = {
1366 .bindings = (module_method_binding_t[]){
1367 { .section = SECTION_NAME("recv", "Access-Request"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* radius */
1368 { .section = SECTION_NAME("accounting", "Start"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* radius */
1369 { .section = SECTION_NAME("accounting", "Interim-Update"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* radius */
1370 { .section = SECTION_NAME("accounting", "Stop"), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* radius */
1371 { .section = SECTION_NAME("accounting", "Accounting-On"), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* radius */
1372 { .section = SECTION_NAME("accounting", "Accounting-Off"), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* radius */
1373
1374 { .section = SECTION_NAME("recv", "Discover"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* dhcpv4 */
1375 { .section = SECTION_NAME("recv", "Release"), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* dhcpv4 */
1376 { .section = SECTION_NAME("send", "Ack"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* dhcpv4 */
1377
1378 { .section = SECTION_NAME("recv", "Solicit"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* dhcpv6 */
1379
1380 { .section = SECTION_NAME("recv", CF_IDENT_ANY), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* generic */
1381 { .section = SECTION_NAME("send", CF_IDENT_ANY), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* generic */
1382
1383 { .section = SECTION_NAME("allocate", NULL), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* verb */
1384 { .section = SECTION_NAME("update", NULL), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* verb */
1385 { .section = SECTION_NAME("renew", NULL), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* verb */
1386 { .section = SECTION_NAME("release", NULL), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* verb */
1387 { .section = SECTION_NAME("bulk-release", NULL), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* verb */
1389 }
1390 }
1391};
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 ...
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.
fr_value_box_t association_time
How long should a device be associated with an IP address.
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.
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, uint32_t assoc_time)
Release an existing IP address in a pool.
fr_value_box_t requested_address
Attribute to read the IP for renewal from.
fr_value_box_t association_time
How long should a device be associated with an IP address.
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.
fr_value_box_t association_time
How log should a device be associated with an IP address.
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