mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-11-25 17:59:25 +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:
parent
6e5b26ba64
commit
e56e4ade90
206
src/ip-pool.c
206
src/ip-pool.c
@ -41,9 +41,215 @@ struct ip_pool_addr4_record {
|
||||
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_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)
|
||||
{
|
||||
struct ip_pool_addr4_record *rec = data;
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user