27 RCSID(
"$Id: 7a49fb331ce0bb8aeff5bea8e9f2419a3b63668c $")
28 #include <freeradius-devel/libradius.h>
29 #include <freeradius-devel/rad_assert.h>
35 #define MAX_PIPELINED 1000
47 return waitpid(pid, status, 0);
107 uint8_t
const *key_prefix,
size_t key_prefix_len,
108 uint8_t
const *range,
size_t range_len,
113 #define IPPOOL_BUILD_IP_KEY_FROM_STR(_buff, _p, _key, _key_len, _ip_str) \
117 memcpy(_p, _key, _key_len); \
119 _slen = strlcpy((char *)_p, "}:"IPPOOL_ADDRESS_KEY":", sizeof(_buff) - (_p - _buff)); \
120 if (is_truncated((size_t)_slen, sizeof(_buff) - (_p - _buff))) { \
121 ERROR("IP key too long"); \
124 _p += (size_t)_slen;\
125 _p += strlcpy((char *)_p, _ip_str, sizeof(_buff) - (_p - _buff)); \
132 INFO(
"Usage: %s [[-a|-d|-r] -p] [options] <server[:port]> <pool> [<range>]", name);
133 INFO(
"Pool management:");
134 INFO(
" -a <addr> Add addresses/prefixes to the pool");
135 INFO(
" -d <addr> Delete addresses/prefixes in this range");
136 INFO(
" -r <addr> Release addresses/prefixes in this range");
137 INFO(
" -s <addr> Show addresses/prefix in this range");
138 INFO(
" -p <prefix_len> Length of prefix to allocate (defaults to 32/128)");
145 INFO(
"Configuration:");
146 INFO(
" -h Print this help message and exit");
147 INFO(
" -x Increase the verbosity level");
149 INFO(
" -f <file> Load options from a FreeRADIUS (radisud) format config file");
151 INFO(
"<addr> is range \"127.0.0.1-127.0.0.254\" or CIDR network \"127.0.0.1/24\"");
152 INFO(
"CIDR host bits set start address, e.g. 127.0.0.200/24 -> 127.0.0.200-127.0.0.254");
153 INFO(
"CIDR /32 or /128 excludes upper broadcast address");
159 if (bits >= 32)
return 0xffffffff;
160 return (1 << bits) - 1;
168 #ifndef HAVE_128BIT_INTEGERS
179 ret.l = 0xffffffffffffffff;
180 ret.h = (uint64_t)1 << (bits - 64);
181 ret.h ^= (ret.h - 1);
185 ret.l = (uint64_t)1 << bits;
186 ret.l ^= (ret.l - 1);
200 num.h = num.l << (bits - 64);
203 num.h = (num.h << bits) | (num.l >> (64 - bits));
217 uint64_t tmp = (((a.l & b.l) & 1) + (a.l >> 1) + (b.l >> 1)) >> 63;
219 ret.h = a.h + b.h + tmp;
234 c = (((ret.l & b.l) & 1) + (b.l >> 1) + (ret.l >> 1)) >> 63;
235 ret.h = a.h - (b.h + c);
267 return (a.h == b.h) && (a.l == b.l);
275 if (a.h < b.h)
return false;
276 if (a.h > b.h)
return true;
292 if (bits >= 128)
return ~(uint128_t)0x00;
293 return (((uint128_t)1) << bits) - 1;
295 #define uint128_lshift(_num, _bits) (_num << _bits)
297 #define uint128_bor(_a, _b) (_a | _b)
298 #define uint128_eq(_a, _b) (_a == _b)
299 #define uint128_gt(_a, _b) (_a > _b)
300 #define uint128_add(_a, _b) (_a + _b)
301 #define uint128_sub(_a, _b) (_a - _b)
302 #define uint128_new(_a, _b) ((uint128_t)_b | ((uint128_t)_a << 64))
318 switch (ipaddr->
af) {
326 uint128_t ip_curr, ip_end;
331 memcpy(&ip_curr, ipaddr->
ipaddr.ip6addr.s6_addr,
sizeof(ip_curr));
332 memcpy(&ip_end, end->
ipaddr.ip6addr.s6_addr,
sizeof(ip_curr));
338 if (
uint128_eq(ip_curr, ip_end))
return false;
342 ip_curr = htonlll(ip_curr);
343 memcpy(&ipaddr->
ipaddr.ip6addr.s6_addr, &ip_curr,
sizeof(ipaddr->
ipaddr.ip6addr.s6_addr));
349 uint32_t ip_curr, ip_end;
353 ip_curr = ntohl(ipaddr->
ipaddr.ip4addr.s_addr);
354 ip_end = ntohl(end->
ipaddr.ip4addr.s_addr);
357 if (ip_curr == ip_end)
return false;
360 ip_curr += 1 << (32 -
prefix);
361 ipaddr->
ipaddr.ip4addr.s_addr = htonl(ip_curr);
386 redisReply **replies = NULL;
390 size_t reply_cnt = 0;
413 if (enqueued < 0)
break;
414 pipelined += enqueued;
417 if (!replies) replies = talloc_zero_array(inst, redisReply *, pipelined);
418 if (!replies)
return 0;
421 talloc_array_length(replies), conn, pipelined);
423 replies[i], request, i);
427 talloc_free(replies);
434 for (i = 0; (size_t)i < reply_cnt; i++) {
437 ret = process(out, &to_process, replies[i]);
438 if (ret < 0)
continue;
443 TALLOC_FREE(replies);
463 if (reply->type != REDIS_REPLY_ARRAY)
return -1;
464 if (reply->elements < 4)
return -1;
466 if (reply->element[0]->type != REDIS_REPLY_STRING)
return -1;
469 lease->
next_event = (time_t)strtoull(reply->element[0]->str, NULL, 10);
471 if (reply->element[1]->type == REDIS_REPLY_STRING) {
472 lease->
device = talloc_memdup(lease, reply->element[1]->str, reply->element[1]->len);
475 if (reply->element[2]->type == REDIS_REPLY_STRING) {
476 lease->
gateway = talloc_memdup(lease, reply->element[2]->str, reply->element[2]->len);
479 if (reply->element[3]->type == REDIS_REPLY_STRING) {
480 lease->
range = talloc_memdup(lease, reply->element[3]->str, reply->element[3]->len);
481 lease->
range_len = reply->element[3]->len;
483 existing = talloc_array_length(*modified);
485 (*modified)[existing - 1] = lease;
494 uint8_t
const *key_prefix,
size_t key_prefix_len,
499 uint8_t *key_p = key;
503 uint8_t *ip_key_p = ip_key;
509 DEBUG(
"Retrieving lease info for %s from pool %s", ip_buff, key_prefix);
510 redisAppendCommand(conn->
handle,
"MULTI");
511 redisAppendCommand(conn->
handle,
"ZSCORE %b %s", key, key_p - key, ip_buff);
512 redisAppendCommand(conn->
handle,
"HGET %b device", ip_key, ip_key_p - ip_key);
513 redisAppendCommand(conn->
handle,
"HGET %b gateway", ip_key, ip_key_p - ip_key);
514 redisAppendCommand(conn->
handle,
"HGET %b range", ip_key, ip_key_p - ip_key);
515 redisAppendCommand(conn->
handle,
"EXEC");
532 uint64_t *modified = out;
538 if (reply->type != REDIS_REPLY_INTEGER)
return -1;
540 *modified += reply->integer;
548 uint8_t
const *key_prefix,
size_t key_prefix_len,
553 uint8_t *key_p = key;
560 DEBUG(
"Releasing %s to pool %s", ip_buff, key_prefix);
561 redisAppendCommand(conn->
handle,
"ZADD %b XX CH 0 %s", key, key_p - key, ip_buff);
581 uint64_t *modified = out;
587 if (reply->type != REDIS_REPLY_ARRAY)
return -1;
589 if ((reply->elements > 0) && (reply->element[0]->type == REDIS_REPLY_INTEGER)) {
590 *modified += reply->element[0]->integer;
601 uint8_t
const *key_prefix,
size_t key_prefix_len,
606 uint8_t *key_p = key;
610 uint8_t *ip_key_p = ip_key;
616 DEBUG(
"Removing %s from pool %s, and removing hash at %s", ip_buff, key_prefix, ip_key);
617 redisAppendCommand(conn->
handle,
"MULTI");
618 redisAppendCommand(conn->
handle,
"ZREM %b %s", key, key_p - key, ip_buff);
619 redisAppendCommand(conn->
handle,
"DEL %b", ip_key, ip_key_p - ip_key);
620 redisAppendCommand(conn->
handle,
"EXEC");
640 uint64_t *modified = out;
646 if (reply->type != REDIS_REPLY_ARRAY)
return -1;
648 if ((reply->elements > 0) && (reply->element[0]->type == REDIS_REPLY_INTEGER)) {
649 *modified += reply->element[0]->integer;
658 uint8_t
const *key_prefix,
size_t key_prefix_len,
659 uint8_t
const *range,
size_t range_len,
663 uint8_t *key_p = key;
667 uint8_t *ip_key_p = ip_key;
673 DEBUG(
"Adding %s to pool %.*s (%zu)", ip_buff, (
int)(key_p - key), key, key_p - key);
674 redisAppendCommand(conn->
handle,
"MULTI");
675 redisAppendCommand(conn->
handle,
"ZADD %b NX %u %s", key, key_p - key, 0, ip_buff);
676 redisAppendCommand(conn->
handle,
"HSET %b range %b", ip_key, ip_key_p - ip_key, range, range_len);
677 redisAppendCommand(conn->
handle,
"EXEC");
700 if (!
this)
return -1;
709 if (!this->cluster) {
734 p = strchr(ip_str,
'-');
736 char start_buff[INET6_ADDRSTRLEN + 4];
737 char end_buff[INET6_ADDRSTRLEN + 4];
740 if ((
size_t)(p - ip_str) >=
sizeof(start_buff)) {
741 ERROR(
"Start address too long");
745 len =
strlcpy(start_buff, ip_str, (p - ip_str) + 1);
748 len =
strlcpy(end_buff, p + 1,
sizeof(end_buff));
750 ERROR(
"End address too long");
754 if (
fr_inet_pton(&start, start_buff, -1, AF_UNSPEC,
false,
true) < 0) {
755 ERROR(
"Failed parsing \"%s\" as start address: %s", start_buff,
fr_strerror());
759 if (
fr_inet_pton(&end, end_buff, -1, AF_UNSPEC,
false,
true) < 0) {
764 if (start.
af != end.
af) {
765 ERROR(
"Start and end address must be of the same address family");
774 if (start.
af == AF_INET6) {
775 uint128_t start_int, end_int;
777 memcpy(&start_int, start.
ipaddr.ip6addr.s6_addr,
sizeof(start_int));
778 memcpy(&end_int, end.
ipaddr.ip6addr.s6_addr,
sizeof(end_int));
780 ERROR(
"End address must be greater than or equal to start address");
787 if (ntohl((uint32_t)(start.
ipaddr.ip4addr.s_addr)) >
788 ntohl((uint32_t)(end.
ipaddr.ip4addr.s_addr))) {
789 ERROR(
"End address must be greater than or equal to start address");
808 if (
fr_inet_pton(&start, ip_str, -1, AF_UNSPEC,
false,
false) < 0) {
809 ERROR(
"Failed parsing \"%s\" as IPv4/v6 subnet", ip_str);
815 if (prefix < start.
prefix) {
816 ERROR(
"-p must be greater than or equal to /<mask> (%u)", start.
prefix);
820 ERROR(
"-p must be less than or equal to address length (%u)",
IPADDR_LEN(start.
af));
824 if ((prefix - start.
prefix) > 64) {
825 ERROR(
"-p must be less than or equal to %u", start.
prefix + 64);
849 if (start.
af == AF_INET6) {
850 uint128_t ip, p_mask;
855 memcpy(&ip, start.
ipaddr.ip6addr.s6_addr,
sizeof(ip));
864 memcpy(&end.
ipaddr.ip6addr.s6_addr, &ip,
sizeof(end.
ipaddr.ip6addr.s6_addr));
870 ip = ntohl(start.
ipaddr.ip4addr.s_addr);
876 if (ex_broadcast) ip--;
877 end.
ipaddr.ip4addr.s_addr = htonl(ip);
886 int main(
int argc,
char *argv[])
893 char const *pool_arg, *range_arg = NULL;
894 bool do_export =
false, print_stats =
false;
895 char *do_import = NULL;
906 if (!conf->
cs) exit(1);
910 #define ADD_ACTION(_action) \
912 if ((size_t)(p - ops) >= sizeof(ops)) { \
913 ERROR("Too many actions, max is " STRINGIFY(sizeof(ops))); \
916 p->action = _action; \
921 while ((opt = getopt(argc, argv,
"a:d:r:s:p:ihxo:f:")) != EOF)
945 ERROR(
"Prefix may only be specified after a pool management action");
949 tmp = strtoul(optarg, &q, 10);
950 if (q != (optarg + strlen(optarg))) {
951 ERROR(
"Prefix must be an integer value");
955 (p - 1)->
prefix = (uint8_t)tmp & 0xff;
993 ERROR(
"Need server and pool name");
996 if (argc > 3)
usage(64);
1000 ERROR(
"Failed creating server pair");
1005 if (argc >= 3) range_arg = argv[2];
1008 ERROR(
"Nothing to do!");
1037 ERROR(
"Driver initialisation failed");
1046 for (p = ops; p < end; p++) {
1051 p->
pool = (uint8_t
const *)pool_arg;
1055 p->
range = (uint8_t
const *)range_arg;
1060 for (p = ops; (p < end) && (p->
start.
af != AF_UNSPEC); p++)
switch (p->
action) {
1068 INFO(
"Added %" PRIu64
" addresses/prefixes", count);
1079 INFO(
"Removed %" PRIu64
" addresses/prefixes", count);
1090 INFO(
"Released %" PRIu64
" addresses/prefixes", count);
1103 len = talloc_array_length(leases);
1104 INFO(
"Retrieved information for %zu addresses/prefixes", len - 1);
1105 for (i = 0; i < (len - 1); i++) {
1110 char *device = NULL;
1111 char *gateway = NULL;
1119 gettimeofday(&now, NULL);
1120 is_active = now.tv_sec <= leases[i]->
next_event;
1121 if (leases[i]->next_event) {
1122 strftime(time_buff,
sizeof(time_buff),
"%b %e %Y %H:%M:%S %Z",
1125 time_buff[0] =
'\0';
1129 if (leases[i]->range) {
1130 range =
fr_asprint(leases, (
char const *)leases[i]->range,
1131 leases[i]->range_len,
'\0');
1135 if (range)
INFO(
"range : %s", range);
1136 INFO(
"address/prefix : %s", ip_buff);
1137 INFO(
"active : %s", is_active ?
"yes" :
"no");
1139 if (leases[i]->device) {
1140 device =
fr_asprint(leases, (
char const *)leases[i]->device,
1141 leases[i]->device_len,
'\0');
1143 if (leases[i]->gateway) {
1144 gateway =
fr_asprint(leases, (
char const *)leases[i]->gateway,
1145 leases[i]->gateway_len,
'\0');
1148 if (*time_buff)
INFO(
"lease expires : %s", time_buff);
1149 if (device)
INFO(
"device id : %s", device);
1150 if (gateway)
INFO(
"gateway id : %s", gateway);
1152 if (*time_buff)
INFO(
"lease expired : %s", time_buff);
1153 if (device)
INFO(
"last device id : %s", device);
1154 if (gateway)
INFO(
"last gateway id : %s", gateway);
1157 talloc_free(leases);
1166 ERROR(
"NOT YET IMPLEMENTED");
1170 ERROR(
"NOT YET IMPLEMENTED");
1174 ERROR(
"NOT YET IMPLEMENTED");
uint128_t ntohlll(uint128_t const num)
Swap byte order of 128 bit integer.
Configuration parameters for a redis connection.
int void exec_trigger_set_conf(CONF_SECTION *cs)
Set the global trigger section exec_trigger will search in.
3rd highest priority debug messages (-xxx | -Xx).
uint8_t prefix
Prefix length - Between 0-32 for IPv4 and 0-128 for IPv6.
void cf_pair_add(CONF_SECTION *parent, CONF_PAIR *cp)
Add a configuration pair to a section.
#define CONF_PARSER_TERMINATOR
int cf_file_read(CONF_SECTION *cs, char const *file)
void fr_redis_reply_print(log_lvl_t lvl, redisReply *reply, REQUEST *request, int idx)
Print the response data in a useful treelike form.
Defines a CONF_PAIR to C data type mapping.
CONF_PAIR * cf_pair_find(CONF_SECTION const *, char const *name)
#define is_truncated(_ret, _max)
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...
redisContext * handle
Hiredis context used when issuing commands.
#define IPPOOL_BUILD_KEY(_buff, _p, _key, _key_len)
Wrap the prefix in {} and add the pool suffix.
#define REDIS_COMMON_CONFIG
int cf_section_parse(CONF_SECTION *, void *base, CONF_PARSER const *variables)
Parse a configuration section into user-supplied variables.
union fr_ipaddr_t::@1 ipaddr
#define IPPOOL_SPRINT_IP(_buff, _ip, _prefix)
If the prefix is as wide as the AF data size then print it without CIDR notation. ...
Redis connection sequence state.
Configuration AVP similar to a VALUE_PAIR.
void fr_ipaddr_mask(fr_ipaddr_t *addr, uint8_t prefix)
Zeroes out the host portion of an fr_ipaddr_t.
char const * fr_strerror(void)
Get the last library error.
CONF_SECTION * cf_section_sub_find(CONF_SECTION const *, char const *name)
Find a sub-section in a section.
CONF_PAIR * cf_pair_alloc(CONF_SECTION *parent, char const *attr, char const *value, FR_TOKEN op, FR_TOKEN lhs_type, FR_TOKEN rhs_type)
Allocate a CONF_PAIR.
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 *request, fr_redis_rcode_t status, redisReply **reply)
Get the next connection to attempt a command against.
static char const * prefix
log_lvl_t rad_debug_lvl
Global debugging level.
Common functions for interacting with Redis via hiredis.
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.
CONF_SECTION * cf_section_alloc(CONF_SECTION *parent, char const *name1, char const *name2)
Allocate a CONF_SECTION.
fr_redis_cluster_t * fr_redis_cluster_alloc(TALLOC_CTX *ctx, CONF_SECTION *module, fr_redis_conf_t *conf)
Allocate and initialise a new cluster structure.
fr_redis_rcode_t fr_redis_pipeline_result(fr_redis_rcode_t *rcode, redisReply *out[], size_t out_len, fr_redis_conn_t *conn, int pipelined)
Simplifies handling of pipelined commands with Redis cluster.
Common functions for interacting with Redis cluster via Hiredis.
fr_redis_rcode_t
Codes are ordered inversely by priority.
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 *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.
size_t strlcpy(char *dst, char const *src, size_t siz)
#define IPPOOL_MAX_IP_KEY_SIZE
{prefix}:ipaddr/prefix
#define fr_redis_pipeline_free(_r, _n)
Operation was successfull.
struct tm * localtime_r(time_t const *l_clock, struct tm *result)
void cf_section_add(CONF_SECTION *parent, CONF_SECTION *cs)
#define IPPOOL_MAX_POOL_KEY_SIZE
{prefix}:pool
#define FR_IPADDR_PREFIX_STRLEN
Like FR_IPADDR_STRLEN but with space for a prefix.
REQUEST * request_alloc(TALLOC_CTX *ctx)
Create a new REQUEST data structure.
Connection handle, holding a redis context.