| /* |
| * |
| * 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) |