The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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  */
41 RCSID("$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  */
61 typedef 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 
84 };
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  */
108 typedef 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  */
136 typedef 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  */
160 typedef 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  */
177 typedef 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 
225  .env = (call_env_parser_t[]) {
229  .pair.dflt = "", .pair.dflt_quote = T_SINGLE_QUOTED_STRING },
231  .pair.dflt = "%{%{Requested-IP-Address} || %{Net.Src.IP}}", .pair.dflt_quote = T_DOUBLE_QUOTED_STRING },
233  }
234 };
235 
238  .env = (call_env_parser_t[]) {
241  .pair.dflt = "", .pair.dflt_quote = T_SINGLE_QUOTED_STRING },
243  }
244 };
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  */
260 static 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 */
331 static char lua_alloc_digest[(SHA1_DIGEST_LENGTH * 2) + 1];
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  */
348 static 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 */
399 static char lua_update_digest[(SHA1_DIGEST_LENGTH * 2) + 1];
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  */
416 static 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 */
456 static char lua_release_digest[(SHA1_DIGEST_LENGTH * 2) + 1];
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  */
467 static 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 
484 static 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 
509  case POOL_ACTION_UPDATE:
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 
519  case POOL_ACTION_RELEASE:
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  */
559 static 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 */
664  FALL_THROUGH;
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 */
673  FALL_THROUGH;
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 
688 finish:
689  va_end(ap);
690  return s_ret;
691 }
692 
693 /** Allocate a new IP address from a pool
694  *
695  */
697  redis_ippool_alloc_call_env_t *env, uint32_t lease_time)
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 
708  now = fr_time_to_timeval(fr_time());
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 = {
757  .lhs = env->allocated_address_attr,
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 
824  tmpl_init_shallow(&range_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
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  }
870 finish:
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 
891  now = fr_time_to_timeval(fr_time());
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 
964  tmpl_init_shallow(&range_rhs, TMPL_TYPE_DATA, T_DOUBLE_QUOTED_STRING, "", 0, NULL);
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 
1006 finish:
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 
1084 finish:
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 
1101 static 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)) {
1118  case IPPOOL_RCODE_SUCCESS:
1119  RDEBUG2("IP address lease allocated");
1121 
1123  RWDEBUG("Pool contains no free addresses");
1125 
1126  default:
1128  }
1129 }
1130 
1131 static 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)) {
1144  case IPPOOL_RCODE_SUCCESS:
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 = {
1157  .lhs = env->allocated_address_attr,
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 
1178  case IPPOOL_RCODE_EXPIRED:
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 
1193 static unlang_action_t CC_HINT(nonnull) mod_release(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
1194 {
1196  redis_ippool_release_call_env_t *env = talloc_get_type_abort(mctx->env_data, redis_ippool_release_call_env_t);
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)) {
1203  case IPPOOL_RCODE_SUCCESS:
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 
1234 static 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;
1256  uint8_t digest[SHA1_DIGEST_LENGTH];
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);
1266  fr_base16_encode(&FR_SBUFF_OUT(lua_update_digest, sizeof(lua_update_digest)), &FR_DBUFF_TMP(digest, sizeof(digest)));
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);
1271  fr_base16_encode(&FR_SBUFF_OUT(lua_release_digest, sizeof(lua_release_digest)), &FR_DBUFF_TMP(digest, sizeof(digest)));
1272  }
1273 
1274  return 0;
1275 }
1276 
1277 static 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),
1290  .config = module_config,
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:481
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition: build.h:320
#define STRINGIFY(x)
Definition: build.h:195
#define UNUSED
Definition: build.h:313
#define NUM_ELEMENTS(_t)
Definition: build.h:335
#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_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:627
#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:385
#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
#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:310
@ CONF_FLAG_SUBSECTION
Instead of putting the information into a configuration structure, the configuration file routines MA...
Definition: cf_parse.h:399
Defines a CONF_PAIR to C data type mapping.
Definition: cf_parse.h:564
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_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
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
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.
Definition: merged_model.c:272
#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.
Definition: merged_model.c:86
@ FR_TYPE_STRING
String of printable characters.
Definition: merged_model.c:83
@ FR_TYPE_UINT32
32 Bit unsigned integer.
Definition: merged_model.c:99
@ FR_TYPE_VOID
User data.
Definition: merged_model.c:127
@ FR_TYPE_COMBO_IP_ADDR
IPv4 or IPv6 address depending on length.
Definition: merged_model.c:91
unsigned int uint32_t
Definition: merged_model.c:33
unsigned char uint8_t
Definition: merged_model.c:30
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 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_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
Definition: redis_ippool.h:60
#define _IPPOOL_RCODE_NOT_FOUND
Definition: redis_ippool.h:34
ippool_action_t
Definition: redis_ippool.h:49
@ POOL_ACTION_RELEASE
Definition: redis_ippool.h:52
@ POOL_ACTION_ALLOCATE
Definition: redis_ippool.h:50
@ POOL_ACTION_UPDATE
Definition: redis_ippool.h:51
#define IPPOOL_OWNER_KEY
Definition: redis_ippool.h:59
#define _IPPOOL_RCODE_POOL_EMPTY
Definition: redis_ippool.h:37
ippool_rcode_t
Definition: redis_ippool.h:40
@ IPPOOL_RCODE_EXPIRED
Definition: redis_ippool.h:43
@ IPPOOL_RCODE_DEVICE_MISMATCH
Definition: redis_ippool.h:44
@ IPPOOL_RCODE_NOT_FOUND
Definition: redis_ippool.h:42
@ IPPOOL_RCODE_POOL_EMPTY
Definition: redis_ippool.h:45
@ IPPOOL_RCODE_SUCCESS
Definition: redis_ippool.h:41
@ IPPOOL_RCODE_FAIL
Definition: redis_ippool.h:46
#define IPPOOL_POOL_KEY
Definition: redis_ippool.h:57
#define _IPPOOL_RCODE_DEVICE_MISMATCH
Definition: redis_ippool.h:36
#define _IPPOOL_RCODE_SUCCESS
Definition: redis_ippool.h:33
#define IPPOOL_SPRINT_IP(_buff, _ip, _prefix)
If the prefix is as wide as the AF data size then print it without CIDR notation.
Definition: redis_ippool.h:119
#define IPPOOL_ADDRESS_KEY
Definition: redis_ippool.h:58
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:1302
#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
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
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_TYPE_DATA
Value in native boxed format.
Definition: tmpl.h:142
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
RETURN_MODULE_FAIL
fr_assert(0)
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