The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
rlm_redis_ippool_tool.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: d27f9a8a2ec250049f4577de32a8dea34a2dcadb $
19  * @file rlm_redis_ippool_tool.c
20  * @brief IP population tool.
21  *
22  * @author Arran Cudbard-Bell
23  *
24  * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
25  * @copyright 2015 The FreeRADIUS server project
26  */
27 RCSID("$Id: d27f9a8a2ec250049f4577de32a8dea34a2dcadb $")
28 #include <freeradius-devel/server/cf_parse.h>
29 #include <freeradius-devel/util/debug.h>
30 
31 #include "base.h"
32 #include "cluster.h"
33 #include "redis_ippool.h"
34 
35 #define MAX_PIPELINED 100000
36 
37 /** Pool management actions
38  *
39  */
40 typedef enum ippool_tool_action {
41  IPPOOL_TOOL_NOOP = 0, //!< Do nothing.
42  IPPOOL_TOOL_ADD, //!< Add one or more IP addresses.
43  IPPOOL_TOOL_REMOVE, //!< Remove one or more IP addresses.
44  IPPOOL_TOOL_RELEASE, //!< Release one or more IP addresses.
45  IPPOOL_TOOL_SHOW, //!< Show one or more IP addresses.
46  IPPOOL_TOOL_MODIFY, //!< Modify attributes of one or more IP addresses.
47  IPPOOL_TOOL_ASSIGN, //!< Assign a static IP address to a device.
48  IPPOOL_TOOL_UNASSIGN //!< Remove static IP address assignment.
50 
51 /** A single pool operation
52  *
53  */
54 typedef struct {
55  char const *name; //!< Original range or CIDR string.
56 
57  uint8_t const *pool; //!< Pool identifier.
58  size_t pool_len; //!< Length of the pool identifier.
59 
60  uint8_t const *range; //!< Range identifier.
61  size_t range_len; //!< Length of the range identifier.
62 
63  fr_ipaddr_t start; //!< Start address.
64  fr_ipaddr_t end; //!< End address.
65  uint8_t prefix; //!< Prefix - The bits between the address mask, and the prefix
66  //!< form the addresses to be modified in the pool.
67  ippool_tool_action_t action; //!< What to do to the leases described by net/prefix.
69 
70 typedef struct {
71  fr_ipaddr_t ipaddr; //!< Prefix or address.
72  time_t next_event; //!< Last state change.
73  uint8_t const *range; //!< Range the lease belongs to.
74  size_t range_len;
75  uint8_t const *device; //!< Last device id.
76  size_t device_len;
77  uint8_t const *gateway; //!< Last gateway id.
78  size_t gateway_len;
80 
81 typedef struct {
82  uint64_t total; //!< Addresses available.
83  uint64_t free; //!< Addresses in use.
84  uint64_t expiring_1m; //!< Addresses that expire in the next minute.
85  uint64_t expiring_30m; //!< Addresses that expire in the next 30 minutes.
86  uint64_t expiring_1h; //!< Addresses that expire in the next hour.
87  uint64_t expiring_1d; //!< Addresses that expire in the next day.
88  uint64_t static_tot; //!< Static assignments configured.
89  uint64_t static_free; //!< Static leases that have not been requested.
90  uint64_t static_1m; //!< Static leases that should renew in the next minute.
91  uint64_t static_30m; //!< Static leases that should renew in the next 30 minutes.
92  uint64_t static_1h; //!< Static leases that should renew in the next hour.
93  uint64_t static_1d; //!< Static leases that should renew in the next day.
95 
99 };
100 
101 typedef struct {
102  fr_redis_conf_t conf; //!< Connection parameters for the Redis server.
105 
106 typedef struct {
107  void *driver;
109 } ippool_tool_t;
110 
111 typedef struct {
112  char const *owner;
114 
116  uint8_t const *key_prefix, size_t key_prefix_len,
117  uint8_t const *range, size_t range_len,
118  fr_ipaddr_t *ipaddr, uint8_t prefix, void *uctx);
119 
120 typedef int (*redis_ippool_process_t)(void *out, fr_ipaddr_t const *ipaddr, redisReply const *reply);
121 
122 #define IPPOOL_BUILD_IP_KEY_FROM_STR(_buff, _p, _key, _key_len, _ip_str) \
123 do { \
124  ssize_t _slen; \
125  *_p++ = '{'; \
126  memcpy(_p, _key, _key_len); \
127  _p += _key_len; \
128  _slen = strlcpy((char *)_p, "}:"IPPOOL_ADDRESS_KEY":", sizeof(_buff) - (_p - _buff)); \
129  if (is_truncated((size_t)_slen, sizeof(_buff) - (_p - _buff))) { \
130  ERROR("IP key too long"); \
131  return 0;\
132  } \
133  _p += (size_t)_slen;\
134  _p += strlcpy((char *)_p, _ip_str, sizeof(_buff) - (_p - _buff)); \
135 } while (0)
136 
137 #if 0
138 #define IPPOOL_BUILD_OWNER_KEY(_buff, _p, _key, _key_len, _owner) \
139 do { \
140  ssize_t _slen; \
141  *_p++ = '{'; \
142  memcpy(_p, _key, _key_len); \
143  _p += _key_len; \
144  _slen = strlcpy((char *)_p, "}:"IPPOOL_OWNER_KEY":", sizeof(_buff) - (_p - _buff)); \
145  if (is_truncated((size_t)_slen, sizeof(_buff) - (_p - _buff))) { \
146  ERROR("Owner key too long"); \
147  return 0;\
148  } \
149  _p += (size_t)_slen;\
150  _p += strlcpy((char *)_p, _owner, sizeof(_buff) - (_p - _buff)); \
151 } while (0)
152 #endif
153 
154 #define EOL "\n"
155 
156 static char const *name;
157 /** Lua script for releasing a lease
158  *
159  * - KEYS[1] The pool name.
160  * - ARGV[1] IP address to release.
161  *
162  * Removes the IP entry in the ZSET, then removes the address hash, and the device key
163  * if one exists.
164  *
165  * Will do nothing if the lease is not found in the ZSET.
166  *
167  * Returns
168  * - 0 if no ip addresses were removed.
169  * - 1 if an ip address was removed.
170  */
171 static char lua_release_cmd[] =
172  "local found" EOL /* 1 */
173  "local ret" EOL /* 2 */
174 
175  /*
176  * Set expiry time to 0
177  */
178  "ret = redis.call('ZADD', '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"', 'XX', 'CH', 0, ARGV[1])" EOL /* 3 */
179  "if ret == 0 then" EOL /* 4 */
180  " return 0" EOL /* 5 */
181  "end" EOL /* 6 */
182  "found = redis.call('HGET', '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":'"
183  " .. ARGV[1], 'device')" EOL /* 7 */
184  "if not found then" EOL /* 8 */
185  " return ret" EOL /* 9 */
186  "end" EOL /* 10 */
187 
188  /*
189  * Remove the association between the device and a lease
190  */
191  "redis.call('DEL', '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. found)" EOL /* 11 */
192  "return 1"; /* 12 */
193 
194 /** Lua script for assigning a static lease
195  *
196  * - KEYS[1] The pool name.
197  * - ARGV[1] THE ip address to create a static assignment for.
198  * - ARGV[2] The owner to assign the static lease to.
199  * - ARGV[3] The range identifier.
200  * - ARGV[4] Wall time (seconds since epoch)
201  *
202  * Checks whether the IP already has a static assignment, and
203  * whether the owner is already associated with a different IP.
204  *
205  * If check pass, sets the static flag on the IP entry in the ZSET and
206  * creates the association between the IP and the owner.
207  *
208  * Returns
209  * - 0 if no assignment is made.
210  * - 1 if the IP assignment is made.
211  */
212 static char lua_assign_cmd[] =
213  "local pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 1 */
214  "local owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[2]" EOL /* 2 */
215  "local ip_key = '{' .. KEYS[1]..'}:"IPPOOL_ADDRESS_KEY":' .. ARGV[1]" EOL /* 3 */
216 
217  /*
218  * Check the address doesn't already have a static assignment.
219  */
220  "local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[1]))" EOL /* 4 */
221  "if expires and expires >= " STRINGIFY(IPPOOL_STATIC_BIT) " then" EOL /* 5 */
222  " return 0" EOL /* 6 */
223  "end" EOL /* 7 */
224 
225  /*
226  * Check current assignment for device.
227  */
228  "local found = redis.call('GET', owner_key)" EOL /* 8 */
229  "if found and found ~= ARGV[1] then" EOL /* 9 */
230  " return 0" EOL /* 10 */
231  "end" EOL /* 11 */
232 
233  /*
234  * If expires is in the future, check it is not
235  * another owner.
236  */
237  "if expires and expires > tonumber(ARGV[4]) then" EOL /* 12 */
238  " found = redis.call('HGET', ip_key, 'device')" /* 13 */
239  " if found and found ~= ARGV[2] then" EOL /* 14 */
240  " return 0" EOL /* 15 */
241  " end" EOL /* 16 */
242  "end" EOL /* 17 */
243 
244  /*
245  * All checks passed - set the assignment.
246  */
247  "expires = (expires or 0) + " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 18 */
248  "redis.call('ZADD', pool_key, 'CH', expires, ARGV[1])" EOL /* 19 */
249  "redis.call('SET', owner_key, ARGV[1])" EOL /* 20 */
250  "redis.call('HSET', ip_key, 'device', ARGV[2], 'counter', 0)" EOL /* 21 */
251  "if ARGV[3] then" EOL /* 22 */
252  " redis.call('HSET', ip_key, range, ARGV[3])" EOL /* 23 */
253  "end" EOL /* 24 */
254  "return 1"; /* 25 */
255 
256 /** Lua script for un-assigning a static lease
257  *
258  * - KEYS[1] The pool name.
259  * - ARGV[1] IP address to remove static lease from.
260  * - ARGV[2] The owner the static lease should be removed from.
261  * - ARGV[3] Wall time (seconds since epoch).
262  *
263  * Removes the static flag from the IP entry in the ZSET, then, depending on the remaining time
264  * determined by the ZSCORE removes the address hash, and the device key.
265  *
266  * Will do nothing if the static assignment does not exist or the IP and device do not match.
267  *
268  * Returns
269  * - 0 if no ip addresses were unassigned.
270  * - 1 if an ip address was unassigned.
271  */
272 static char lua_unassign_cmd[] =
273  "local found" EOL /* 1 */
274  "local pool_key = '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"'" EOL /* 2 */
275  "local owner_key = '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. ARGV[2]" EOL /* 3 */
276 
277  /*
278  * Check that the device hash exists and points at the correct IP
279  */
280  "found = redis.call('GET', owner_key)" EOL /* 4 */
281  "if not found or found ~= ARGV[1] then" EOL /* 5 */
282  " return 0" EOL /* 6 */
283  "end" EOL /* 7 */
284 
285  /*
286  * Check the assignment is actually static
287  */
288  "local expires = tonumber(redis.call('ZSCORE', pool_key, ARGV[1]))" EOL /* 8 */
289  "local static = expires >= " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 9 */
290  "if not static then" EOL /* 10 */
291  " return 0" EOL /* 11 */
292  "end" EOL /* 12 */
293 
294  /*
295  * Remove static bit from ZSCORE
296  */
297  "expires = expires - " STRINGIFY(IPPOOL_STATIC_BIT) EOL /* 13 */
298  "redis.call('ZADD', pool_key, 'XX', expires, ARGV[1])" EOL /* 14 */
299 
300  /*
301  * If the lease still has time left, set an expiry on the device key.
302  * otherwise delete it.
303  */
304  "if expires > tonumber(ARGV[3]) then" EOL /* 15 */
305  " redis.call('EXPIRE', owner_key, expires - tonumber(ARGV[3]))" EOL /* 16 */
306  "else" EOL /* 17 */
307  " redis.call('DEL', owner_key)" EOL /* 18 */
308  "end" EOL /* 19 */
309  "return 1"; /* 20 */
310 
311 /** Lua script for removing a lease
312  *
313  * - KEYS[1] The pool name.
314  * - ARGV[1] IP address to remove.
315  *
316  * Removes the IP entry in the ZSET, then removes the address hash, and the device key
317  * if one exists.
318  *
319  * Will work with partially removed IP addresses (where the ZSET entry is absent but other
320  * elements weren't cleaned up).
321  *
322  * Returns
323  * - 0 if no ip addresses were removed.
324  * - 1 if an ip address was removed.
325  */
326 static char lua_remove_cmd[] =
327  "local found" EOL /* 1 */
328  "local ret" EOL /* 2 */
329  "local address_key" EOL /* 3 */
330 
331  "ret = redis.call('ZREM', '{' .. KEYS[1] .. '}:"IPPOOL_POOL_KEY"', ARGV[1])" EOL /* 4 */
332  "address_key = '{' .. KEYS[1] .. '}:"IPPOOL_ADDRESS_KEY":' .. ARGV[1]" EOL /* 5 */
333  "found = redis.call('HGET', address_key, 'device')" EOL /* 6 */
334  "redis.call('DEL', address_key)" EOL /* 7 */
335  "if not found then" EOL /* 8 */
336  " return ret" EOL /* 9 */
337  "end" EOL /* 10 */
338 
339  /*
340  * Remove the association between the device and a lease
341  */
342  "redis.call('DEL', '{' .. KEYS[1] .. '}:"IPPOOL_OWNER_KEY":' .. found)" EOL /* 11 */
343  "return 1" EOL; /* 12 */
344 
345 static NEVER_RETURNS void usage(int ret) {
346  INFO("Usage: %s -adrsm range... [-p prefix_len]... [-x]... [-oShf] server[:port] [pool] [range id]", name);
347  INFO("Pool management:");
348  INFO(" -a range Add address(es)/prefix(es) to the pool.");
349  INFO(" -d range Delete address(es)/prefix(es) in this range.");
350  INFO(" -r range Release address(es)/prefix(es) in this range.");
351  INFO(" -s range Show addresses/prefix in this range.");
352  INFO(" -A address/prefix Assign a static lease.");
353  INFO(" -O owner To use when assigning a static lease.");
354  INFO(" -U address/prefix Un-assign a static lease");
355  INFO(" -p prefix_len Length of prefix to allocate (defaults to 32/128)");
356  INFO(" This is used primarily for IPv6 where a prefix is");
357  INFO(" allocated to an intermediary router, which in turn");
358  INFO(" allocates sub-prefixes to the devices it serves.");
359  INFO(" This argument changes the prefix_len for the previous");
360  INFO(" instance of an -adrsm argument, only.");
361  INFO(" -m range Change the range id to the one specified for addresses");
362  INFO(" in this range.");
363  INFO(" -l List available pools.");
364 // INFO(" -L List available ranges in pool [NYI]");
365 // INFO(" -i file Import entries from ISC lease file [NYI]");
366  INFO(" "); /* -Werror=format-zero-length */
367 // INFO("Pool status:");
368 // INFO(" -I Output active entries in ISC lease file format [NYI]");
369  INFO(" -S Print pool statistics");
370  INFO(" "); /* -Werror=format-zero-length */
371  INFO("Configuration:");
372  INFO(" -h Print this help message and exit");
373  INFO(" -x Increase the verbosity level");
374 // INFO(" -o attr=value Set option, these are specific to the backends [NYI]");
375  INFO(" -f file Load connection options from a FreeRADIUS format config file");
376  INFO(" This file should contain a pool { ... } section and one or more");
377  INFO(" `server = <fqdn>` pairs`");
378  INFO(" ");
379  INFO("<range> is range \"127.0.0.1-127.0.0.254\" or CIDR network \"127.0.0.1/24\" or host \"127.0.0.1\"");
380  INFO("CIDR host bits set start address, e.g. 127.0.0.200/24 -> 127.0.0.200-127.0.0.254");
381  fr_exit_now(ret);
382 }
383 
385 {
386  if (bits >= 32) return 0xffffffff;
387  return (1 << bits) - 1;
388 }
389 
390 /** Iterate over range of IP addresses
391  *
392  * Mutates the ipaddr passed in, adding one to the prefix bits on each call.
393  *
394  * @param[in,out] ipaddr to increment.
395  * @param[in] end ipaddr to stop at.
396  * @param[in] prefix Length of the prefix.
397  * @return
398  * - true if the prefix bits are not high (continue).
399  * - false if the prefix bits are high (stop).
400  */
401 static bool ipaddr_next(fr_ipaddr_t *ipaddr, fr_ipaddr_t const *end, uint8_t prefix)
402 {
403  switch (ipaddr->af) {
404  default:
405  case AF_UNSPEC:
406  fr_assert(0);
407  return false;
408 
409  case AF_INET6:
410  {
411  uint128_t ip_curr, ip_end;
412 
413  if (!fr_cond_assert((prefix > 0) && (prefix <= 128))) return false;
414 
415  /* Don't be tempted to cast */
416  memcpy(&ip_curr, ipaddr->addr.v6.s6_addr, sizeof(ip_curr));
417  memcpy(&ip_end, end->addr.v6.s6_addr, sizeof(ip_curr));
418 
419  ip_curr = ntohlll(ip_curr);
420  ip_end = ntohlll(ip_end);
421 
422  /* We're done */
423  if (uint128_eq(ip_curr, ip_end)) return false;
424 
425  /* Increment the prefix */
426  ip_curr = uint128_add(ip_curr, uint128_lshift(uint128_new(0, 1), (128 - prefix)));
427  ip_curr = htonlll(ip_curr);
428  memcpy(&ipaddr->addr.v6.s6_addr, &ip_curr, sizeof(ipaddr->addr.v6.s6_addr));
429  return true;
430  }
431 
432  case AF_INET:
433  {
434  uint32_t ip_curr, ip_end;
435 
436  if (!fr_cond_assert((prefix > 0) && (prefix <= 32))) return false;
437 
438  ip_curr = ntohl(ipaddr->addr.v4.s_addr);
439  ip_end = ntohl(end->addr.v4.s_addr);
440 
441  /* We're done */
442  if (ip_curr == ip_end) return false;
443 
444  /* Increment the prefix */
445  ip_curr += 1 << (32 - prefix);
446  ipaddr->addr.v4.s_addr = htonl(ip_curr);
447  return true;
448  }
449  }
450 }
451 
452 /** Add a net to the pool
453  *
454  * @return the number of new addresses added.
455  */
456 static int driver_do_lease(void *out, void *instance, ippool_tool_operation_t const *op,
457  redis_ippool_queue_t enqueue, redis_ippool_process_t process, void *uctx)
458 {
459  redis_driver_conf_t *inst = talloc_get_type_abort(instance, redis_driver_conf_t);
460 
461  int i;
462  bool more = true;
463  fr_redis_conn_t *conn;
464 
466  fr_redis_rcode_t status;
467 
468  fr_ipaddr_t ipaddr = op->start;
470  redisReply **replies = NULL;
471 
472  unsigned int pipelined = 0;
473 
474  while (more) {
475  fr_ipaddr_t acked = ipaddr; /* Record our progress */
476  size_t reply_cnt = 0;
477 
478  for (s_ret = fr_redis_cluster_state_init(&state, &conn, inst->cluster, NULL,
479  op->pool, op->pool_len, false);
480  s_ret == REDIS_RCODE_TRY_AGAIN;
481  s_ret = fr_redis_cluster_state_next(&state, &conn, inst->cluster, NULL, status, &replies[0])) {
482  more = true; /* Reset to true, may have errored last loop */
483  status = REDIS_RCODE_SUCCESS;
484 
485  /*
486  * If we got a redirect, start back at the beginning of the block.
487  */
488  ipaddr = acked;
489 
490  for (i = 0; (i < MAX_PIPELINED) && more; i++, more = ipaddr_next(&ipaddr, &op->end,
491  op->prefix)) {
492  int enqueued;
493 
494  enqueued = enqueue(inst, conn, op->pool, op->pool_len,
495  op->range, op->range_len, &ipaddr, op->prefix, uctx);
496  if (enqueued < 0) break;
497  pipelined += enqueued;
498  }
499 
500  if (!replies) replies = talloc_zero_array(inst, redisReply *, pipelined);
501  if (!replies) return -1;
502 
503  reply_cnt = fr_redis_pipeline_result(&pipelined, &status, replies,
504  talloc_array_length(replies), conn);
505  for (i = 0; (size_t)i < reply_cnt; i++) fr_redis_reply_print(L_DBG_LVL_3,
506  replies[i], NULL, i);
507  }
508  if (s_ret != REDIS_RCODE_SUCCESS) {
509  fr_redis_pipeline_free(replies, reply_cnt);
510  talloc_free(replies);
511  return -1;
512  }
513 
514  if (process) {
515  fr_ipaddr_t to_process = acked;
516 
517  for (i = 0; (size_t)i < reply_cnt; i++) {
518  int ret;
519 
520  ret = process(out, &to_process, replies[i]);
521  if (ret < 0) continue;
522  ipaddr_next(&to_process, &op->end, op->prefix);
523  }
524  }
525  fr_redis_pipeline_free(replies, reply_cnt);
526  TALLOC_FREE(replies);
527  }
528 
529  return 0;
530 }
531 
532 /** Enqueue commands to retrieve lease information
533  *
534  */
535 static int _driver_show_lease_process(void *out, fr_ipaddr_t const *ipaddr, redisReply const *reply)
536 {
537  size_t existing;
538  ippool_tool_lease_t ***modified = out;
539  ippool_tool_lease_t *lease;
540 
541  if (!*modified) *modified = talloc_array(NULL, ippool_tool_lease_t *, 1);
542 
543  /*
544  * The exec command is the only one that produces an array.
545  */
546  if (reply->type != REDIS_REPLY_ARRAY) return -1;
547  if (reply->elements < 4) return -1;
548 
549  if (reply->element[0]->type == REDIS_REPLY_NIL) return 0; /* A nil result (IP didn't exist) */
550  if (reply->element[0]->type != REDIS_REPLY_STRING) return -1; /* Something bad */
551  lease = talloc_zero(*modified, ippool_tool_lease_t);
552  lease->ipaddr = *ipaddr;
553  lease->next_event = (time_t)strtoull(reply->element[0]->str, NULL, 10);
554 
555  if (reply->element[1]->type == REDIS_REPLY_STRING) {
556  lease->device = talloc_memdup(lease, reply->element[1]->str, reply->element[1]->len);
557  lease->device_len = reply->element[1]->len;
558  }
559  if (reply->element[2]->type == REDIS_REPLY_STRING) {
560  lease->gateway = talloc_memdup(lease, reply->element[2]->str, reply->element[2]->len);
561  lease->gateway_len = reply->element[2]->len;
562  }
563  if (reply->element[3]->type == REDIS_REPLY_STRING) {
564  lease->range = talloc_memdup(lease, reply->element[3]->str, reply->element[3]->len);
565  lease->range_len = reply->element[3]->len;
566  }
567 
568  /*
569  * Grow the result array...
570  */
571  existing = talloc_array_length(*modified);
572  MEM(*modified = talloc_realloc(NULL, *modified, ippool_tool_lease_t *, existing + 1));
573  (*modified)[existing - 1] = lease;
574 
575  return 0;
576 }
577 
578 /** Enqueue commands to retrieve lease information
579  *
580  */
582  uint8_t const *key_prefix, size_t key_prefix_len,
583  UNUSED uint8_t const *range, UNUSED size_t range_len,
584  fr_ipaddr_t *ipaddr, uint8_t prefix, UNUSED void *uctx)
585 {
587  uint8_t *key_p = key;
588  char ip_buff[FR_IPADDR_PREFIX_STRLEN];
589 
591  uint8_t *ip_key_p = ip_key;
592 
593  IPPOOL_BUILD_KEY(key, key_p, key_prefix, key_prefix_len);
594  IPPOOL_SPRINT_IP(ip_buff, ipaddr, prefix);
595  IPPOOL_BUILD_IP_KEY_FROM_STR(ip_key, ip_key_p, key_prefix, key_prefix_len, ip_buff);
596 
597  DEBUG("Retrieving lease info for %s from pool %pV", ip_buff,
598  fr_box_strvalue_len((char const *)key_prefix, key_prefix_len));
599 
600  redisAppendCommand(conn->handle, "MULTI");
601  redisAppendCommand(conn->handle, "ZSCORE %b %s", key, key_p - key, ip_buff);
602  redisAppendCommand(conn->handle, "HGET %b device", ip_key, ip_key_p - ip_key);
603  redisAppendCommand(conn->handle, "HGET %b gateway", ip_key, ip_key_p - ip_key);
604  redisAppendCommand(conn->handle, "HGET %b range", ip_key, ip_key_p - ip_key);
605  redisAppendCommand(conn->handle, "EXEC");
606  return 6;
607 }
608 
609 /** Show information about leases
610  *
611  */
612 static inline int driver_show_lease(void *out, void *instance, ippool_tool_operation_t const *op)
613 {
615 }
616 
617 /** Count the number of leases we released
618  *
619  */
620 static int _driver_release_lease_process(void *out, UNUSED fr_ipaddr_t const *ipaddr, redisReply const *reply)
621 {
622  uint64_t *modified = out;
623  /*
624  * Record the actual number of addresses released.
625  * Leases with a score of zero shouldn't be included,
626  * in this count.
627  */
628  if (reply->type != REDIS_REPLY_INTEGER) return -1;
629 
630  *modified += reply->integer;
631 
632  return 0;
633 }
634 
635 /** Release a lease by setting its score back to zero
636  *
637  */
639  uint8_t const *key_prefix, size_t key_prefix_len,
640  UNUSED uint8_t const *range, UNUSED size_t range_len,
641  fr_ipaddr_t *ipaddr, uint8_t prefix, UNUSED void *uctx)
642 {
643  char ip_buff[FR_IPADDR_PREFIX_STRLEN];
644 
645  IPPOOL_SPRINT_IP(ip_buff, ipaddr, prefix);
646 
647  DEBUG("Releasing %pV to pool \"%pV\"", ip_buff,
648  fr_box_strvalue_len((char const *)key_prefix, key_prefix_len));
649  redisAppendCommand(conn->handle, "EVAL %s 1 %b %s", lua_release_cmd, key_prefix, key_prefix_len, ip_buff);
650  return 1;
651 }
652 
653 /** Release a range of leases
654  *
655  */
656 static inline int driver_release_lease(void *out, void *instance, ippool_tool_operation_t const *op)
657 {
658  return driver_do_lease(out, instance, op,
660 }
661 
662 /** Count the number of leases we removed
663  *
664  * Because the ZREM and DEL have to occur in a transaction, we need
665  * some fancier processing to just count the number of ZREMs.
666  */
667 static int _driver_remove_lease_process(void *out, UNUSED fr_ipaddr_t const *ipaddr, redisReply const *reply)
668 {
669  uint64_t *modified = out;
670  /*
671  * Record the actual number of addresses released.
672  * Leases with a score of zero shouldn't be included,
673  * in this count.
674  */
675  if (reply->type != REDIS_REPLY_INTEGER) return -1;
676 
677  *modified += reply->integer;
678 
679  return 0;
680 }
681 
682 /** Enqueue lease removal commands
683  *
684  * This removes the lease from the expiry heap, and the data associated with
685  * the lease.
686  */
688  uint8_t const *key_prefix, size_t key_prefix_len,
689  UNUSED uint8_t const *range, UNUSED size_t range_len,
690  fr_ipaddr_t *ipaddr, uint8_t prefix, UNUSED void *uctx)
691 {
692  char ip_buff[FR_IPADDR_PREFIX_STRLEN];
693 
694  IPPOOL_SPRINT_IP(ip_buff, ipaddr, prefix);
695 
696  DEBUG("Removing %s from pool \"%pV\"", ip_buff,
697  fr_box_strvalue_len((char const *)key_prefix, key_prefix_len));
698  redisAppendCommand(conn->handle, "EVAL %s 1 %b %s", lua_remove_cmd, key_prefix, key_prefix_len, ip_buff);
699  return 1;
700 }
701 
702 /** Remove a range of leases
703  *
704  */
705 static int driver_remove_lease(void *out, void *instance, ippool_tool_operation_t const *op)
706 {
707  return driver_do_lease(out, instance, op,
709 }
710 
711 /** Count the number of leases we actually added
712  *
713  * This isn't necessarily the same as the number of ZADDs, as leases may
714  * already exist.
715  */
716 static int _driver_add_lease_process(void *out, UNUSED fr_ipaddr_t const *ipaddr, redisReply const *reply)
717 {
718  uint64_t *modified = out;
719  /*
720  * Record the actual number of addresses modified.
721  * Existing addresses won't be included in this
722  * count.
723  */
724  if (reply->type != REDIS_REPLY_ARRAY) return -1;
725 
726  if ((reply->elements > 0) && (reply->element[0]->type == REDIS_REPLY_INTEGER)) {
727  *modified += reply->element[0]->integer;
728  }
729  return 0;
730 }
731 
732 /** Enqueue lease addition commands
733  *
734  */
736  uint8_t const *key_prefix, size_t key_prefix_len,
737  uint8_t const *range, size_t range_len,
738  fr_ipaddr_t *ipaddr, uint8_t prefix, UNUSED void *uctx)
739 {
741  uint8_t *key_p = key;
742  char ip_buff[FR_IPADDR_PREFIX_STRLEN];
743 
745  uint8_t *ip_key_p = ip_key;
746 
747  int enqueued = 0;
748 
749  IPPOOL_BUILD_KEY(key, key_p, key_prefix, key_prefix_len);
750  IPPOOL_SPRINT_IP(ip_buff, ipaddr, prefix);
751  IPPOOL_BUILD_IP_KEY_FROM_STR(ip_key, ip_key_p, key_prefix, key_prefix_len, ip_buff);
752 
753  DEBUG("Adding %s to pool \"%pV\" (%zu)", ip_buff, fr_box_strvalue_len((char *)key, (key_p - key)), key_p - key);
754  redisAppendCommand(conn->handle, "MULTI");
755  enqueued++;
756  redisAppendCommand(conn->handle, "ZADD %b NX %u %s", key, key_p - key, 0, ip_buff);
757  enqueued++;
758 
759  /*
760  * Only add range if it's not NULL.
761  *
762  * Zero length ranges are allowed, and should be preserved.
763  */
764  if (range) {
765  redisAppendCommand(conn->handle, "HSET %b range %b", ip_key, ip_key_p - ip_key, range, range_len);
766  enqueued++;
767  }
768  redisAppendCommand(conn->handle, "EXEC");
769  enqueued++;
770 
771  return enqueued;
772 }
773 
774 /** Add a range of prefixes
775  *
776  */
777 static int driver_add_lease(void *out, void *instance, ippool_tool_operation_t const *op)
778 {
780 }
781 
782 /** Count the number of leases we modified
783  */
784 static int _driver_modify_lease_process(void *out, UNUSED fr_ipaddr_t const *ipaddr, redisReply const *reply)
785 {
786  uint64_t *modified = out;
787  /*
788  * Record the actual number of addresses released.
789  * Leases with a score of zero shouldn't be included,
790  * in this count.
791  */
792  if (reply->type != REDIS_REPLY_INTEGER) return -1;
793 
794  /*
795  * return code is 0 or 1 depending on if its a new
796  * field, neither are useful
797  */
798  *modified += 1;
799 
800  return 0;
801 }
802 
803 /** Enqueue lease removal commands
804  *
805  * This modifys the lease from the expiry heap, and the data associated with
806  * the lease.
807  */
809  uint8_t const *key_prefix, size_t key_prefix_len,
810  uint8_t const *range, size_t range_len,
811  fr_ipaddr_t *ipaddr, uint8_t prefix, UNUSED void *uctx)
812 {
814  uint8_t *key_p = key;
815  char ip_buff[FR_IPADDR_PREFIX_STRLEN];
816 
818  uint8_t *ip_key_p = ip_key;
819 
820  IPPOOL_BUILD_KEY(key, key_p, key_prefix, key_prefix_len);
821  IPPOOL_SPRINT_IP(ip_buff, ipaddr, prefix);
822  IPPOOL_BUILD_IP_KEY_FROM_STR(ip_key, ip_key_p, key_prefix, key_prefix_len, ip_buff);
823 
824  DEBUG("Modifying %s in pool \"%pV\"", ip_buff, fr_box_strvalue_len((char const *)key_prefix, key_prefix_len));
825  redisAppendCommand(conn->handle, "HSET %b range %b", ip_key, ip_key_p - ip_key, range, range_len);
826 
827  return 1;
828 }
829 
830 /** Remove a range of leases
831  *
832  */
833 static int driver_modify_lease(void *out, void *instance, ippool_tool_operation_t const *op)
834 {
835  return driver_do_lease(out, instance, op,
837 }
838 
839 static int _driver_assign_lease_process(void *out, UNUSED fr_ipaddr_t const *ipaddr, redisReply const *reply)
840 {
841  uint64_t *modified = out;
842  if (reply->type != REDIS_REPLY_INTEGER) return -1;
843 
844  *modified += reply->integer;
845  return 0;
846 }
847 
848 /** Enqueue static lease assignment commands
849  *
850  */
852  uint8_t const *key_prefix, size_t key_prefix_len,
853  uint8_t const *range, size_t range_len,
854  fr_ipaddr_t *ipaddr, uint8_t prefix, void *uctx)
855 {
856  ippool_tool_owner_t *owner = uctx;
857  char ip_buff[FR_IPADDR_PREFIX_STRLEN];
858  fr_time_t now;
859 
860  IPPOOL_SPRINT_IP(ip_buff, ipaddr, prefix);
861  now = fr_time();
862 
863  DEBUG("Assigning address %s to owner %s", ip_buff, owner->owner);
864 
865  redisAppendCommand(conn->handle, "EVAL %s 1 %b %s %b %b %i", lua_assign_cmd, key_prefix, key_prefix_len,
866  ip_buff, owner->owner, strlen(owner->owner), range, range_len, fr_time_to_sec(now));
867  return 1;
868 }
869 
870 /** Add static lease assignments
871  *
872  */
873 static int driver_assign_lease(void *out, void *instance, ippool_tool_operation_t const *op, char const *owner)
874 {
875  return driver_do_lease(out, instance, op,
877  &(ippool_tool_owner_t){ .owner = owner });
878 }
879 
880 static int _driver_unassign_lease_process(void *out, UNUSED fr_ipaddr_t const *ipaddr, redisReply const *reply)
881 {
882  uint64_t *modified = out;
883  /*
884  * Record the actual number of addresses unassigned.
885  */
886  if (reply->type != REDIS_REPLY_INTEGER) return -1;
887 
888  *modified += reply->integer;
889 
890  return 0;
891 }
892 
893 /** Enqueue static lease un-assignment commands
894  *
895  */
897  uint8_t const *key_prefix, size_t key_prefix_len,
898  UNUSED uint8_t const *range, UNUSED size_t range_len,
899  fr_ipaddr_t *ipaddr, uint8_t prefix, void *uctx)
900 {
901  ippool_tool_owner_t *owner = uctx;
902  char ip_buff[FR_IPADDR_PREFIX_STRLEN];
903  fr_time_t now;
904 
905  IPPOOL_SPRINT_IP(ip_buff, ipaddr, prefix);
906  now = fr_time();
907 
908  DEBUG("Un-assigning address %s from owner %s", ip_buff, owner->owner);
909  redisAppendCommand(conn->handle, "EVAL %s 1 %b %s %b %i", lua_unassign_cmd, key_prefix, key_prefix_len,
910  ip_buff, owner->owner, strlen(owner->owner), fr_time_to_sec(now));
911  return 1;
912 }
913 
914 /** Unassign static lease assignments
915  *
916  */
917 static int driver_unassign_lease(void *out, void *instance, ippool_tool_operation_t const *op, char const *owner)
918 {
919  return driver_do_lease(out, instance, op,
921  &(ippool_tool_owner_t){ .owner = owner });
922 }
923 
924 /** Compare two pool names
925  *
926  */
927 static int8_t pool_cmp(void const *a, void const *b)
928 {
929  size_t len_a;
930  size_t len_b;
931  int ret;
932 
933  len_a = talloc_array_length((uint8_t const *)a);
934  len_b = talloc_array_length((uint8_t const *)b);
935 
936  ret = CMP(len_a, len_b);
937  if (ret != 0) return ret;
938 
939  ret = memcmp(a, b, len_a);
940  return CMP(ret, 0);
941 }
942 
943 /** Return the pools available across the cluster
944  *
945  * @param[in] ctx to allocate range names in.
946  * @param[out] out Array of pool names.
947  * @param[in] instance Driver specific instance data.
948  * @return
949  * - < 0 on failure.
950  * - >= 0 the number of ranges in the array we allocated.
951  */
952 static ssize_t driver_get_pools(TALLOC_CTX *ctx, uint8_t **out[], void *instance)
953 {
954  fr_socket_t *master;
955  size_t k;
956  ssize_t ret, i, used = 0;
957  fr_redis_conn_t *conn = NULL;
958  redis_driver_conf_t *inst = talloc_get_type_abort(instance, redis_driver_conf_t);
960  uint8_t *key_p = key;
961  uint8_t **result;
962 
963  IPPOOL_BUILD_KEY(key, key_p, "*}:"IPPOOL_POOL_KEY, 1);
964 
965  *out = NULL; /* Initialise output pointer */
966 
967  /*
968  * Get the addresses of all masters in the pool
969  */
970  ret = fr_redis_cluster_node_addr_by_role(ctx, &master, inst->cluster, true, false);
971  if (ret <= 0) {
972  result = NULL;
973  return ret;
974  }
975 
976  result = talloc_zero_array(ctx, uint8_t *, 1);
977  if (!result) {
978  ERROR("Failed allocating array of pool names");
979  talloc_free(master);
980  return -1;
981  }
982 
983  /*
984  * Iterate over the masters, getting the pools on each
985  */
986  for (i = 0; i < ret; i++) {
987  fr_pool_t *pool;
988  redisReply *reply;
989  char const *p;
990  size_t len;
991  char cursor[19] = "0";
992 
993  if (fr_redis_cluster_pool_by_node_addr(&pool, inst->cluster, &master[i], false) < 0) {
994  ERROR("Failed retrieving pool for node");
995  error:
996  TALLOC_FREE(result);
997  talloc_free(master);
998  return -1;
999  }
1000 
1001  conn = fr_pool_connection_get(pool, NULL);
1002  if (!conn) goto error;
1003  do {
1004  /*
1005  * Break up the scan so we don't block any single
1006  * Redis node too long.
1007  */
1008  reply = redisCommand(conn->handle, "SCAN %s MATCH %b COUNT 20", cursor, key, key_p - key);
1009  if (!reply) {
1010  ERROR("Failed reading reply");
1011  fr_pool_connection_release(pool, NULL, conn);
1012  goto error;
1013  }
1014  fr_redis_reply_print(L_DBG_LVL_3, reply, NULL, 0);
1015  if (fr_redis_command_status(conn, reply) != REDIS_RCODE_SUCCESS) {
1016  PERROR("Error retrieving keys %s", cursor);
1017 
1018  reply_error:
1019  fr_pool_connection_release(pool, NULL, conn);
1020  fr_redis_reply_free(&reply);
1021  goto error;
1022  }
1023 
1024  if (reply->type != REDIS_REPLY_ARRAY) {
1025  ERROR("Failed retrieving result, expected array got %s",
1026  fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
1027 
1028  goto reply_error;
1029  }
1030 
1031  if (reply->elements != 2) {
1032  ERROR("Failed retrieving result, expected array with two elements, got %zu elements",
1033  reply->elements);
1034  fr_redis_reply_free(&reply);
1035  goto reply_error;
1036  }
1037 
1038  if (reply->element[0]->type != REDIS_REPLY_STRING) {
1039  ERROR("Failed retrieving result, expected string got %s",
1040  fr_table_str_by_value(redis_reply_types, reply->element[0]->type, "<UNKNOWN>"));
1041  goto reply_error;
1042  }
1043 
1044  if (reply->element[1]->type != REDIS_REPLY_ARRAY) {
1045  ERROR("Failed retrieving result, expected array got %s",
1046  fr_table_str_by_value(redis_reply_types, reply->element[1]->type, "<UNKNOWN>"));
1047  goto reply_error;
1048  }
1049 
1050  if ((talloc_array_length(result) - used) < reply->element[1]->elements) {
1051  MEM(result = talloc_realloc(ctx, result, uint8_t *,
1052  used + reply->element[1]->elements));
1053  if (!result) {
1054  ERROR("Failed expanding array of pool names");
1055  goto reply_error;
1056  }
1057  }
1058  strlcpy(cursor, reply->element[0]->str, sizeof(cursor));
1059 
1060  for (k = 0; k < reply->element[1]->elements; k++) {
1061  redisReply *pool_key = reply->element[1]->element[k];
1062 
1063  /*
1064  * Skip over things which are not pool names
1065  */
1066  if (pool_key->len < 7) continue; /* { + [<name>] + }:pool */
1067 
1068  if ((pool_key->str[0]) != '{') continue;
1069  p = memchr(pool_key->str + 1, '}', pool_key->len - 1);
1070  if (!p) continue;
1071 
1072  len = (pool_key->len - ((p + 1) - pool_key->str));
1073  if (len != (sizeof(IPPOOL_POOL_KEY) - 1) + 1) continue;
1074  if (memcmp(p + 1, ":" IPPOOL_POOL_KEY, (sizeof(IPPOOL_POOL_KEY) - 1) + 1) != 0) {
1075  continue;
1076  }
1077 
1078  /*
1079  * String between the curly braces is the pool name
1080  */
1081  result[used++] = talloc_memdup(result, pool_key->str + 1, (p - pool_key->str) - 1);
1082  }
1083 
1084  fr_redis_reply_free(&reply);
1085  } while (!((cursor[0] == '0') && (cursor[1] == '\0'))); /* Cursor value of 0 means no more results */
1086 
1087  fr_pool_connection_release(pool, NULL, conn);
1088  }
1089 
1090  if (used == 0) {
1091  *out = NULL;
1092  talloc_free(result);
1093  return 0;
1094  }
1095 
1096  /*
1097  * Sort the results
1098  */
1099  if (used > 1) {
1100  uint8_t const **to_sort;
1101 
1102  memcpy(&to_sort, &result, sizeof(to_sort));
1103 
1104  fr_quick_sort((void const **)to_sort, 0, used - 1, pool_cmp);
1105  }
1106 
1107  *out = talloc_array(ctx, uint8_t *, used);
1108  if (!*out) {
1109  ERROR("Failed allocating file pool name array");
1110  talloc_free(result);
1111  return -1;
1112  }
1113 
1114  /*
1115  * SCAN can produce duplicates, remove them here
1116  */
1117  i = 0;
1118  k = 0;
1119  do { /* stop before last entry */
1120  (*out)[k++] = talloc_steal(*out, result[i++]);
1121  while ((i < used) && (pool_cmp(result[i - 1], result[i]) == 0)) i++;
1122  } while (i < used);
1123 
1124  talloc_free(result);
1125 
1126  return used;
1127 }
1128 
1129 static int driver_get_stats(ippool_tool_stats_t *out, void *instance, uint8_t const *key_prefix, size_t key_prefix_len)
1130 {
1131  redis_driver_conf_t *inst = talloc_get_type_abort(instance, redis_driver_conf_t);
1133  uint8_t *key_p = key;
1134 
1135  fr_redis_conn_t *conn;
1136 
1138  fr_redis_rcode_t status;
1139  fr_time_t now;
1140 
1141  int s_ret = REDIS_RCODE_SUCCESS;
1142  redisReply **replies = NULL, *reply;
1143  unsigned int pipelined = 0; /* Update if additional commands added */
1144 
1145  size_t reply_cnt = 0, i = 0;
1146 
1147 #define STATS_COMMANDS_TOTAL 14
1148 
1149  IPPOOL_BUILD_KEY(key, key_p, key_prefix, key_prefix_len);
1150 
1151  MEM(replies = talloc_zero_array(inst, redisReply *, STATS_COMMANDS_TOTAL));
1152 
1153  now = fr_time();
1154 
1155  for (s_ret = fr_redis_cluster_state_init(&state, &conn, inst->cluster, NULL, key, key_p - key, false);
1156  s_ret == REDIS_RCODE_TRY_AGAIN;
1157  s_ret = fr_redis_cluster_state_next(&state, &conn, inst->cluster, NULL, status, &replies[0])) {
1158  status = REDIS_RCODE_SUCCESS;
1159 
1160  redisAppendCommand(conn->handle, "MULTI");
1161  redisAppendCommand(conn->handle, "ZCARD %b", key, key_p - key); /* Total */
1162  redisAppendCommand(conn->handle, "ZCOUNT %b -inf %i",
1163  key, key_p - key, fr_time_to_sec(now)); /* Free */
1164  redisAppendCommand(conn->handle, "ZCOUNT %b -inf %i",
1165  key, key_p - key, fr_time_to_sec(now) + 60); /* Free in next 60s */
1166  redisAppendCommand(conn->handle, "ZCOUNT %b -inf %i",
1167  key, key_p - key, fr_time_to_sec(now) + (60 * 30)); /* Free in next 30 mins */
1168  redisAppendCommand(conn->handle, "ZCOUNT %b -inf %i",
1169  key, key_p - key, fr_time_to_sec(now) + (60 * 60)); /* Free in next 60 mins */
1170  redisAppendCommand(conn->handle, "ZCOUNT %b -inf %i",
1171  key, key_p - key, fr_time_to_sec(now) + (60 * 60 * 24)); /* Free in next day */
1172  redisAppendCommand(conn->handle, "ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " inf",
1173  key, key_p - key); /* Total static */
1174  redisAppendCommand(conn->handle, "ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
1175  key, key_p - key, IPPOOL_STATIC_BIT + fr_time_to_sec(now)); /* Static assignments 'free' */
1176  redisAppendCommand(conn->handle, "ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
1177  key, key_p - key,
1178  IPPOOL_STATIC_BIT + fr_time_to_sec(now) + 60); /* Static renew in 60s */
1179  redisAppendCommand(conn->handle, "ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
1180  key, key_p - key,
1181  IPPOOL_STATIC_BIT + fr_time_to_sec(now) + (60 * 30)); /* Static renew in 30 mins */
1182  redisAppendCommand(conn->handle, "ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
1183  key, key_p - key,
1184  IPPOOL_STATIC_BIT + fr_time_to_sec(now) + (60 * 60)); /* Static renew in 60 mins */
1185  redisAppendCommand(conn->handle, "ZCOUNT %b " STRINGIFY(IPPOOL_STATIC_BIT) " %"PRIu64,
1186  key, key_p - key,
1187  IPPOOL_STATIC_BIT + fr_time_to_sec(now) + (60 * 60 * 24)); /* Static renew in 1 day */
1188  redisAppendCommand(conn->handle, "EXEC");
1189  if (!replies) return -1;
1190 
1191  pipelined = STATS_COMMANDS_TOTAL;
1192  reply_cnt = fr_redis_pipeline_result(&pipelined, &status, replies,
1193  talloc_array_length(replies), conn);
1194  for (i = 0; (size_t)i < reply_cnt; i++) fr_redis_reply_print(L_DBG_LVL_3,
1195  replies[i], NULL, i);
1196  }
1197  if (s_ret != REDIS_RCODE_SUCCESS) {
1198  error:
1199  fr_redis_pipeline_free(replies, reply_cnt);
1200  talloc_free(replies);
1201  return -1;
1202  }
1203 
1204  if (reply_cnt != STATS_COMMANDS_TOTAL) {
1205  ERROR("Failed retrieving pool stats: Expected %i replies, got %zu", pipelined, reply_cnt);
1206  goto error;
1207  }
1208 
1209  reply = replies[reply_cnt - 1];
1210 
1211  if (reply->type != REDIS_REPLY_ARRAY) {
1212  ERROR("Failed retrieving pool stats: Expected array got %s",
1213  fr_table_str_by_value(redis_reply_types, reply->element[1]->type, "<UNKNOWN>"));
1214  goto error;
1215  }
1216 
1217  if (reply->elements != (reply_cnt - 2)) {
1218  ERROR("Failed retrieving pool stats: Expected %zu results, got %zu", reply_cnt - 2, reply->elements);
1219  goto error;
1220  }
1221 
1222  if (reply->element[0]->integer == 0) {
1223  ERROR("Pool not found");
1224  goto error;
1225  }
1226 
1227  out->total = reply->element[0]->integer;
1228  out->free = reply->element[1]->integer;
1229  out->expiring_1m = reply->element[2]->integer - out->free;
1230  out->expiring_30m = reply->element[3]->integer - out->free;
1231  out->expiring_1h = reply->element[4]->integer - out->free;
1232  out->expiring_1d = reply->element[5]->integer - out->free;
1233  out->static_tot = reply->element[6]->integer;
1234  out->static_free = reply->element[7]->integer;
1235  out->static_1m = reply->element[8]->integer - out->static_free;
1236  out->static_30m = reply->element[9]->integer - out->static_free;
1237  out->static_1h = reply->element[10]->integer - out->static_free;
1238  out->static_1d = reply->element[11]->integer - out->static_free;
1239 
1240  fr_redis_pipeline_free(replies, reply_cnt);
1241  talloc_free(replies);
1242 
1243  return 0;
1244 }
1245 
1246 /** Driver initialization function
1247  *
1248  */
1249 static int driver_init(TALLOC_CTX *ctx, CONF_SECTION *conf, void **instance)
1250 {
1251  redis_driver_conf_t *this;
1252  int ret;
1253 
1254  *instance = NULL;
1255 
1256  if (cf_section_rules_push(conf, redis_config) < 0) return -1;
1257 
1258  this = talloc_zero(ctx, redis_driver_conf_t);
1259  if (!this) return -1;
1260 
1261  ret = cf_section_parse(this, &this->conf, conf);
1262  if (ret < 0) {
1263  talloc_free(this);
1264  return -1;
1265  }
1266 
1267  this->cluster = fr_redis_cluster_alloc(this, conf, &this->conf, false,
1268  "rlm_redis_ippool_tool", NULL, NULL);
1269  if (!this->cluster) {
1270  talloc_free(this);
1271  return -1;
1272  }
1273  *instance = this;
1274 
1275  return 0;
1276 }
1277 
1278 /** Convert an IP range or CIDR mask to a start and stop address
1279  *
1280  * @param[out] start_out Where to write the start address.
1281  * @param[out] end_out Where to write the end address.
1282  * @param[in] ip_str Unparsed IP string.
1283  * @param[in] prefix length of prefixes we'll be allocating.
1284  * @return
1285  * - 0 on success.
1286  * - -1 on failure.
1287  */
1288 static int parse_ip_range(fr_ipaddr_t *start_out, fr_ipaddr_t *end_out, char const *ip_str, uint8_t prefix)
1289 {
1290  fr_ipaddr_t start, end;
1291  bool ex_broadcast;
1292  char const *p;
1293 
1294  p = strchr(ip_str, '-');
1295  if (p) {
1296  char start_buff[INET6_ADDRSTRLEN + 4];
1297  char end_buff[INET6_ADDRSTRLEN + 4];
1298  size_t len;
1299 
1300  if ((size_t)(p - ip_str) >= sizeof(start_buff)) {
1301  ERROR("Start address too long");
1302  return -1;
1303  }
1304 
1305  len = strlcpy(start_buff, ip_str, (p - ip_str) + 1);
1306  if (is_truncated(len, sizeof(start_buff))) {
1307  ERROR("Start address too long");
1308  return -1;
1309  }
1310 
1311  len = strlcpy(end_buff, p + 1, sizeof(end_buff));
1312  if (is_truncated(len, sizeof(end_buff))) {
1313  ERROR("End address too long");
1314  return -1;
1315  }
1316 
1317  if (fr_inet_pton(&start, start_buff, -1, AF_UNSPEC, false, true) < 0) {
1318  PERROR("Failed parsing \"%s\" as start address", start_buff);
1319  return -1;
1320  }
1321 
1322  if (fr_inet_pton(&end, end_buff, -1, AF_UNSPEC, false, true) < 0) {
1323  PERROR("Failed parsing \"%s\" end address", end_buff);
1324  return -1;
1325  }
1326 
1327  if (start.af != end.af) {
1328  ERROR("Start and end address must be of the same address family");
1329  return -1;
1330  }
1331 
1332  if (!prefix) prefix = IPADDR_LEN(start.af);
1333 
1334  /*
1335  * IPv6 addresses
1336  */
1337  if (start.af == AF_INET6) {
1338  uint128_t start_int, end_int;
1339 
1340  memcpy(&start_int, start.addr.v6.s6_addr, sizeof(start_int));
1341  memcpy(&end_int, end.addr.v6.s6_addr, sizeof(end_int));
1342  if (uint128_gt(ntohlll(start_int), ntohlll(end_int))) {
1343  ERROR("End address must be greater than or equal to start address");
1344  return -1;
1345  }
1346  /*
1347  * IPv4 addresses
1348  */
1349  } else {
1350  if (ntohl((uint32_t)(start.addr.v4.s_addr)) >
1351  ntohl((uint32_t)(end.addr.v4.s_addr))) {
1352  ERROR("End address must be greater than or equal to start address");
1353  return -1;
1354  }
1355  }
1356 
1357  /*
1358  * Mask start and end so we can do prefix ranges too
1359  */
1360  fr_ipaddr_mask(&start, prefix);
1361  fr_ipaddr_mask(&end, prefix);
1362  start.prefix = prefix;
1363  end.prefix = prefix;
1364 
1365  *start_out = start;
1366  *end_out = end;
1367 
1368  return 0;
1369  }
1370 
1371  if (fr_inet_pton(&start, ip_str, -1, AF_UNSPEC, false, false) < 0) {
1372  ERROR("Failed parsing \"%s\" as IPv4/v6 subnet", ip_str);
1373  return -1;
1374  }
1375 
1376  if (!prefix) prefix = IPADDR_LEN(start.af);
1377 
1378  if (prefix < start.prefix) {
1379  ERROR("-p must be greater than or equal to /<mask> (%u)", start.prefix);
1380  return -1;
1381  }
1382  if (prefix > IPADDR_LEN(start.af)) {
1383  ERROR("-p must be less than or equal to address length (%u)", IPADDR_LEN(start.af));
1384  return -1;
1385  }
1386 
1387  if ((prefix - start.prefix) > 64) {
1388  ERROR("-p must be less than or equal to %u", start.prefix + 64);
1389  return -1;
1390  }
1391 
1392  /*
1393  * Exclude the broadcast address only if we're dealing with IPv4 addresses
1394  * if we're allocating IPv6 addresses or prefixes we don't need to.
1395  */
1396  ex_broadcast = (start.af == AF_INET) && (IPADDR_LEN(start.af) == prefix);
1397 
1398  /*
1399  * Excluding broadcast, 31/32 or 127/128 start/end are the same
1400  */
1401  if (ex_broadcast && (start.prefix >= (IPADDR_LEN(start.af) - 1))) {
1402  *start_out = start;
1403  *end_out = start;
1404  return 0;
1405  }
1406 
1407  /*
1408  * Set various fields (we only overwrite the IP later)
1409  */
1410  end = start;
1411 
1412  if (start.af == AF_INET6) {
1413  uint128_t ip, p_mask;
1414 
1415  /* cond assert to satisfy clang scan */
1416  if (!fr_cond_assert((prefix > 0) && (prefix <= 128))) return -1;
1417 
1418  /* Don't be tempted to cast */
1419  memcpy(&ip, start.addr.v6.s6_addr, sizeof(ip));
1420  ip = ntohlll(ip);
1421 
1422  /* Generate a mask that covers the prefix bits, and sets them high */
1423  p_mask = uint128_lshift(uint128_gen_mask(prefix - start.prefix), (128 - prefix));
1424  ip = htonlll(uint128_bor(p_mask, ip));
1425 
1426  /* Decrement by one */
1427  if (ex_broadcast) ip = uint128_sub(ip, uint128_new(0, 1));
1428  memcpy(&end.addr.v6.s6_addr, &ip, sizeof(end.addr.v6.s6_addr));
1429  } else {
1430  uint32_t ip;
1431 
1432  /* cond assert to satisfy clang scan */
1433  if (!fr_cond_assert((prefix > 0) && (prefix <= 32))) return -1;
1434 
1435  ip = ntohl(start.addr.v4.s_addr);
1436 
1437  /* Generate a mask that covers the prefix bits and sets them high */
1438  ip |= uint32_gen_mask(prefix - start.prefix) << (32 - prefix);
1439 
1440  /* Decrement by one */
1441  if (ex_broadcast) ip--;
1442  end.addr.v4.s_addr = htonl(ip);
1443  }
1444 
1445  *start_out = start;
1446  *end_out = end;
1447 
1448  return 0;
1449 }
1450 
1451 int main(int argc, char *argv[])
1452 {
1453  static ippool_tool_operation_t ops[128];
1454  ippool_tool_operation_t *p = ops, *end = ops + (NUM_ELEMENTS(ops));
1455 
1456  int c;
1457 
1458  uint8_t *range_arg = NULL;
1459  uint8_t *pool_arg = NULL;
1460  bool do_export = false, print_stats = false, list_pools = false;
1461  bool need_pool = false;
1462  char *do_import = NULL;
1463  char const *filename = NULL;
1464  char const *owner = NULL;
1465 
1466  CONF_SECTION *pool_cs;
1467  CONF_PAIR *cp;
1469 
1470  fr_debug_lvl = 0;
1471  name = argv[0];
1472 
1473  conf = talloc_zero(NULL, ippool_tool_t);
1474  conf->cs = cf_section_alloc(conf, NULL, "main", NULL);
1475  if (!conf->cs) fr_exit_now(EXIT_FAILURE);
1476 
1477 #define ADD_ACTION(_action) \
1478 do { \
1479  if (p >= end) { \
1480  ERROR("Too many actions, max is " STRINGIFY(sizeof(ops))); \
1481  usage(64); \
1482  } \
1483  p->action = _action; \
1484  p->name = optarg; \
1485  p++; \
1486  need_pool = true; \
1487 } while (0)
1488 
1489  while ((c = getopt(argc, argv, "a:d:r:s:Sm:A:U:O:p:ilLhxo:f:")) != -1) switch (c) {
1490  case 'a':
1492  break;
1493 
1494  case 'd':
1496  break;
1497 
1498  case 'r':
1500  break;
1501 
1502  case 's':
1504  break;
1505 
1506  case 'm':
1508  break;
1509 
1510  case 'A':
1512  break;
1513 
1514  case 'U':
1516  break;
1517 
1518  case 'O':
1519  owner = optarg;
1520  break;
1521 
1522  case 'p':
1523  {
1524  unsigned long tmp;
1525  char *q;
1526 
1527  if (p == ops) {
1528  ERROR("Prefix may only be specified after a pool management action");
1529  usage(64);
1530  }
1531 
1532  tmp = strtoul(optarg, &q, 10);
1533  if (q != (optarg + strlen(optarg))) {
1534  ERROR("Prefix must be an integer value");
1535 
1536  }
1537 
1538  (p - 1)->prefix = (uint8_t)tmp & 0xff;
1539  }
1540  break;
1541 
1542  case 'i':
1543  do_import = optarg;
1544  break;
1545 
1546  case 'I':
1547  do_export = true;
1548  break;
1549 
1550  case 'l':
1551  if (list_pools) usage(1); /* Only allowed once */
1552  list_pools = true;
1553  break;
1554 
1555  case 'S':
1556  print_stats = true;
1557  break;
1558 
1559  case 'h':
1560  usage(0);
1561 
1562  case 'x':
1563  fr_debug_lvl++;
1564  break;
1565 
1566  case 'o':
1567  break;
1568 
1569  case 'f':
1570  filename = optarg;
1571  break;
1572 
1573  default:
1574  usage(1);
1575  }
1576  argc -= optind;
1577  argv += optind;
1578 
1579  if (argc == 0) {
1580  ERROR("Need server address/port");
1581  usage(64);
1582  }
1583  if ((argc == 1) && need_pool) {
1584  ERROR("Need pool to operate on");
1585  usage(64);
1586  }
1587  if (argc > 3) usage(64);
1588 
1589  /*
1590  * Read configuration files if necessary.
1591  */
1592  if (filename && (cf_file_read(conf->cs, filename) < 0 || (cf_section_pass2(conf->cs) < 0))) {
1593  fr_exit_now(EXIT_FAILURE);
1594  }
1595 
1596  cp = cf_pair_alloc(conf->cs, "server", argv[0], T_OP_EQ, T_BARE_WORD, T_DOUBLE_QUOTED_STRING);
1597  if (!cp) {
1598  ERROR("Failed creating server pair");
1599  fr_exit_now(EXIT_FAILURE);
1600  }
1601 
1602  /*
1603  * Unescape sequences in the pool name
1604  */
1605  if (argv[1] && (argv[1][0] != '\0')) {
1606  fr_sbuff_t out;
1608 
1609  MEM(fr_sbuff_init_talloc(conf, &out, &tctx, strlen(argv[1]) + 1, SIZE_MAX));
1610  (void) fr_value_str_unescape(&out,
1611  &FR_SBUFF_IN(argv[1], strlen(argv[1])), SIZE_MAX, '"');
1612  talloc_realloc(conf, out.buff, uint8_t, fr_sbuff_used(&out));
1613  pool_arg = (uint8_t *)out.buff;
1614  }
1615 
1616  if (argc >= 3 && (argv[2][0] != '\0')) {
1617  fr_sbuff_t out;
1619 
1620  MEM(fr_sbuff_init_talloc(conf, &out, &tctx, strlen(argv[1]) + 1, SIZE_MAX));
1621  (void) fr_value_str_unescape(&out,
1622  &FR_SBUFF_IN(argv[2], strlen(argv[2])), SIZE_MAX, '"');
1623  talloc_realloc(conf, out.buff, uint8_t, fr_sbuff_used(&out));
1624  range_arg = (uint8_t *)out.buff;
1625  }
1626 
1627  if (!do_import && !do_export && !list_pools && !print_stats && (p == ops)) {
1628  ERROR("Nothing to do!");
1629  fr_exit_now(EXIT_FAILURE);
1630  }
1631 
1632  /*
1633  * Set some alternative default pool settings
1634  */
1635  pool_cs = cf_section_find(conf->cs, "pool", NULL);
1636  if (!pool_cs) {
1637  pool_cs = cf_section_alloc(conf->cs, conf->cs, "pool", NULL);
1638  }
1639  cp = cf_pair_find(pool_cs, "start");
1640  if (!cp) {
1641  /*
1642  * Start should always default to 1
1643  * else the cluster code doesn't
1644  * map the cluster.
1645  */
1646  (void) cf_pair_alloc(pool_cs, "start", "1", T_OP_EQ, T_BARE_WORD, T_BARE_WORD);
1647  }
1648  cp = cf_pair_find(pool_cs, "spare");
1649  if (!cp) {
1650  (void) cf_pair_alloc(pool_cs, "spare", "0", T_OP_EQ, T_BARE_WORD, T_BARE_WORD);
1651  }
1652  cp = cf_pair_find(pool_cs, "min");
1653  if (!cp) {
1654  (void) cf_pair_alloc(pool_cs, "min", "0", T_OP_EQ, T_BARE_WORD, T_BARE_WORD);
1655  }
1656  cp = cf_pair_find(pool_cs, "max");
1657  if (!cp) {
1658  /*
1659  * Set a safe default for "max" - as this is a stand alone tool,
1660  * it can't use automatic value from the worker thread count.
1661  */
1662  (void) cf_pair_alloc(pool_cs, "max", "10", T_OP_EQ, T_BARE_WORD, T_BARE_WORD);
1663  }
1664 
1665  if (driver_init(conf, conf->cs, &conf->driver) < 0) {
1666  ERROR("Driver initialisation failed");
1667  fr_exit_now(EXIT_FAILURE);
1668  }
1669 
1670  if (do_import) {
1671  ERROR("NOT YET IMPLEMENTED");
1672  }
1673 
1674  if (do_export) {
1675  ERROR("NOT YET IMPLEMENTED");
1676  }
1677 
1678  if (print_stats) {
1680  uint8_t **pools;
1681  ssize_t slen;
1682  size_t i;
1683 
1684  if (pool_arg) {
1685  pools = talloc_zero_array(conf, uint8_t *, 1);
1686  slen = 1;
1687  pools[0] = pool_arg;
1688  } else {
1689  slen = driver_get_pools(conf, &pools, conf->driver);
1690  if (slen < 0) fr_exit_now(EXIT_FAILURE);
1691  }
1692 
1693  for (i = 0; i < (size_t)slen; i++) {
1694  if (driver_get_stats(&stats, conf->driver,
1695  pools[i], talloc_array_length(pools[i])) < 0) fr_exit_now(EXIT_FAILURE);
1696 
1697  INFO("pool : %pV", fr_box_strvalue_len((char *)pools[i],
1698  talloc_array_length(pools[i])));
1699  INFO("total : %" PRIu64, stats.total);
1700  INFO("dynamic total : %" PRIu64, stats.total - stats.static_tot);
1701  INFO("dynamic free : %" PRIu64, stats.free);
1702  INFO("dynamic used : %" PRIu64, stats.total - stats.free - stats.static_tot);
1703  if ((stats.total - stats.static_tot) > 0) {
1704  INFO("dynamic used (%%) : %.2Lf",
1705  ((long double)(stats.total - stats.free - stats.static_tot) /
1706  (long double)(stats.total - stats.static_tot)) * 100);
1707  } else {
1708  INFO("used (%%) : 0");
1709  }
1710  INFO("expiring 0-1m : %" PRIu64, stats.expiring_1m);
1711  INFO("expiring 1-30m : %" PRIu64, stats.expiring_30m - stats.expiring_1m);
1712  INFO("expiring 30m-1h : %" PRIu64, stats.expiring_1h - stats.expiring_30m);
1713  INFO("expiring 1h-1d : %" PRIu64, stats.expiring_1d - stats.expiring_1h);
1714  INFO("static total : %" PRIu64, stats.static_tot);
1715  INFO("static 'free' : %" PRIu64, stats.static_free);
1716  INFO("static issued : %" PRIu64, stats.static_tot - stats.static_free);
1717  if (stats.static_tot) {
1718  INFO("static issued (%%) : %.2Lf",
1719  ((long double)(stats.static_tot - stats.static_free) /
1720  (long double)(stats.static_tot)) * 100);
1721  } else {
1722  INFO("static issued (%%) : 0");
1723  }
1724  INFO("static renew 0-1m : %" PRIu64, stats.static_1m);
1725  INFO("static renew 1-30m : %" PRIu64, stats.static_30m - stats.static_1m);
1726  INFO("static renew 30m-1h : %" PRIu64, stats.static_1h - stats.static_30m);
1727  INFO("static renew 1h-1d : %" PRIu64, stats.static_1d - stats.static_1h);
1728  INFO("--");
1729  }
1730  }
1731 
1732  if (list_pools) {
1733  ssize_t slen;
1734  size_t i;
1735  uint8_t **pools;
1736 
1737  slen = driver_get_pools(conf, &pools, conf->driver);
1738  if (slen < 0) fr_exit_now(EXIT_FAILURE);
1739  if (slen > 0) {
1740  for (i = 0; i < (size_t)slen; i++) {
1741  INFO("%pV", fr_box_strvalue_len((char *)pools[i], talloc_array_length(pools[i])));
1742  }
1743  INFO("--");
1744  }
1745 
1746  talloc_free(pools);
1747  }
1748 
1749  /*
1750  * Fixup the operations without specific pools or ranges
1751  * and parse the IP ranges.
1752  */
1753  end = p;
1754  for (p = ops; p < end; p++) {
1755  if (parse_ip_range(&p->start, &p->end, p->name, p->prefix) < 0) usage(64);
1756  if (!p->prefix) p->prefix = IPADDR_LEN(p->start.af);
1757 
1758  if (!p->pool) {
1759  p->pool = pool_arg;
1760  p->pool_len = talloc_array_length(pool_arg);
1761  }
1762  if (!p->range && range_arg) {
1763  p->range = range_arg;
1764  p->range_len = talloc_array_length(range_arg);
1765  }
1766  }
1767 
1768  for (p = ops; (p < end) && (p->start.af != AF_UNSPEC); p++) switch (p->action) {
1769  case IPPOOL_TOOL_ADD:
1770  {
1771  uint64_t count = 0;
1772 
1773  if (driver_add_lease(&count, conf->driver, p) < 0) {
1774  fr_exit_now(EXIT_FAILURE);
1775  }
1776  INFO("Added %" PRIu64 " address(es)/prefix(es)", count);
1777  }
1778  break;
1779 
1780  case IPPOOL_TOOL_REMOVE:
1781  {
1782  uint64_t count = 0;
1783 
1784  if (driver_remove_lease(&count, conf->driver, p) < 0) {
1785  fr_exit_now(EXIT_FAILURE);
1786  }
1787  INFO("Removed %" PRIu64 " address(es)/prefix(es)", count);
1788  }
1789  continue;
1790 
1791  case IPPOOL_TOOL_RELEASE:
1792  {
1793  uint64_t count = 0;
1794 
1795  if (driver_release_lease(&count, conf->driver, p) < 0) {
1796  fr_exit_now(EXIT_FAILURE);
1797  }
1798  INFO("Released %" PRIu64 " address(es)/prefix(es)", count);
1799  }
1800  continue;
1801 
1802  case IPPOOL_TOOL_SHOW:
1803  {
1804  ippool_tool_lease_t **leases = NULL;
1805  size_t len, i;
1806 
1807  if (driver_show_lease(&leases, conf->driver, p) < 0) {
1808  fr_exit_now(EXIT_FAILURE);
1809  }
1810  if (!fr_cond_assert(leases)) continue;
1811 
1812  len = talloc_array_length(leases);
1813  INFO("Retrieved information for %zu address(es)/prefix(es)", len - 1);
1814  for (i = 0; i < (len - 1); i++) {
1815  char ip_buff[FR_IPADDR_PREFIX_STRLEN];
1816  char time_buff[30];
1817  struct tm tm;
1818  struct timeval now;
1819  char *device = NULL;
1820  char *gateway = NULL;
1821  char *range = NULL;
1822  bool is_active;
1823 
1824  leases[i] = talloc_get_type_abort(leases[i], ippool_tool_lease_t);
1825 
1826  now = fr_time_to_timeval(fr_time());
1827  is_active = now.tv_sec <= leases[i]->next_event;
1828  if (leases[i]->next_event) {
1829  strftime(time_buff, sizeof(time_buff), "%b %e %Y %H:%M:%S %Z",
1830  localtime_r(&(leases[i]->next_event), &tm));
1831  } else {
1832  time_buff[0] = '\0';
1833  }
1834  IPPOOL_SPRINT_IP(ip_buff, &(leases[i]->ipaddr), leases[i]->ipaddr.prefix);
1835 
1836  if (leases[i]->range) {
1837  range = fr_asprint(leases, (char const *)leases[i]->range,
1838  leases[i]->range_len, '\0');
1839  }
1840 
1841  INFO("--");
1842  if (range) INFO("range : %s", range);
1843  INFO("address/prefix : %s", ip_buff);
1844  INFO("active : %s", is_active ? "yes" : "no");
1845 
1846  if (leases[i]->device) {
1847  device = fr_asprint(leases, (char const *)leases[i]->device,
1848  leases[i]->device_len, '\0');
1849  }
1850  if (leases[i]->gateway) {
1851  gateway = fr_asprint(leases, (char const *)leases[i]->gateway,
1852  leases[i]->gateway_len, '\0');
1853  }
1854  if (is_active) {
1855  if (*time_buff) INFO("lease expires : %s", time_buff);
1856  if (device) INFO("device id : %s", device);
1857  if (gateway) INFO("gateway id : %s", gateway);
1858  } else {
1859  if (*time_buff) INFO("lease expired : %s", time_buff);
1860  if (device) INFO("last device id : %s", device);
1861  if (gateway) INFO("last gateway id : %s", gateway);
1862  }
1863  }
1864  talloc_free(leases);
1865  }
1866  continue;
1867 
1868  case IPPOOL_TOOL_MODIFY:
1869  {
1870  uint64_t count = 0;
1871 
1872  if (driver_modify_lease(&count, conf->driver, p) < 0) {
1873  fr_exit_now(EXIT_FAILURE);
1874  }
1875  INFO("Modified %" PRIu64 " address(es)/prefix(es)", count);
1876  }
1877  continue;
1878 
1879  case IPPOOL_TOOL_ASSIGN:
1880  {
1881  uint64_t count = 0;
1882 
1883  if (fr_ipaddr_cmp(&p->start, &p->end) != 0) {
1884  ERROR("Static assignment requires a single IP");
1885  fr_exit_now(EXIT_FAILURE);
1886  }
1887  if (!owner) {
1888  ERROR("Static assignment requires an owner");
1889  fr_exit_now(EXIT_FAILURE);
1890  }
1891 
1892  if (driver_assign_lease(&count, conf->driver, p, owner) < 0) {
1893  fr_exit_now(EXIT_FAILURE);
1894  }
1895  INFO("Assigned %" PRIu64 " address(es)/prefix(es)", count);
1896  }
1897  continue;
1898 
1899  case IPPOOL_TOOL_UNASSIGN:
1900  {
1901  uint64_t count = 0;
1902 
1903  if (fr_ipaddr_cmp(&p->start, &p->end) != 0) {
1904  ERROR("Static lease un-assignment requires a single IP");
1905  fr_exit_now(EXIT_FAILURE);
1906  }
1907  if (!owner) {
1908  ERROR("Static lease un-assignment requires an owner");
1909  fr_exit_now(EXIT_FAILURE);
1910  }
1911 
1912  if (driver_unassign_lease(&count, conf->driver, p, owner) < 0) {
1913  fr_exit_now(EXIT_FAILURE);
1914  }
1915  INFO("Un-assigned %" PRIu64 " address(es)/prefix(es)", count);
1916  }
1917  continue;
1918 
1919  case IPPOOL_TOOL_NOOP:
1920  break;
1921  }
1922 
1923  talloc_free(conf);
1924 
1925  return 0;
1926 }
#define RCSID(id)
Definition: build.h:444
#define NEVER_RETURNS
Should be placed before the function return type.
Definition: build.h:311
#define STRINGIFY(x)
Definition: build.h:195
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
Definition: build.h:110
#define UNUSED
Definition: build.h:313
#define NUM_ELEMENTS(_t)
Definition: build.h:335
int cf_file_read(CONF_SECTION *cs, char const *filename)
Definition: cf_file.c:3010
int cf_section_pass2(CONF_SECTION *cs)
Definition: cf_file.c:757
int cf_section_parse(TALLOC_CTX *ctx, void *base, CONF_SECTION *cs)
Parse a configuration section into user-supplied variables.
Definition: cf_parse.c:985
#define CONF_PARSER_TERMINATOR
Definition: cf_parse.h:626
#define cf_section_rules_push(_cs, _rule)
Definition: cf_parse.h:658
Defines a CONF_PAIR to C data type mapping.
Definition: cf_parse.h:563
Configuration AVP similar to a fr_pair_t.
Definition: cf_priv.h:70
A section grouping multiple CONF_PAIR.
Definition: cf_priv.h:89
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:970
CONF_PAIR * cf_pair_find(CONF_SECTION const *cs, char const *attr)
Search for a CONF_PAIR with a specific name.
Definition: cf_util.c:1356
CONF_PAIR * cf_pair_alloc(CONF_SECTION *parent, char const *attr, char const *value, fr_token_t op, fr_token_t lhs_quote, fr_token_t rhs_quote)
Allocate a CONF_PAIR.
Definition: cf_util.c:1220
#define cf_section_alloc(_ctx, _parent, _name1, _name2)
Definition: cf_util.h:137
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:1859
int fr_redis_cluster_pool_by_node_addr(fr_pool_t **pool, fr_redis_cluster_t *cluster, fr_socket_t *node_addr, bool create)
Get the pool associated with a node in the cluster.
Definition: cluster.c:2066
ssize_t fr_redis_cluster_node_addr_by_role(TALLOC_CTX *ctx, fr_socket_t *out[], fr_redis_cluster_t *cluster, bool is_master, bool is_slave)
Return an array of IP addresses belonging to masters or slaves.
Definition: cluster.c:2134
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:1737
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:2257
A redis cluster.
Definition: cluster.c:247
Common functions for interacting with Redis cluster via Hiredis.
Redis connection sequence state.
Definition: cluster.h:49
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition: debug.h:137
#define fr_exit_now(_x)
Exit without calling atexit() handlers, producing a log message in debug builds.
Definition: debug.h:232
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
#define DEBUG(fmt,...)
Definition: dhcpclient.c:39
int fr_inet_pton(fr_ipaddr_t *out, char const *value, ssize_t inlen, int af, bool resolve, bool mask)
Simple wrapper to decide whether an IP value is v4 or v6 and call the appropriate parser.
Definition: inet.c:764
int8_t fr_ipaddr_cmp(fr_ipaddr_t const *a, fr_ipaddr_t const *b)
Compare two ip addresses.
Definition: inet.c:1332
void fr_ipaddr_mask(fr_ipaddr_t *addr, uint8_t prefix)
Zeroes out the host portion of an fr_ipaddr_t.
Definition: inet.c:203
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
union fr_ipaddr_t::@121 addr
#define FR_IPADDR_PREFIX_STRLEN
Like FR_IPADDR_STRLEN but with space for a prefix.
Definition: inet.h:93
IPv4/6 prefix.
Definition: merged_model.c:272
#define PERROR(_fmt,...)
Definition: log.h:228
talloc_free(reap)
int fr_debug_lvl
Definition: log.c:42
@ L_DBG_LVL_3
3rd highest priority debug messages (-xxx | -Xx).
Definition: log.h:72
unsigned int uint32_t
Definition: merged_model.c:33
long int ssize_t
Definition: merged_model.c:24
unsigned char uint8_t
Definition: merged_model.c:30
unsigned long int size_t
Definition: merged_model.c:25
static size_t used
void fr_quick_sort(void const *to_sort[], int start, int end, fr_cmp_t cmp)
Quick sort an array of pointers using a comparator.
Definition: misc.c:420
struct tm * localtime_r(time_t const *l_clock, struct tm *result)
Definition: missing.c:163
void fr_pool_connection_release(fr_pool_t *pool, request_t *request, void *conn)
Release a connection.
Definition: pool.c:1405
void * fr_pool_connection_get(fr_pool_t *pool, request_t *request)
Reserve a connection in the connection pool.
Definition: pool.c:1390
A connection pool.
Definition: pool.c:85
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:430
#define is_truncated(_ret, _max)
Definition: print.h:48
static rc_stats_t stats
Definition: radclient-ng.c:72
#define INFO(fmt,...)
Definition: radict.c:54
static rs_t * conf
Definition: radsniff.c:53
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
fr_redis_rcode_t fr_redis_command_status(fr_redis_conn_t *conn, redisReply *reply)
Check the reply for errors.
Definition: redis.c:71
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
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 IPADDR_LEN(_af)
Definition: redis_ippool.h:75
#define IPPOOL_MAX_POOL_KEY_SIZE
{prefix}:pool
Definition: redis_ippool.h:64
#define IPPOOL_OWNER_KEY
Definition: redis_ippool.h:59
#define IPPOOL_POOL_KEY
Definition: redis_ippool.h:57
#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
#define IPPOOL_MAX_IP_KEY_SIZE
{prefix}:ipaddr/prefix
Definition: redis_ippool.h:68
#define IPPOOL_BUILD_KEY(_buff, _p, _key, _key_len)
Wrap the prefix in {} and add the pool suffix.
Definition: redis_ippool.h:80
static NEVER_RETURNS void usage(int ret)
uint64_t static_tot
Static assignments configured.
static char lua_remove_cmd[]
Lua script for removing a lease.
int main(int argc, char *argv[])
time_t next_event
Last state change.
static int _driver_show_lease_process(void *out, fr_ipaddr_t const *ipaddr, redisReply const *reply)
Enqueue commands to retrieve lease information.
char const * name
Original range or CIDR string.
fr_redis_conf_t conf
Connection parameters for the Redis server.
uint8_t const * gateway
Last gateway id.
static int _driver_add_lease_process(void *out, UNUSED fr_ipaddr_t const *ipaddr, redisReply const *reply)
Count the number of leases we actually added.
fr_ipaddr_t ipaddr
Prefix or address.
static int _driver_add_lease_enqueue(UNUSED redis_driver_conf_t *inst, fr_redis_conn_t *conn, uint8_t const *key_prefix, size_t key_prefix_len, uint8_t const *range, size_t range_len, fr_ipaddr_t *ipaddr, uint8_t prefix, UNUSED void *uctx)
Enqueue lease addition commands.
static int _driver_unassign_lease_process(void *out, UNUSED fr_ipaddr_t const *ipaddr, redisReply const *reply)
static char lua_assign_cmd[]
Lua script for assigning a static lease.
static int driver_remove_lease(void *out, void *instance, ippool_tool_operation_t const *op)
Remove a range of leases.
#define MAX_PIPELINED
static int _driver_modify_lease_process(void *out, UNUSED fr_ipaddr_t const *ipaddr, redisReply const *reply)
Count the number of leases we modified.
static int _driver_assign_lease_process(void *out, UNUSED fr_ipaddr_t const *ipaddr, redisReply const *reply)
uint8_t const * range
Range the lease belongs to.
#define STATS_COMMANDS_TOTAL
static int driver_get_stats(ippool_tool_stats_t *out, void *instance, uint8_t const *key_prefix, size_t key_prefix_len)
static int driver_unassign_lease(void *out, void *instance, ippool_tool_operation_t const *op, char const *owner)
Unassign static lease assignments.
static int driver_do_lease(void *out, void *instance, ippool_tool_operation_t const *op, redis_ippool_queue_t enqueue, redis_ippool_process_t process, void *uctx)
Add a net to the pool.
#define EOL
static int _driver_show_lease_enqueue(UNUSED redis_driver_conf_t *inst, fr_redis_conn_t *conn, uint8_t const *key_prefix, size_t key_prefix_len, UNUSED uint8_t const *range, UNUSED size_t range_len, fr_ipaddr_t *ipaddr, uint8_t prefix, UNUSED void *uctx)
Enqueue commands to retrieve lease information.
size_t pool_len
Length of the pool identifier.
uint64_t static_30m
Static leases that should renew in the next 30 minutes.
static char const * name
uint64_t expiring_1m
Addresses that expire in the next minute.
static int driver_assign_lease(void *out, void *instance, ippool_tool_operation_t const *op, char const *owner)
Add static lease assignments.
int(* redis_ippool_process_t)(void *out, fr_ipaddr_t const *ipaddr, redisReply const *reply)
static uint32_t uint32_gen_mask(uint8_t bits)
uint64_t expiring_30m
Addresses that expire in the next 30 minutes.
uint64_t expiring_1h
Addresses that expire in the next hour.
static ssize_t driver_get_pools(TALLOC_CTX *ctx, uint8_t **out[], void *instance)
Return the pools available across the cluster.
static char lua_unassign_cmd[]
Lua script for un-assigning a static lease.
static conf_parser_t redis_config[]
uint64_t total
Addresses available.
static int _driver_release_lease_process(void *out, UNUSED fr_ipaddr_t const *ipaddr, redisReply const *reply)
Count the number of leases we released.
static int8_t pool_cmp(void const *a, void const *b)
Compare two pool names.
uint64_t static_1h
Static leases that should renew in the next hour.
static int _driver_remove_lease_enqueue(UNUSED redis_driver_conf_t *inst, fr_redis_conn_t *conn, uint8_t const *key_prefix, size_t key_prefix_len, UNUSED uint8_t const *range, UNUSED size_t range_len, fr_ipaddr_t *ipaddr, uint8_t prefix, UNUSED void *uctx)
Enqueue lease removal commands.
#define ADD_ACTION(_action)
static char lua_release_cmd[]
Lua script for releasing a lease.
static int driver_add_lease(void *out, void *instance, ippool_tool_operation_t const *op)
Add a range of prefixes.
fr_redis_cluster_t * cluster
static int _driver_remove_lease_process(void *out, UNUSED fr_ipaddr_t const *ipaddr, redisReply const *reply)
Count the number of leases we removed.
static int driver_release_lease(void *out, void *instance, ippool_tool_operation_t const *op)
Release a range of leases.
static int driver_init(TALLOC_CTX *ctx, CONF_SECTION *conf, void **instance)
Driver initialization function.
uint8_t const * range
Range identifier.
uint8_t prefix
Prefix - The bits between the address mask, and the prefix form the addresses to be modified in the p...
uint8_t const * pool
Pool identifier.
ippool_tool_action_t action
What to do to the leases described by net/prefix.
static int _driver_modify_lease_enqueue(UNUSED redis_driver_conf_t *inst, fr_redis_conn_t *conn, uint8_t const *key_prefix, size_t key_prefix_len, uint8_t const *range, size_t range_len, fr_ipaddr_t *ipaddr, uint8_t prefix, UNUSED void *uctx)
Enqueue lease removal commands.
uint64_t free
Addresses in use.
static int _driver_assign_lease_enqueue(UNUSED redis_driver_conf_t *inst, fr_redis_conn_t *conn, uint8_t const *key_prefix, size_t key_prefix_len, uint8_t const *range, size_t range_len, fr_ipaddr_t *ipaddr, uint8_t prefix, void *uctx)
Enqueue static lease assignment commands.
uint64_t expiring_1d
Addresses that expire in the next day.
static int driver_modify_lease(void *out, void *instance, ippool_tool_operation_t const *op)
Remove a range of leases.
uint64_t static_free
Static leases that have not been requested.
size_t range_len
Length of the range identifier.
fr_ipaddr_t end
End address.
uint64_t static_1m
Static leases that should renew in the next minute.
ippool_tool_action
Pool management actions.
@ IPPOOL_TOOL_ADD
Add one or more IP addresses.
@ IPPOOL_TOOL_REMOVE
Remove one or more IP addresses.
@ IPPOOL_TOOL_NOOP
Do nothing.
@ IPPOOL_TOOL_MODIFY
Modify attributes of one or more IP addresses.
@ IPPOOL_TOOL_SHOW
Show one or more IP addresses.
@ IPPOOL_TOOL_ASSIGN
Assign a static IP address to a device.
@ IPPOOL_TOOL_RELEASE
Release one or more IP addresses.
@ IPPOOL_TOOL_UNASSIGN
Remove static IP address assignment.
static int parse_ip_range(fr_ipaddr_t *start_out, fr_ipaddr_t *end_out, char const *ip_str, uint8_t prefix)
Convert an IP range or CIDR mask to a start and stop address.
fr_ipaddr_t start
Start address.
#define IPPOOL_BUILD_IP_KEY_FROM_STR(_buff, _p, _key, _key_len, _ip_str)
static int _driver_unassign_lease_enqueue(UNUSED redis_driver_conf_t *inst, fr_redis_conn_t *conn, uint8_t const *key_prefix, size_t key_prefix_len, UNUSED uint8_t const *range, UNUSED size_t range_len, fr_ipaddr_t *ipaddr, uint8_t prefix, void *uctx)
Enqueue static lease un-assignment commands.
int(* redis_ippool_queue_t)(redis_driver_conf_t *inst, fr_redis_conn_t *conn, uint8_t const *key_prefix, size_t key_prefix_len, uint8_t const *range, size_t range_len, fr_ipaddr_t *ipaddr, uint8_t prefix, void *uctx)
static bool ipaddr_next(fr_ipaddr_t *ipaddr, fr_ipaddr_t const *end, uint8_t prefix)
Iterate over range of IP addresses.
uint64_t static_1d
Static leases that should renew in the next day.
static int _driver_release_lease_enqueue(UNUSED redis_driver_conf_t *inst, fr_redis_conn_t *conn, uint8_t const *key_prefix, size_t key_prefix_len, UNUSED uint8_t const *range, UNUSED size_t range_len, fr_ipaddr_t *ipaddr, uint8_t prefix, UNUSED void *uctx)
Release a lease by setting its score back to zero.
static int driver_show_lease(void *out, void *instance, ippool_tool_operation_t const *op)
Show information about leases.
uint8_t const * device
Last device id.
enum ippool_tool_action ippool_tool_action_t
Pool management actions.
A single pool operation.
#define FR_SBUFF_IN(_start, _len_or_end)
#define fr_sbuff_used(_sbuff_or_marker)
Talloc sbuff extension structure.
Definition: sbuff.h:114
return count
Definition: module.c:175
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
eap_aka_sim_process_conf_t * inst
#define fr_time()
Allow us to arbitrarily manipulate time.
Definition: state_test.c:8
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:34
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition: table.h:253
static int64_t fr_time_to_sec(fr_time_t when)
Convert an fr_time_t (internal time) to number of sec since the unix epoch (wallclock time)
Definition: time.h:729
#define fr_time_to_timeval(_when)
Convert server epoch time to unix epoch time.
Definition: time.h:740
"server local" time.
Definition: time.h:69
@ T_BARE_WORD
Definition: token.h:120
@ T_OP_EQ
Definition: token.h:83
@ T_DOUBLE_QUOTED_STRING
Definition: token.h:121
static uint128_t uint128_new(uint64_t h, uint64_t l)
Creates a new uint128_t from a uint64_t.
Definition: uint128.h:268
static uint128_t uint128_gen_mask(uint8_t bits)
Create a 128 bit integer value with n bits high.
Definition: uint128.h:65
static uint128_t uint128_bor(uint128_t a, uint128_t b)
Perform bitwise | of two 128bit unsigned integers.
Definition: uint128.h:239
static uint128_t uint128_lshift(uint128_t num, uint8_t bits)
Left shift 128 bit integer.
Definition: uint128.h:191
static bool uint128_gt(uint128_t a, uint128_t b)
Return whether one integer is greater than the other.
Definition: uint128.h:258
static bool uint128_eq(uint128_t a, uint128_t b)
Return whether the integers are equal.
Definition: uint128.h:250
static uint128_t uint128_sub(uint128_t a, uint128_t b)
Subtract one 128bit integer from another.
Definition: uint128.h:128
static uint128_t uint128_add(uint128_t a, uint128_t b)
Add two 128bit unsigned integers.
Definition: uint128.h:115
Holds information necessary for binding or connecting to a socket.
Definition: socket.h:63
size_t fr_value_str_unescape(fr_sbuff_t *out, fr_sbuff_t *in, size_t inlen, char quote)
Convert a string value with escape sequences into its binary form.
Definition: value.c:1083
#define fr_box_strvalue_len(_val, _len)
Definition: value.h:279
static size_t char ** out
Definition: value.h:984