3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2024-11-22 06:29:23 +01:00

ip-pool: Add subnet address selection logic

Add the ip_pool_select_addr4 function to select a random subnet of requested
size from an address space defined by a string list (for use with the
AP profile [IPv4].Address and the global [IPv4].APAddressPool settings),
avoiding those subnets that conflict with subnets in use.  We take care
to give a similar weight to all subnets contained in the specified
ranges regardless of how many ranges contain each, basically so that
overlapping ranges don't affect the probabilities (debatable.)
This commit is contained in:
Andrew Zaborowski 2021-05-28 02:47:31 +02:00 committed by Denis Kenzior
parent 6e5b26ba64
commit e56e4ade90
2 changed files with 208 additions and 0 deletions

View File

@ -41,9 +41,215 @@ struct ip_pool_addr4_record {
struct l_rtnl_address *addr; struct l_rtnl_address *addr;
}; };
struct ip_pool_addr4_range {
uint32_t start;
uint32_t end;
};
static struct l_queue *used_addr4_list; static struct l_queue *used_addr4_list;
static struct l_netlink *rtnl; static struct l_netlink *rtnl;
static int ip_pool_addr4_range_compare(const void *a, const void *b,
void *user_data)
{
const struct ip_pool_addr4_range *range_a = a;
const struct ip_pool_addr4_range *range_b = b;
return range_a->start > range_b->start ? 1 : -1;
}
/*
* Append any address ranges within an input start/end range which contain
* at least one full subnet and don't intersect with any subnets already in
* use. This may result in the input range being split into multiple ranges
* of different sizes or being skipped altogether.
* All inputs must be rounded to the subnet boundary and the @used queue
* sorted by the subnet start address.
*/
static void ip_pool_append_range(struct l_queue *to,
const struct ip_pool_addr4_range *range,
struct l_queue *used, const char *str)
{
const struct l_queue_entry *entry = l_queue_get_entries(used);
const struct ip_pool_addr4_range *used_range =
entry ? entry->data : NULL;
uint32_t start = range->start;
bool print = true;
while (range->end > start) {
while (used_range && used_range->end <= start) {
entry = entry->next;
used_range = entry ? entry->data : NULL;
}
/* No more used ranges that intersect with @start/@range->end */
if (!used_range || range->end <= used_range->start) {
struct ip_pool_addr4_range *sub =
l_new(struct ip_pool_addr4_range, 1);
sub->start = start;
sub->end = range->end;
l_queue_push_tail(to, sub);
l_queue_insert(used, l_memdup(sub, sizeof(*sub)),
ip_pool_addr4_range_compare, NULL);
return;
}
if (print) {
l_debug("Address spec %s intersects with at least one "
"subnet already in use on the system or "
"specified in the settings", str);
print = false;
}
/* Now we know @used_range is non-NULL and intersects */
if (start < used_range->start) {
struct ip_pool_addr4_range *sub =
l_new(struct ip_pool_addr4_range, 1);
sub->start = start;
sub->end = used_range->start;
l_queue_push_tail(to, sub);
l_queue_insert(used, l_memdup(sub, sizeof(*sub)),
ip_pool_addr4_range_compare, NULL);
}
/* Skip to the start of the next subnet */
start = used_range->end;
}
}
/*
* Select a subnet and a host address from a defined space.
*
* Returns: 0 when an address was selected and written to *out_addr,
* -EEXIST if all available subnet addresses are in use,
* -EINVAL if there was a different error.
*/
int ip_pool_select_addr4(const char **addr_str_list, uint8_t subnet_prefix_len,
struct l_rtnl_address **out_addr)
{
uint32_t total = 0;
uint32_t selected;
unsigned int i;
uint32_t subnet_size = 1 << (32 - subnet_prefix_len);
uint32_t host_mask = subnet_size - 1;
uint32_t subnet_mask = ~host_mask;
uint32_t host_addr = 0;
struct l_queue *ranges = l_queue_new();
struct l_queue *used = l_queue_new();
struct in_addr ia;
const struct l_queue_entry *entry;
int err = -EINVAL;
/* Build a sorted list of used/unavailable subnets */
for (entry = l_queue_get_entries((struct l_queue *) used_addr4_list);
entry; entry = entry->next) {
const struct ip_pool_addr4_record *rec = entry->data;
struct ip_pool_addr4_range *range;
char addr_str[INET_ADDRSTRLEN];
uint32_t used_subnet_size = 1 <<
(32 - l_rtnl_address_get_prefix_length(rec->addr));
if (!l_rtnl_address_get_address(rec->addr, addr_str) ||
inet_aton(addr_str, &ia) < 0)
continue;
range = l_new(struct ip_pool_addr4_range, 1);
range->start = ntohl(ia.s_addr) & subnet_mask;
range->end = (range->start + used_subnet_size + subnet_size -
1) & subnet_mask;
l_queue_insert(used, range, ip_pool_addr4_range_compare, NULL);
}
/* Build the list of available subnets */
/* Check for the static IP syntax: Address=<IP> */
if (l_strv_length((char **) addr_str_list) == 1 &&
inet_pton(AF_INET, *addr_str_list, &ia) == 1) {
struct ip_pool_addr4_range range;
host_addr = ntohl(ia.s_addr);
range.start = host_addr & subnet_mask;
range.end = range.start + subnet_size;
ip_pool_append_range(ranges, &range, used, *addr_str_list);
goto check_avail;
}
for (i = 0; addr_str_list[i]; i++) {
struct ip_pool_addr4_range range;
uint32_t addr;
uint8_t addr_prefix;
if (!util_ip_prefix_tohl(addr_str_list[i], &addr_prefix, &addr,
NULL, NULL)) {
l_error("Can't parse %s as a subnet address",
addr_str_list[i]);
goto cleanup;
}
if (addr_prefix > subnet_prefix_len) {
l_debug("Address spec %s smaller than requested "
"subnet (prefix len %i)", addr_str_list[i],
subnet_prefix_len);
continue;
}
range.start = addr & subnet_mask;
range.end = range.start + (1 << (32 - addr_prefix));
ip_pool_append_range(ranges, &range, used, addr_str_list[i]);
}
check_avail:
if (l_queue_isempty(ranges)) {
l_error("No IP subnets available for new Access Point after "
"eliminating those already in use on the system");
err = -EEXIST;
goto cleanup;
}
if (host_addr)
goto done;
/* Count available @subnet_prefix_len-sized subnets */
for (entry = l_queue_get_entries(ranges); entry; entry = entry->next) {
struct ip_pool_addr4_range *range = entry->data;
total += (range->end - range->start) >>
(32 - subnet_prefix_len);
}
selected = l_getrandom_uint32() % total;
/* Find the @selected'th @subnet_prefix_len-sized subnet */
for (entry = l_queue_get_entries(ranges);; entry = entry->next) {
struct ip_pool_addr4_range *range = entry->data;
uint32_t count = (range->end - range->start) >>
(32 - subnet_prefix_len);
if (selected < count) {
host_addr = range->start +
(selected << (32 - subnet_prefix_len));
break;
}
selected -= count;
}
if ((host_addr & 0xff) == 0)
host_addr += 1;
done:
err = 0;
ia.s_addr = htonl(host_addr);
*out_addr = l_rtnl_address_new(inet_ntoa(ia), subnet_prefix_len);
cleanup:
l_queue_destroy(ranges, l_free);
l_queue_destroy(used, l_free);
return err;
}
static void ip_pool_addr4_record_free(void *data) static void ip_pool_addr4_record_free(void *data)
{ {
struct ip_pool_addr4_record *rec = data; struct ip_pool_addr4_record *rec = data;

View File

@ -20,4 +20,6 @@
* *
*/ */
int ip_pool_select_addr4(const char **addr_str_list, uint8_t subnet_prefix_len,
struct l_rtnl_address **out_addr);
struct l_rtnl_address *ip_pool_get_addr4(uint32_t ifindex); struct l_rtnl_address *ip_pool_get_addr4(uint32_t ifindex);