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: d164d1f3bf09da743e90979d6d6a7a9c464aeda2 $
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: d164d1f3bf09da743e90979d6d6a7a9c464aeda2 $")
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#include <freeradius-devel/unlang/call_env.h>
55
56#include "redis_ippool.h"
57
58/** rlm_redis module instance
59 *
60 */
61typedef struct {
62 fr_redis_conf_t conf; //!< Connection parameters for the Redis server.
63 //!< Must be first field in this struct.
64
65 char const *name; //!< Instance name.
66
67 uint32_t wait_num; //!< How many slaves we want to acknowledge allocations
68 //!< or updates.
69
70 fr_time_delta_t wait_timeout; //!< How long we wait for slaves to acknowledge writing.
71
72 bool ipv4_integer; //!< Whether IPv4 addresses should be cast to integers,
73 //!< for renew operations.
74
75 bool copy_on_update; //!< Copy the address provided by ip_address to the
76 //!< allocated_address_attr if updates are successful.
77
78 fr_redis_cluster_t *cluster; //!< Redis cluster.
80
85
87 { FR_CONF_OFFSET("wait_num", rlm_redis_ippool_t, wait_num) },
88 { FR_CONF_OFFSET("wait_timeout", rlm_redis_ippool_t, wait_timeout) },
89
90 { FR_CONF_DEPRECATED("ip_address", rlm_redis_ippool_t, NULL) },
91
92 { FR_CONF_DEPRECATED("reply_attr", rlm_redis_ippool_t, NULL) },
93
94 { FR_CONF_OFFSET("ipv4_integer", rlm_redis_ippool_t, ipv4_integer) },
95 { FR_CONF_OFFSET("copy_on_update", rlm_redis_ippool_t, copy_on_update), .dflt = "yes", .quote = T_BARE_WORD },
96
97 /*
98 * Split out to allow conversion to universal ippool module with
99 * minimum of config changes.
100 */
101 { FR_CONF_POINTER("redis", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = redis_config },
103};
104
105/** Call environment used when calling redis_ippool allocate method.
106 *
107 */
108typedef struct {
109 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
110
111 fr_value_box_t offer_time; //!< How long we should reserve a lease for during
112 ///< the pre-allocation stage (typically responding
113 ///< to DHCP discover).
114
115 fr_value_box_t lease_time; //!< How long an IP address should be allocated for.
116
117 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
118 ///< or a combination of User-Name and something
119 ///< unique to the device.
120
121 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
122 ///< Option 82 gateway. Used for bulk lease cleanups.
123
124 fr_value_box_t requested_address; //!< Attribute to read the IP for renewal from.
125
126 tmpl_t *allocated_address_attr; //!< Attribute to populate with allocated IP.
127
128 tmpl_t *range_attr; //!< Attribute to write the range ID to.
129
130 tmpl_t *expiry_attr; //!< Time at which the lease will expire.
132
133/** Call environment used when calling redis_ippool update method.
134 *
135 */
136typedef struct {
137 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
138
139 fr_value_box_t lease_time; //!< How long an IP address should be allocated for.
140
141 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
142 ///< or a combination of User-Name and something
143 ///< unique to the device.
144
145 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
146 ///< Option 82 gateway. Used for bulk lease cleanups.
147
148 fr_value_box_t requested_address; //!< Attribute to read the IP for renewal from.
149
150 tmpl_t *allocated_address_attr; //!< Attribute to populate with allocated IP.
151
152 tmpl_t *range_attr; //!< Attribute to write the range ID to.
153
154 tmpl_t *expiry_attr; //!< Time at which the lease will expire.
156
157/** Call environment used when calling redis_ippool release method.
158 *
159 */
160typedef struct {
161 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
162
163 fr_value_box_t owner; //!< Unique lease owner identifier. Could be mac-address
164 ///< or a combination of User-Name and something
165 ///< unique to the device.
166
167 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
168 ///< Option 82 gateway. Used for bulk lease cleanups.
169
170 fr_value_box_t requested_address; //!< Attribute to read the IP for renewal from.
171
173
174/** Call environment used when calling redis_ippool bulk release method.
175 *
176 */
177typedef struct {
178 fr_value_box_t pool_name; //!< Name of the pool we're allocating IP addresses from.
179
180 fr_value_box_t gateway_id; //!< Gateway identifier, usually NAS-Identifier or
181 ///< Option 82 gateway. Used for bulk lease cleanups.
183
186 .env = (call_env_parser_t[]){
188 redis_ippool_alloc_call_env_t, pool_name) },
192 redis_ippool_alloc_call_env_t, gateway_id ), .pair.dflt = "", .pair.dflt_quote = T_SINGLE_QUOTED_STRING },
196 .pair.dflt = "%{%{Requested-IP-Address} || %{Net.Src.IP}}", .pair.dflt_quote = T_DOUBLE_QUOTED_STRING },
199 .pair.dflt = "&reply.IP-Pool.Range", .pair.dflt_quote = T_BARE_WORD },
202 }
203};
204
207 .env = (call_env_parser_t[]) {
211 .pair.dflt = "", .pair.dflt_quote = T_SINGLE_QUOTED_STRING },
214 .pair.dflt = "%{%{Requested-IP-Address} || %{Net.Src.IP}}", .pair.dflt_quote = T_DOUBLE_QUOTED_STRING },
217 .pair.dflt = "&reply.IP-Pool.Range", .pair.dflt_quote = T_BARE_WORD },
220 }
221};
222
235
245
246#define EOL "\n"
247
248/** Lua script for allocating new leases
249 *
250 * - KEYS[1] The pool name.
251 * - ARGV[1] Wall time (seconds since epoch).
252 * - ARGV[2] Expires in (seconds).
253 * - ARGV[3] Lease owner identifier (administratively configured).
254 * - ARGV[4] (optional) Gateway identifier.
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 /*
272 * Check to see if the client already has a lease,
273 * and if it does return that.
274 *
275 * The additional sanity checks are to allow for the record
276 * of device/ip binding to persist for longer than the lease.
277 */
278 "exists = redis.call('GET', owner_key);" EOL /* 8 */
279 "if exists then" EOL /* 9 */
280 " local expires = tonumber(redis.call('ZSCORE', pool_key, exists))" EOL /* 10 */
281 " local static = expires >= " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 11 */
282 " local expires_in = expires - (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0) - ARGV[1]" EOL /* 12 */
283 " if expires_in > 0 or static then" EOL /* 13 */
284 " ip = redis.call('HMGET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. exists, 'device', 'range', 'counter', 'gateway')" EOL /* 14 */
285 " if ip and (ip[1] == ARGV[3]) then" EOL /* 15 */
286 " if expires_in < tonumber(ARGV[2]) then" EOL /* 16 */
287 " redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2] + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), exists)" EOL /* 17 */
288 " expires_in = tonumber(ARGV[2])" EOL /* 18 */
289 " if not static then" EOL /* 19 */
290 " redis.call('EXPIRE', owner_key, ARGV[2])" EOL /* 20 */
291 " end" EOL /* 21 */
292 " end" EOL /* 22 */
293
294 /*
295 * Ensure gateway is set correctly
296 */
297 " if ARGV[4] ~= ip[4] then" EOL /* 23 */
298 " redis.call('HSET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":', 'gateway', ARGV[4])" EOL /* 24 */
299 " end" EOL /* 25 */
300 " return {" STRINGIFY(_IPPOOL_RCODE_SUCCESS) ", exists, ip[2], expires_in, ip[3] }" EOL /* 26 */
301 " end" EOL /* 27 */
302 " end" EOL /* 28 */
303 "end" EOL /* 29 */
304
305 /*
306 * Else, get the IP address which expired the longest time ago.
307 */
308 "ip = redis.call('ZREVRANGE', pool_key, -1, -1, 'WITHSCORES')" EOL /* 30 */
309 "if not ip or not ip[1] then" EOL /* 31 */
310 " return {" STRINGIFY(_IPPOOL_RCODE_POOL_EMPTY) "}" EOL /* 32 */
311 "end" EOL /* 33 */
312 "if ip[2] >= ARGV[1] then" EOL /* 34 */
313 " return {" STRINGIFY(_IPPOOL_RCODE_POOL_EMPTY) "}" EOL /* 35 */
314 "end" EOL /* 36 */
315 "redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2], ip[1])" EOL /* 37 */
316
317 /*
318 * Set the device/gateway keys
319 */
320 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ip[1]" EOL /* 38 */
321 "redis.call('HMSET', address_key, 'device', ARGV[3], 'gateway', ARGV[4])" EOL /* 39 */
322 "redis.call('SET', owner_key, ip[1])" EOL /* 40 */
323 "redis.call('EXPIRE', owner_key, ARGV[2])" EOL /* 41 */
324 "return { " EOL /* 42 */
325 " " STRINGIFY(_IPPOOL_RCODE_SUCCESS) "," EOL /* 43 */
326 " ip[1], " EOL /* 44 */
327 " redis.call('HGET', address_key, 'range'), " EOL /* 45 */
328 " tonumber(ARGV[2]), " EOL /* 46 */
329 " redis.call('HINCRBY', address_key, 'counter', 1)" EOL /* 47 */
330 "}" EOL; /* 48 */
332
333/** Lua script for updating leases
334 *
335 * - KEYS[1] The pool name.
336 * - ARGV[1] Wall time (seconds since epoch).
337 * - ARGV[2] Expires in (seconds).
338 * - ARGV[3] IP address to update.
339 * - ARGV[4] Lease owner identifier.
340 * - ARGV[5] (optional) Gateway identifier.
341 *
342 * Returns @verbatim array { <rcode>[, <range>] } @endverbatim
343 * - IPPOOL_RCODE_SUCCESS lease updated..
344 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
345 * - IPPOOL_RCODE_EXPIRED lease has already expired.
346 * - IPPOOL_RCODE_DEVICE_MISMATCH lease was allocated to a different client.
347 */
348static char lua_update_cmd[] =
349 "local ret" EOL /* 1 */
350 "local found" EOL /* 2 */
351
352 "local pool_key" EOL /* 3 */
353 "local address_key" EOL /* 4 */
354 "local owner_key" EOL /* 5 */
355
356 /*
357 * We either need to know that the IP was last allocated to the
358 * same device, or that the lease on the IP has NOT expired.
359 */
360 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[3]" EOL /* 6 */
361 "found = redis.call('HMGET', address_key, 'range', 'device', 'gateway', 'counter' )" EOL /* 7 */
362 /*
363 * Range may be nil (if not used), so we use the device key
364 */
365 "if not found[2] then" EOL /* 8 */
366 " return {" STRINGIFY(_IPPOOL_RCODE_NOT_FOUND) "}" EOL /* 9 */
367 "end" EOL /* 10 */
368 "if found[2] ~= ARGV[4] then" EOL /* 11 */
369 " return {" STRINGIFY(_IPPOOL_RCODE_DEVICE_MISMATCH) ", found[2]}" EOL /* 12 */
370 "end" EOL /* 13 */
371
372 /*
373 * Update the expiry time
374 */
375 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 14 */
376 "local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[3]))" EOL /* 15 */
377 "local static = expires > " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 16 */
378 "redis.call('ZADD', pool_key, 'XX', ARGV[1] + ARGV[2] + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), ARGV[3])" EOL /* 17 */
379
380 /*
381 * The device key should usually exist, but
382 * theoretically, if we were right on the cusp
383 * of a lease being expired, it may have been
384 * removed.
385 */
386 "owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[4]" EOL /* 18 */
387 "if not static and (redis.call('EXPIRE', owner_key, ARGV[2]) == 0) then" EOL /* 19 */
388 " redis.call('SET', owner_key, ARGV[3])" EOL /* 20 */
389 " redis.call('EXPIRE', owner_key, ARGV[2])" EOL /* 21 */
390 "end" EOL /* 22 */
391
392 /*
393 * Update the gateway address
394 */
395 "if ARGV[5] ~= found[3] then" EOL /* 23 */
396 " redis.call('HSET', address_key, 'gateway', ARGV[5])" EOL /* 24 */
397 "end" EOL /* 25 */
398 "return { " STRINGIFY(_IPPOOL_RCODE_SUCCESS) ", found[1], found[4] }"EOL; /* 26 */
400
401/** Lua script for releasing leases
402 *
403 * - KEYS[1] The pool name.
404 * - ARGV[1] Wall time (seconds since epoch).
405 * - ARGV[2] IP address to release.
406 * - ARGV[3] Client identifier.
407 *
408 * Sets the expiry time to be NOW() - 1 to maximise time between
409 * IP address allocations.
410 *
411 * Returns @verbatim array { <rcode>[, <counter>] } @endverbatim
412 * - IPPOOL_RCODE_SUCCESS lease updated..
413 * - IPPOOL_RCODE_NOT_FOUND lease not found in pool.
414 * - IPPOOL_RCODE_DEVICE_MISMATCH lease was allocated to a different client..
415 */
416static char lua_release_cmd[] =
417 "local ret" EOL /* 1 */
418 "local found" EOL /* 2 */
419
420 "local pool_key" EOL /* 3 */
421 "local address_key" EOL /* 4 */
422 "local owner_key" EOL /* 5 */
423
424 /*
425 * Check that the device releasing was the one
426 * the IP address is allocated to.
427 */
428 "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[2]" EOL /* 6 */
429 "found = redis.call('HGET', address_key, 'device')" EOL /* 7 */
430 "if not found then" EOL /* 8 */
431 " return { " STRINGIFY(_IPPOOL_RCODE_NOT_FOUND) "}" EOL /* 9 */
432 "end" EOL /* 10 */
433 "if found and found ~= ARGV[3] then" EOL /* 11 */
434 " return { " STRINGIFY(_IPPOOL_RCODE_DEVICE_MISMATCH) ", found }" EOL /* 12 */
435 "end" EOL /* 13 */
436
437 /*
438 * Set expiry time to now() - 1
439 */
440 "pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 14 */
441 "found = tonumber(redis.call('ZSCORE', pool_key, ARGV[2]))" EOL /* 15 */
442 "local static = found > " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 16 */
443 "redis.call('ZADD', pool_key, 'XX', ARGV[1] - 1 + (static and " STRINGIFY(IPPOOL_STATIC_BIT) " or 0), ARGV[2])" EOL /* 17 */
444
445 /*
446 * Remove the association between the device and a lease
447 */
448 "if not static then" EOL /* 18 */
449 " owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[3]" EOL /* 19 */
450 " redis.call('DEL', owner_key)" EOL /* 20 */
451 "end" EOL /* 21 */
452 "return { " EOL /* 22 */
453 " " STRINGIFY(_IPPOOL_RCODE_SUCCESS) "," EOL /* 23 */
454 " redis.call('HINCRBY', address_key, 'counter', 1) - 1" EOL /* 24 */
455 "}"; /* 25 */
457
458/** Check the requisite number of slaves replicated the lease info
459 *
460 * @param request The current request.
461 * @param wait_num Number of slaves required.
462 * @param reply we got from the server.
463 * @return
464 * - 0 if enough slaves replicated the data.
465 * - -1 if too few slaves replicated the data, or another error.
466 */
467static inline int ippool_wait_check(request_t *request, uint32_t wait_num, redisReply *reply)
468{
469 if (!wait_num) return 0;
470
471 if (reply->type != REDIS_REPLY_INTEGER) {
472 REDEBUG("WAIT result is wrong type, expected integer got %s",
473 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
474 return -1;
475 }
476 if (reply->integer < wait_num) {
477 REDEBUG("Too few slaves acknowledged allocation, needed %i, got %lli",
478 wait_num, reply->integer);
479 return -1;
480 }
481 return 0;
482}
483
484static void ippool_action_print(request_t *request, ippool_action_t action,
485 fr_log_lvl_t lvl,
486 fr_value_box_t const *key_prefix,
487 fr_value_box_t const *ip,
488 fr_value_box_t const *owner,
489 fr_value_box_t const *gateway_id,
490 uint32_t expires)
491{
492 char *device_str = NULL, *gateway_str = NULL;
493
494 if (gateway_id && gateway_id->vb_length > 0) gateway_str = fr_asprint(request, gateway_id->vb_strvalue,
495 gateway_id->vb_length, '"');
496 if (owner && owner->vb_length > 0) device_str = fr_asprint(request, owner->vb_strvalue, owner->vb_length, '"');
497
498 switch (action) {
500 RDEBUGX(lvl, "Allocating lease from pool \"%pV\"%s%s%s%s%s%s, expires in %us",
501 key_prefix,
502 device_str ? ", to \"" : "", device_str ? device_str : "",
503 device_str ? "\"" : "",
504 gateway_str ? ", on \"" : "", gateway_str ? gateway_str : "",
505 gateway_str ? "\"" : "",
506 expires);
507 break;
508
510 RDEBUGX(lvl, "Updating %pV in pool \"%pV\"%s%s%s%s%s%s, expires in %us",
511 ip, key_prefix,
512 device_str ? ", device \"" : "", device_str ? device_str : "",
513 device_str ? "\"" : "",
514 gateway_str ? ", gateway \"" : "", gateway_str ? gateway_str : "",
515 gateway_str ? "\"" : "",
516 expires);
517 break;
518
520 RDEBUGX(lvl, "Releasing %pV%s%s%s to pool \"%pV\"",
521 ip,
522 device_str ? " leased by \"" : "", device_str ? device_str : "",
523 device_str ? "\"" : "",
524 key_prefix);
525 break;
526
527 default:
528 break;
529 }
530
531 /*
532 * Ordering is important, needs to be LIFO
533 * for proper talloc pool reuse.
534 */
535 talloc_free(device_str);
536 talloc_free(gateway_str);
537}
538
539/** Execute a script against Redis cluster
540 *
541 * Handles uploading the script to the server if required.
542 *
543 * @note All replies will be freed on error.
544 *
545 * @param[out] out Where to write Redis reply object resulting from the command.
546 * @param[in] request The current request.
547 * @param[in] cluster configuration.
548 * @param[in] key to use to determine the cluster node.
549 * @param[in] key_len length of the key.
550 * @param[in] wait_num If > 0 wait until this many slaves have replicated the data
551 * from the last command.
552 * @param[in] wait_timeout How long to wait for slaves to replicate the data.
553 * @param[in] digest of script.
554 * @param[in] script to upload.
555 * @param[in] cmd EVALSHA command to execute.
556 * @param[in] ... Arguments for the eval command.
557 * @return status of the command.
558 */
559static fr_redis_rcode_t ippool_script(redisReply **out, request_t *request, fr_redis_cluster_t *cluster,
560 uint8_t const *key, size_t key_len,
561 uint32_t wait_num, fr_time_delta_t wait_timeout,
562 char const digest[], char const *script,
563 char const *cmd, ...)
564{
565 fr_redis_conn_t *conn;
566 redisReply *replies[5]; /* Must be equal to the maximum number of pipelined commands */
567 size_t reply_cnt = 0, i;
568
570 fr_redis_rcode_t s_ret, status;
571 unsigned int pipelined = 0;
572
573 va_list ap;
574
575 *out = NULL;
576
577#ifndef NDEBUG
578 memset(replies, 0, sizeof(replies));
579#endif
580
581 va_start(ap, cmd);
582
583 for (s_ret = fr_redis_cluster_state_init(&state, &conn, cluster, request, key, key_len, false);
584 s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */
585 s_ret = fr_redis_cluster_state_next(&state, &conn, cluster, request, status, &replies[0])) {
586 va_list copy;
587
588 RDEBUG3("Calling script 0x%s", digest);
589 va_copy(copy, ap); /* copy or segv */
590 redisvAppendCommand(conn->handle, cmd, copy);
591 va_end(copy);
592 pipelined = 1;
593 if (wait_num) {
594 redisAppendCommand(conn->handle, "WAIT %i %i", wait_num, fr_time_delta_to_msec(wait_timeout));
595 pipelined++;
596 }
597 reply_cnt = fr_redis_pipeline_result(&pipelined, &status,
598 replies, NUM_ELEMENTS(replies),
599 conn);
600 if (status != REDIS_RCODE_NO_SCRIPT) continue;
601
602 /*
603 * Clear out the existing reply
604 */
605 fr_redis_pipeline_free(replies, reply_cnt);
606
607 /*
608 * Last command failed with NOSCRIPT, this means
609 * we have to send the Lua script up to the node
610 * so it can be cached.
611 */
612 RDEBUG3("Loading script 0x%s", digest);
613 redisAppendCommand(conn->handle, "MULTI");
614 redisAppendCommand(conn->handle, "SCRIPT LOAD %s", script);
615 va_copy(copy, ap); /* copy or segv */
616 redisvAppendCommand(conn->handle, cmd, copy);
617 va_end(copy);
618 redisAppendCommand(conn->handle, "EXEC");
619 pipelined = 4;
620 if (wait_num) {
621 redisAppendCommand(conn->handle, "WAIT %i %i", wait_num, fr_time_delta_to_msec(wait_timeout));
622 pipelined++;
623 }
624
625 reply_cnt = fr_redis_pipeline_result(&pipelined, &status,
626 replies, NUM_ELEMENTS(replies),
627 conn);
628 if (status == REDIS_RCODE_SUCCESS) {
629 if (RDEBUG_ENABLED3) for (i = 0; i < reply_cnt; i++) {
630 fr_redis_reply_print(L_DBG_LVL_3, replies[i], request, i);
631 }
632
633 if (replies[3]->type != REDIS_REPLY_ARRAY) {
634 RERROR("Bad response to EXEC, expected array got %s",
635 fr_table_str_by_value(redis_reply_types, replies[3]->type, "<UNKNOWN>"));
636 error:
637 fr_redis_pipeline_free(replies, reply_cnt);
638 status = REDIS_RCODE_ERROR;
639 goto finish;
640 }
641 if (replies[3]->elements != 2) {
642 RERROR("Bad response to EXEC, expected 2 result elements, got %zu",
643 replies[3]->elements);
644 goto error;
645 }
646 if (replies[3]->element[0]->type != REDIS_REPLY_STRING) {
647 RERROR("Bad response to SCRIPT LOAD, expected string got %s",
648 fr_table_str_by_value(redis_reply_types, replies[3]->element[0]->type, "<UNKNOWN>"));
649 goto error;
650 }
651 if (strcmp(replies[3]->element[0]->str, digest) != 0) {
652 RWDEBUG("Incorrect SHA1 from SCRIPT LOAD, expected %s, got %s",
653 digest, replies[3]->element[0]->str);
654 goto error;
655 }
656 }
657 }
658 if (s_ret != REDIS_RCODE_SUCCESS) goto error;
659
660 switch (reply_cnt) {
661 case 2: /* EVALSHA with wait */
662 if (ippool_wait_check(request, wait_num, replies[1]) < 0) goto error;
663 fr_redis_reply_free(&replies[1]); /* Free the wait response */
665
666 case 1: /* EVALSHA */
667 *out = replies[0];
668 break;
669
670 case 5: /* LOADSCRIPT + EVALSHA + WAIT */
671 if (ippool_wait_check(request, wait_num, replies[4]) < 0) goto error;
672 fr_redis_reply_free(&replies[4]); /* Free the wait response */
674
675 case 4: /* LOADSCRIPT + EVALSHA */
676 fr_redis_reply_free(&replies[2]); /* Free the queued cmd response*/
677 fr_redis_reply_free(&replies[1]); /* Free the queued script load response */
678 fr_redis_reply_free(&replies[0]); /* Free the queued multi response */
679 *out = replies[3]->element[1];
680 replies[3]->element[1] = NULL; /* Prevent double free */
681 fr_redis_reply_free(&replies[3]); /* This works because hiredis checks for NULL elements */
682 break;
683
684 case 0:
685 break;
686 }
687
688finish:
689 va_end(ap);
690 return s_ret;
691}
692
693/** Allocate a new IP address from a pool
694 *
695 */
698{
699 struct timeval now;
700 redisReply *reply = NULL;
701
702 fr_redis_rcode_t status;
704
705 fr_assert(env->pool_name.vb_length > 0);
706 fr_assert(env->owner.vb_length > 0);
707
709
710 status = ippool_script(&reply, request, inst->cluster,
711 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
712 inst->wait_num, inst->wait_timeout,
714 "EVALSHA %s 1 %b %u %u %b %b",
716 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
717 (unsigned int)now.tv_sec, lease_time,
718 (uint8_t const *)env->owner.vb_strvalue, env->owner.vb_length,
719 (uint8_t const *)env->gateway_id.vb_strvalue, env->gateway_id.vb_length);
720 if (status != REDIS_RCODE_SUCCESS) {
721 ret = IPPOOL_RCODE_FAIL;
722 goto finish;
723 }
724
725 fr_assert(reply);
726 if (reply->type != REDIS_REPLY_ARRAY) {
727 REDEBUG("Expected result to be array got \"%s\"",
728 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
729 ret = IPPOOL_RCODE_FAIL;
730 goto finish;
731 }
732
733 if (reply->elements == 0) {
734 REDEBUG("Got empty result array");
735 ret = IPPOOL_RCODE_FAIL;
736 goto finish;
737 }
738
739 /*
740 * Process return code
741 */
742 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
743 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
744 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
745 ret = IPPOOL_RCODE_FAIL;
746 goto finish;
747 }
748 ret = reply->element[0]->integer;
749 if (ret < 0) goto finish;
750
751 /*
752 * Process IP address
753 */
754 if (reply->elements > 1) {
755 tmpl_t ip_rhs;
756 map_t ip_map = {
758 .op = T_OP_SET,
759 .rhs = &ip_rhs
760 };
761
762 tmpl_init_shallow(&ip_rhs, TMPL_TYPE_DATA, T_BARE_WORD, "", 0, NULL);
763 switch (reply->element[1]->type) {
764 /*
765 * Destination attribute may not be IPv4, in which case
766 * we want to pre-convert the integer value to an IPv4
767 * address before casting it once more to the type of
768 * the destination attribute.
769 */
770 case REDIS_REPLY_INTEGER:
771 {
772 if (tmpl_attr_tail_da(ip_map.lhs)->type != FR_TYPE_IPV4_ADDR) {
773 fr_value_box_t tmp;
774
775 fr_value_box(&tmp, (uint32_t)ntohl((uint32_t)reply->element[1]->integer), true);
777 NULL, &tmp)) {
778 RPEDEBUG("Failed converting integer to IPv4 address");
779 ret = IPPOOL_RCODE_FAIL;
780 goto finish;
781 }
782 } else {
783 fr_value_box(&ip_map.rhs->data.literal,
784 (uint32_t)ntohl((uint32_t)reply->element[1]->integer), true);
785 }
786 }
787 goto do_ip_map;
788
789 case REDIS_REPLY_STRING:
790 fr_value_box_bstrndup_shallow(&ip_map.rhs->data.literal,
791 NULL, reply->element[1]->str, reply->element[1]->len, false);
792 do_ip_map:
793 if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) {
794 ret = IPPOOL_RCODE_FAIL;
795 goto finish;
796 }
797 break;
798
799 default:
800 REDEBUG("Server returned unexpected type \"%s\" for IP element (result[1])",
801 fr_table_str_by_value(redis_reply_types, reply->element[1]->type, "<UNKNOWN>"));
802 ret = IPPOOL_RCODE_FAIL;
803 goto finish;
804 }
805 }
806
807 /*
808 * Process Range identifier
809 */
810 if (reply->elements > 2) {
811 switch (reply->element[2]->type) {
812 /*
813 * Add range ID to request
814 */
815 case REDIS_REPLY_STRING:
816 {
817 tmpl_t range_rhs;
818 map_t range_map = {
819 .lhs = env->range_attr,
820 .op = T_OP_SET,
821 .rhs = &range_rhs
822 };
823
825 fr_value_box_bstrndup_shallow(&range_map.rhs->data.literal,
826 NULL, reply->element[2]->str, reply->element[2]->len, true);
827 if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) {
828 ret = IPPOOL_RCODE_FAIL;
829 goto finish;
830 }
831 }
832 break;
833
834 case REDIS_REPLY_NIL:
835 break;
836
837 default:
838 REDEBUG("Server returned unexpected type \"%s\" for range element (result[2])",
839 fr_table_str_by_value(redis_reply_types, reply->element[2]->type, "<UNKNOWN>"));
840 ret = IPPOOL_RCODE_FAIL;
841 goto finish;
842 }
843 }
844
845 /*
846 * Process Expiry time
847 */
848 if (env->expiry_attr && (reply->elements > 3)) {
849 tmpl_t expiry_rhs;
850 map_t expiry_map = {
851 .lhs = env->expiry_attr,
852 .op = T_OP_SET,
853 .rhs = &expiry_rhs
854 };
855
856 tmpl_init_shallow(&expiry_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
857 if (reply->element[3]->type != REDIS_REPLY_INTEGER) {
858 REDEBUG("Server returned unexpected type \"%s\" for expiry element (result[3])",
859 fr_table_str_by_value(redis_reply_types, reply->element[3]->type, "<UNKNOWN>"));
860 ret = IPPOOL_RCODE_FAIL;
861 goto finish;
862 }
863
864 fr_value_box(&expiry_map.rhs->data.literal, (uint32_t)reply->element[3]->integer, true);
865 if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) {
866 ret = IPPOOL_RCODE_FAIL;
867 goto finish;
868 }
869 }
870finish:
871 fr_redis_reply_free(&reply);
872 return ret;
873}
874
875/** Update an existing IP address in a pool
876 *
877 */
880 fr_ipaddr_t *ip,
881 fr_value_box_t const *owner,
882 fr_value_box_t const *gateway_id,
883 uint32_t expires)
884{
885 struct timeval now;
886 redisReply *reply = NULL;
887
888 fr_redis_rcode_t status;
890
892
893 if ((ip->af == AF_INET) && inst->ipv4_integer) {
894 status = ippool_script(&reply, request, inst->cluster,
895 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
896 inst->wait_num, inst->wait_timeout,
898 "EVALSHA %s 1 %b %u %u %u %b %b",
900 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
901 (unsigned int)now.tv_sec, expires,
902 htonl(ip->addr.v4.s_addr),
903 (uint8_t const *)owner->vb_strvalue, owner->vb_length,
904 (uint8_t const *)gateway_id->vb_strvalue, gateway_id->vb_length);
905 } else {
906 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
907
908 IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix);
909 status = ippool_script(&reply, request, inst->cluster,
910 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
911 inst->wait_num, inst->wait_timeout,
913 "EVALSHA %s 1 %b %u %u %s %b %b",
915 (uint8_t const *)env->pool_name.vb_strvalue, env->pool_name.vb_length,
916 (unsigned int)now.tv_sec, expires,
917 ip_buff,
918 (uint8_t const *)owner->vb_strvalue, owner->vb_length,
919 (uint8_t const *)gateway_id->vb_strvalue, gateway_id->vb_length);
920 }
921 if (status != REDIS_RCODE_SUCCESS) {
922 ret = IPPOOL_RCODE_FAIL;
923 goto finish;
924 }
925
926 if (reply->type != REDIS_REPLY_ARRAY) {
927 REDEBUG("Expected result to be array got \"%s\"",
928 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
929 ret = IPPOOL_RCODE_FAIL;
930 goto finish;
931 }
932
933 if (reply->elements == 0) {
934 REDEBUG("Got empty result array");
935 ret = IPPOOL_RCODE_FAIL;
936 goto finish;
937 }
938
939 /*
940 * Process return code
941 */
942 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
943 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
944 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
945 ret = IPPOOL_RCODE_FAIL;
946 goto finish;
947 }
948 ret = reply->element[0]->integer;
949 if (ret < 0) goto finish;
950
951 /*
952 * Process Range identifier
953 */
954 if (reply->elements > 1) {
955 switch (reply->element[1]->type) {
956 /*
957 * Add range ID to request
958 */
959 case REDIS_REPLY_STRING:
960 {
961 tmpl_t range_rhs;
962 map_t range_map = { .lhs = env->range_attr, .op = T_OP_SET, .rhs = &range_rhs };
963
965 fr_value_box_bstrndup_shallow(&range_map.rhs->data.literal, NULL,
966 reply->element[1]->str, reply->element[1]->len, true);
967 if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) {
968 ret = IPPOOL_RCODE_FAIL;
969 goto finish;
970 }
971 }
972 break;
973
974 case REDIS_REPLY_NIL:
975 break;
976
977 default:
978 REDEBUG("Server returned unexpected type \"%s\" for range element (result[1])",
979 fr_table_str_by_value(redis_reply_types, reply->element[0]->type, "<UNKNOWN>"));
980 ret = IPPOOL_RCODE_FAIL;
981 goto finish;
982 }
983 }
984
985 /*
986 * Copy expiry time to expires attribute (if set)
987 */
988 if (env->expiry_attr) {
989 tmpl_t expiry_rhs;
990 map_t expiry_map = {
991 .lhs = env->expiry_attr,
992 .op = T_OP_SET,
993 .rhs = &expiry_rhs
994 };
995
996
997 tmpl_init_shallow(&expiry_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
998
999 fr_value_box(&expiry_map.rhs->data.literal, expires, false);
1000 if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) {
1001 ret = IPPOOL_RCODE_FAIL;
1002 goto finish;
1003 }
1004 }
1005
1006finish:
1007 fr_redis_reply_free(&reply);
1008
1009 return ret;
1010}
1011
1012/** Release an existing IP address in a pool
1013 *
1014 */
1016 fr_value_box_t const *key_prefix,
1017 fr_ipaddr_t *ip,
1018 fr_value_box_t const *owner)
1019{
1020 struct timeval now;
1021 redisReply *reply = NULL;
1022
1023 fr_redis_rcode_t status;
1025
1026 now = fr_time_to_timeval(fr_time());
1027
1028 if ((ip->af == AF_INET) && inst->ipv4_integer) {
1029 status = ippool_script(&reply, request, inst->cluster,
1030 (uint8_t const *)key_prefix->vb_strvalue, key_prefix->vb_length,
1031 inst->wait_num, inst->wait_timeout,
1033 "EVALSHA %s 1 %b %u %u %b",
1035 (uint8_t const *)key_prefix->vb_strvalue, key_prefix->vb_length,
1036 (unsigned int)now.tv_sec,
1037 htonl(ip->addr.v4.s_addr),
1038 (uint8_t const *)owner->vb_strvalue, owner->vb_length);
1039 } else {
1040 char ip_buff[FR_IPADDR_PREFIX_STRLEN];
1041
1042 IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix);
1043 status = ippool_script(&reply, request, inst->cluster,
1044 (uint8_t const *)key_prefix->vb_strvalue, key_prefix->vb_length,
1045 inst->wait_num, inst->wait_timeout,
1047 "EVALSHA %s 1 %b %u %s %b",
1049 (uint8_t const *)key_prefix->vb_strvalue, key_prefix->vb_length,
1050 (unsigned int)now.tv_sec,
1051 ip_buff,
1052 (uint8_t const *)owner->vb_strvalue, owner->vb_length);
1053 }
1054 if (status != REDIS_RCODE_SUCCESS) {
1055 ret = IPPOOL_RCODE_FAIL;
1056 goto finish;
1057 }
1058
1059 if (reply->type != REDIS_REPLY_ARRAY) {
1060 REDEBUG("Expected result to be array got \"%s\"",
1061 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1062 ret = IPPOOL_RCODE_FAIL;
1063 goto finish;
1064 }
1065
1066 if (reply->elements == 0) {
1067 REDEBUG("Got empty result array");
1068 ret = IPPOOL_RCODE_FAIL;
1069 goto finish;
1070 }
1071
1072 /*
1073 * Process return code
1074 */
1075 if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
1076 REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
1077 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1078 ret = IPPOOL_RCODE_FAIL;
1079 goto finish;
1080 }
1081 ret = reply->element[0]->integer;
1082 if (ret < 0) goto finish;
1083
1084finish:
1085 fr_redis_reply_free(&reply);
1086
1087 return ret;
1088}
1089
1090#define CHECK_POOL_NAME \
1091 if (env->pool_name.vb_length > IPPOOL_MAX_KEY_PREFIX_SIZE) { \
1092 REDEBUG("Pool name too long. Expected %u bytes, got %ld bytes", \
1093 IPPOOL_MAX_KEY_PREFIX_SIZE, env->pool_name.vb_length); \
1094 RETURN_MODULE_FAIL; \
1095 } \
1096 if (env->pool_name.vb_length == 0) { \
1097 RDEBUG2("Empty pool name. Doing nothing"); \
1098 RETURN_MODULE_NOOP; \
1099 }
1100
1101static unlang_action_t CC_HINT(nonnull) mod_alloc(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
1102{
1104 redis_ippool_alloc_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_alloc_call_env_t);
1105 uint32_t lease_time;
1106
1108
1109 /*
1110 * If offer_time is defined, it will be FR_TYPE_UINT32.
1111 * Fall back to lease_time otherwise.
1112 */
1113 lease_time = (env->offer_time.type == FR_TYPE_UINT32) ?
1114 env->offer_time.vb_uint32 : env->lease_time.vb_uint32;
1116 &env->owner, &env->gateway_id, lease_time);
1117 switch (redis_ippool_allocate(inst, request, env, lease_time)) {
1119 RDEBUG2("IP address lease allocated");
1121
1123 RWDEBUG("Pool contains no free addresses");
1125
1126 default:
1128 }
1129}
1130
1131static unlang_action_t CC_HINT(nonnull) mod_update(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
1132{
1134 redis_ippool_update_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_update_call_env_t);
1135
1137
1139 &env->requested_address, &env->owner, &env->gateway_id, env->lease_time.vb_uint32);
1140 switch (redis_ippool_update(inst, request, env,
1141 &env->requested_address.datum.ip, &env->owner,
1142 &env->gateway_id,
1143 env->lease_time.vb_uint32)) {
1145 RDEBUG2("Requested IP address' \"%pV\" lease updated", &env->requested_address);
1146
1147 /*
1148 * Copy over the input IP address to the reply attribute
1149 */
1150 if (inst->copy_on_update) {
1151 tmpl_t ip_rhs = {
1152 .name = "",
1153 .type = TMPL_TYPE_DATA,
1154 .quote = T_BARE_WORD,
1155 };
1156 map_t ip_map = {
1158 .op = T_OP_SET,
1159 .rhs = &ip_rhs
1160 };
1161
1162 fr_value_box_copy(NULL, &ip_rhs.data.literal, &env->requested_address);
1163
1164 if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) RETURN_MODULE_FAIL;
1165 }
1167
1168 /*
1169 * It's useful to be able to identify the 'not found' case
1170 * as we can relay to a server where the IP address might
1171 * be found. This extremely useful for migrations.
1172 */
1174 REDEBUG("Requested IP address \"%pV\" is not a member of the specified pool",
1175 &env->requested_address);
1177
1179 REDEBUG("Requested IP address' \"%pV\" lease already expired at time of renewal",
1180 &env->requested_address);
1182
1184 REDEBUG("Requested IP address' \"%pV\" lease allocated to another device",
1185 &env->requested_address);
1187
1188 default:
1190 }
1191}
1192
1193static unlang_action_t CC_HINT(nonnull) mod_release(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
1194{
1197
1199
1201 &env->requested_address, &env->owner, &env->gateway_id, 0);
1202 switch (redis_ippool_release(inst, request, &env->pool_name, &env->requested_address.datum.ip, &env->owner)) {
1204 RDEBUG2("IP address \"%pV\" released", &env->requested_address);
1206
1207 /*
1208 * It's useful to be able to identify the 'not found' case
1209 * as we can relay to a server where the IP address might
1210 * be found. This extremely useful for migrations.
1211 */
1213 REDEBUG("Requested IP address \"%pV\" is not a member of the specified pool",
1214 &env->requested_address);
1216
1218 REDEBUG("Requested IP address' \"%pV\" lease allocated to another device",
1219 &env->requested_address);
1221
1222 default:
1224 }
1225}
1226
1228 request_t *request)
1229{
1230 RDEBUG2("Bulk release not yet implemented");
1232}
1233
1234static int mod_instantiate(module_inst_ctx_t const *mctx)
1235{
1236 static bool done_hash = false;
1237 CONF_SECTION *subcs = cf_section_find(mctx->mi->conf, "redis", NULL);
1238
1239 rlm_redis_ippool_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_redis_ippool_t);
1240
1241 fr_assert(subcs);
1242
1243 inst->cluster = fr_redis_cluster_alloc(inst, subcs, &inst->conf, true, NULL, NULL, NULL);
1244 if (!inst->cluster) return -1;
1245
1246 if (!fr_redis_cluster_min_version(inst->cluster, "3.0.2")) {
1247 PERROR("Cluster error");
1248 return -1;
1249 }
1250
1251 /*
1252 * Pre-Compute the SHA1 hashes of the Lua scripts
1253 */
1254 if (!done_hash) {
1255 fr_sha1_ctx sha1_ctx;
1257
1258 fr_sha1_init(&sha1_ctx);
1259 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_alloc_cmd, sizeof(lua_alloc_cmd) - 1);
1260 fr_sha1_final(digest, &sha1_ctx);
1261 fr_base16_encode(&FR_SBUFF_OUT(lua_alloc_digest, sizeof(lua_alloc_digest)), &FR_DBUFF_TMP(digest, sizeof(digest)));
1262
1263 fr_sha1_init(&sha1_ctx);
1264 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_update_cmd, sizeof(lua_update_cmd) - 1);
1265 fr_sha1_final(digest, &sha1_ctx);
1267
1268 fr_sha1_init(&sha1_ctx);
1269 fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_release_cmd, sizeof(lua_release_cmd) - 1);
1270 fr_sha1_final(digest, &sha1_ctx);
1272 }
1273
1274 return 0;
1275}
1276
1277static int mod_load(void)
1278{
1280
1281 return 0;
1282}
1283
1286 .common = {
1287 .magic = MODULE_MAGIC_INIT,
1288 .name = "redis",
1289 .inst_size = sizeof(rlm_redis_ippool_t),
1291 .onload = mod_load,
1293 },
1294 .method_group = {
1295 .bindings = (module_method_binding_t[]){
1296 { .section = SECTION_NAME("recv", "Access-Request"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* radius */
1297 { .section = SECTION_NAME("accounting", "Start"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* radius */
1298 { .section = SECTION_NAME("accounting", "Interim-Update"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* radius */
1299 { .section = SECTION_NAME("accounting", "Stop"), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* radius */
1300 { .section = SECTION_NAME("accounting", "Accounting-On"), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* radius */
1301 { .section = SECTION_NAME("accounting", "Accounting-Off"), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* radius */
1302
1303 { .section = SECTION_NAME("recv", "Discover"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* dhcpv4 */
1304 { .section = SECTION_NAME("recv", "Release"), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* dhcpv4 */
1305 { .section = SECTION_NAME("send", "Ack"), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* dhcpv4 */
1306
1307 { .section = SECTION_NAME("recv", "Solicit"), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* dhcpv6 */
1308
1309 { .section = SECTION_NAME("recv", CF_IDENT_ANY), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* generic */
1310 { .section = SECTION_NAME("send", CF_IDENT_ANY), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* generic */
1311
1312 { .section = SECTION_NAME("allocate", NULL), .method = mod_alloc, .method_env = &redis_ippool_alloc_method_env }, /* verb */
1313 { .section = SECTION_NAME("update", NULL), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* verb */
1314 { .section = SECTION_NAME("renew", NULL), .method = mod_update, .method_env = &redis_ippool_update_method_env }, /* verb */
1315 { .section = SECTION_NAME("release", NULL), .method = mod_release, .method_env = &redis_ippool_release_method_env }, /* verb */
1316 { .section = SECTION_NAME("bulk-release", NULL), .method = mod_bulk_release, .method_env = &redis_ippool_bulk_release_method_env }, /* verb */
1318 }
1319 }
1320};
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:57
#define RCSID(id)
Definition build.h:483
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:322
#define STRINGIFY(x)
Definition build.h:197
#define UNUSED
Definition build.h:315
#define NUM_ELEMENTS(_t)
Definition build.h:337
#define CALL_ENV_TERMINATOR
Definition call_env.h:231
#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:235
call_env_parser_t const * env
Parsing rules for call method env.
Definition call_env.h:242
@ 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_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:335
#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:384
Per method call config.
Definition call_env.h:175
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:642
#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:398
#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:268
fr_token_t quote
Quoting around the default value. Only used for templates.
Definition cf_parse.h:634
#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:323
@ CONF_FLAG_SUBSECTION
Instead of putting the information into a configuration structure, the configuration file routines MA...
Definition cf_parse.h:412
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:579
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:1028
#define CF_IDENT_ANY
Definition cf_util.h:78
fr_redis_cluster_t * fr_redis_cluster_alloc(TALLOC_CTX *ctx, CONF_SECTION *module, fr_redis_conf_t *conf, bool triggers_enabled, char const *log_prefix, char const *trigger_prefix, fr_pair_list_t *trigger_args)
Allocate and initialise a new cluster structure.
Definition cluster.c:2261
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:1863
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:1741
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:2202
A redis cluster.
Definition cluster.c:251
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:514
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
uint8_t prefix
Prefix length - Between 0-32 for IPv4 and 0-128 for IPv6.
Definition inet.h:69
int af
Address family.
Definition inet.h:64
#define FR_IPADDR_PREFIX_STRLEN
Like FR_IPADDR_STRLEN but with space for a prefix.
Definition inet.h:93
union fr_ipaddr_t::@130 addr
IPv4/6 prefix.
#define PERROR(_fmt,...)
Definition log.h:228
#define RWDEBUG(fmt,...)
Definition log.h:361
#define RDEBUG_ENABLED3
True if request debug level 1-3 messages are enabled.
Definition log.h:335
#define RDEBUG3(fmt,...)
Definition log.h:343
#define RDEBUGX(_l, fmt,...)
Definition log.h:340
#define RERROR(fmt,...)
Definition log.h:298
#define RPEDEBUG(fmt,...)
Definition log.h:376
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:1487
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:1781
talloc_free(reap)
fr_log_lvl_t
Definition log.h:67
@ L_DBG_LVL_3
3rd highest priority debug messages (-xxx | -Xx).
Definition log.h:72
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition log.h:71
@ 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:428
static const conf_parser_t config[]
Definition base.c:183
#define fr_assert(_expr)
Definition rad_assert.h:38
#define REDEBUG(fmt,...)
Definition radclient.h:52
#define RDEBUG2(fmt,...)
Definition radclient.h:54
#define RETURN_MODULE_NOOP
Definition rcode.h:62
#define RETURN_MODULE_INVALID
Definition rcode.h:59
#define RETURN_MODULE_FAIL
Definition rcode.h:56
#define RETURN_MODULE_UPDATED
Definition rcode.h:63
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:40
#define RETURN_MODULE_NOTFOUND
Definition rcode.h:61
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:133
void fr_redis_reply_print(fr_log_lvl_t lvl, redisReply *reply, request_t *request, int idx)
Print the response data in a useful treelike form.
Definition redis.c:141
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:535
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 unlang_action_t mod_release(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
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.
#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_alloc(rlm_rcode_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.
static unlang_action_t mod_bulk_release(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request)
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 unlang_action_t mod_update(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
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
static int instantiate(module_inst_ctx_t const *mctx)
Definition rlm_rest.c:1310
#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:40
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:329
size_t inst_size
Size of the module's instance data.
Definition module.h:203
void * data
Module's instance data.
Definition module.h:271
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:151
Named methods exported by a module.
Definition module.h:173
#define tmpl_value(_tmpl)
Definition tmpl.h:948
@ TMPL_TYPE_DATA
Value in native boxed format.
Definition tmpl.h:142
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition tmpl.h:812
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
#define fr_time()
Allow us to arbitrarily manipulate time.
Definition state_test.c:8
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:282
#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:122
@ T_BARE_WORD
Definition token.h:120
@ T_OP_SET
Definition token.h:84
@ T_DOUBLE_QUOTED_STRING
Definition token.h:121
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:3352
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:3740
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:4232
#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:871
int nonnull(2, 5))
static size_t char ** out
Definition value.h:997