mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-11-23 07:29:28 +01:00
e56e4ade90
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.)
382 lines
10 KiB
C
382 lines
10 KiB
C
/*
|
|
*
|
|
* Wireless daemon for Linux
|
|
*
|
|
* Copyright (C) 2021 Intel Corporation. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <ell/ell.h>
|
|
|
|
#include "src/util.h"
|
|
#include "src/iwd.h"
|
|
#include "src/module.h"
|
|
#include "src/netdev.h"
|
|
#include "src/ip-pool.h"
|
|
|
|
struct ip_pool_addr4_record {
|
|
uint32_t ifindex;
|
|
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;
|
|
|
|
l_rtnl_address_free(rec->addr);
|
|
l_free(rec);
|
|
}
|
|
|
|
static bool ip_pool_addr4_match_ifindex(const void *a, const void *b)
|
|
{
|
|
const struct ip_pool_addr4_record *addr = a;
|
|
|
|
return addr->ifindex == L_PTR_TO_UINT(b);
|
|
}
|
|
|
|
struct l_rtnl_address *ip_pool_get_addr4(uint32_t ifindex)
|
|
{
|
|
const struct ip_pool_addr4_record *rec =
|
|
l_queue_find(used_addr4_list, ip_pool_addr4_match_ifindex,
|
|
L_UINT_TO_PTR(ifindex));
|
|
|
|
return rec ? l_rtnl_address_clone(rec->addr) : 0;
|
|
}
|
|
|
|
static bool ip_pool_addr4_match_free(void *data, void *user_data)
|
|
{
|
|
const struct ip_pool_addr4_record *a = data;
|
|
const struct ip_pool_addr4_record *b = user_data;
|
|
char a_addr_str[INET_ADDRSTRLEN];
|
|
char b_addr_str[INET_ADDRSTRLEN];
|
|
|
|
if (a->ifindex != b->ifindex ||
|
|
l_rtnl_address_get_prefix_length(a->addr) !=
|
|
l_rtnl_address_get_prefix_length(b->addr))
|
|
return false;
|
|
|
|
if (!l_rtnl_address_get_address(a->addr, a_addr_str) ||
|
|
!l_rtnl_address_get_address(b->addr, b_addr_str) ||
|
|
strcmp(a_addr_str, b_addr_str))
|
|
return false;
|
|
|
|
ip_pool_addr4_record_free(data);
|
|
return true;
|
|
}
|
|
|
|
static void ip_pool_addr_notify(uint16_t type, const void *data, uint32_t len,
|
|
void *user_data)
|
|
{
|
|
const struct ifaddrmsg *ifa = data;
|
|
struct l_rtnl_address *addr;
|
|
|
|
if (ifa->ifa_family != AF_INET || ifa->ifa_prefixlen < 1)
|
|
return;
|
|
|
|
len -= NLMSG_ALIGN(sizeof(struct ifaddrmsg));
|
|
|
|
addr = l_rtnl_ifaddr_extract(ifa, len);
|
|
if (!addr)
|
|
return;
|
|
|
|
if (type == RTM_NEWADDR) {
|
|
struct ip_pool_addr4_record *rec;
|
|
|
|
rec = l_new(struct ip_pool_addr4_record, 1);
|
|
rec->ifindex = ifa->ifa_index;
|
|
rec->addr = l_steal_ptr(addr);
|
|
l_queue_push_tail(used_addr4_list, rec);
|
|
} else if (type == RTM_DELADDR) {
|
|
struct ip_pool_addr4_record rec;
|
|
|
|
rec.ifindex = ifa->ifa_index;
|
|
rec.addr = addr;
|
|
|
|
l_queue_foreach_remove(used_addr4_list,
|
|
ip_pool_addr4_match_free, &rec);
|
|
}
|
|
|
|
l_rtnl_address_free(addr);
|
|
}
|
|
|
|
static void ip_pool_addr4_dump_cb(int error,
|
|
uint16_t type, const void *data,
|
|
uint32_t len, void *user_data)
|
|
{
|
|
if (error) {
|
|
l_error("addr4_dump_cb: %s (%i)", strerror(-error), -error);
|
|
return;
|
|
}
|
|
|
|
ip_pool_addr_notify(type, data, len, user_data);
|
|
}
|
|
|
|
static int ip_pool_init(void)
|
|
{
|
|
const struct l_settings *settings = iwd_get_config();
|
|
bool netconfig_enabled;
|
|
|
|
if (!l_settings_get_bool(settings, "General",
|
|
"EnableNetworkConfiguration",
|
|
&netconfig_enabled))
|
|
netconfig_enabled = false;
|
|
|
|
if (!netconfig_enabled)
|
|
return 0;
|
|
|
|
rtnl = iwd_get_rtnl();
|
|
|
|
if (!l_netlink_register(rtnl, RTNLGRP_IPV4_IFADDR,
|
|
ip_pool_addr_notify, NULL, NULL)) {
|
|
l_error("Failed to register for RTNL link notifications");
|
|
return -EIO;
|
|
}
|
|
|
|
if (!l_rtnl_ifaddr4_dump(rtnl, ip_pool_addr4_dump_cb, NULL, NULL)) {
|
|
l_error("Sending the IPv4 addr dump req failed");
|
|
return -EIO;
|
|
}
|
|
|
|
used_addr4_list = l_queue_new();
|
|
return 0;
|
|
}
|
|
|
|
static void ip_pool_exit(void)
|
|
{
|
|
l_queue_destroy(used_addr4_list, ip_pool_addr4_record_free);
|
|
used_addr4_list = NULL;
|
|
}
|
|
|
|
IWD_MODULE(ip_pool, ip_pool_init, ip_pool_exit)
|