blob: 07783f2677430d0f187a49a0975c4d22e51f3042 [file] [log] [blame]
/*
*
* Embedded Linux library
*
* Copyright (C) 2022 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 <linux/types.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netinet/icmp6.h>
#include "private.h"
#include "useful.h"
#include "log.h"
#include "dhcp.h"
#include "dhcp-private.h"
#include "icmp6.h"
#include "icmp6-private.h"
#include "dhcp6.h"
#include "netlink.h"
#include "rtnl.h"
#include "rtnl-private.h"
#include "queue.h"
#include "time.h"
#include "idle.h"
#include "strv.h"
#include "net.h"
#include "net-private.h"
#include "netconfig.h"
struct l_netconfig {
uint32_t ifindex;
uint32_t route_priority;
bool v4_enabled;
struct l_rtnl_address *v4_static_addr;
char *v4_gateway_override;
char **v4_dns_override;
char **v4_domain_names_override;
bool v6_enabled;
struct l_rtnl_address *v6_static_addr;
char *v6_gateway_override;
char **v6_dns_override;
char **v6_domain_names_override;
bool started;
struct l_idle *do_static_work;
bool v4_configured;
struct l_dhcp_client *dhcp_client;
bool v6_configured;
struct l_icmp6_client *icmp6_client;
struct l_dhcp6_client *dhcp6_client;
/* These objects, if not NULL, are owned by @addresses and @routes */
struct l_rtnl_address *v4_address;
struct l_rtnl_route *v4_subnet_route;
struct l_rtnl_route *v4_default_route;
struct l_rtnl_address *v6_address;
struct {
struct l_queue *current;
/*
* Temporary lists for use by the UPDATED handler to avoid
* having to remove all entries on the interface and re-add
* them from @current. Entries in @updated are those that
* RTM_NEWADDR/RTM_NEWROUTE will correctly identify as
* existing objects and replace (with NLM_F_REPLACE) or
* error out (without it) rather than create duplicates,
* for example those that only have their lifetime updated.
*
* Any entries in @added and @updated are owned by @current.
*/
struct l_queue *added;
struct l_queue *updated;
struct l_queue *removed;
} addresses, routes;
struct {
l_netconfig_event_cb_t callback;
void *user_data;
l_netconfig_destroy_cb_t destroy;
} handler;
};
union netconfig_addr {
struct in_addr v4;
struct in6_addr v6;
};
static void netconfig_update_cleanup(struct l_netconfig *nc)
{
l_queue_clear(nc->addresses.added, NULL);
l_queue_clear(nc->addresses.updated, NULL);
l_queue_clear(nc->addresses.removed,
(l_queue_destroy_func_t) l_rtnl_address_free);
l_queue_clear(nc->routes.added, NULL);
l_queue_clear(nc->routes.updated, NULL);
l_queue_clear(nc->routes.removed,
(l_queue_destroy_func_t) l_rtnl_route_free);
}
static void netconfig_emit_event(struct l_netconfig *nc, uint8_t family,
enum l_netconfig_event event)
{
if (!nc->handler.callback)
return;
nc->handler.callback(nc, family, event, nc->handler.user_data);
if (L_IN_SET(event, L_NETCONFIG_EVENT_UPDATE,
L_NETCONFIG_EVENT_CONFIGURE,
L_NETCONFIG_EVENT_UNCONFIGURE))
netconfig_update_cleanup(nc);
}
static struct l_rtnl_route *netconfig_route_new(struct l_netconfig *nc,
uint8_t family,
const void *dst,
uint8_t prefix_len,
const void *gw,
uint8_t protocol)
{
struct l_rtnl_route *rt = l_new(struct l_rtnl_route, 1);
rt->family = family;
rt->scope = (family == AF_INET && dst) ?
RT_SCOPE_LINK : RT_SCOPE_UNIVERSE;
rt->protocol = protocol;
rt->lifetime = 0xffffffff;
rt->priority = nc->route_priority;
if (dst) {
memcpy(&rt->dst, dst, family == AF_INET ? 4 : 16);
rt->dst_prefix_len = prefix_len;
}
if (gw)
memcpy(&rt->gw, gw, family == AF_INET ? 4 : 16);
return rt;
}
static void netconfig_add_v4_routes(struct l_netconfig *nc, const char *ip,
uint8_t prefix_len, const char *gateway,
uint8_t rtm_protocol)
{
struct in_addr in_addr;
char network[INET_ADDRSTRLEN];
/* Subnet route */
if (L_WARN_ON(inet_pton(AF_INET, ip, &in_addr) != 1))
return;
in_addr.s_addr &= htonl(0xfffffffflu << (32 - prefix_len));
nc->v4_subnet_route = netconfig_route_new(nc, AF_INET, network,
prefix_len, NULL,
rtm_protocol);
l_queue_push_tail(nc->routes.current, nc->v4_subnet_route);
l_queue_push_tail(nc->routes.added, nc->v4_subnet_route);
/* Gateway route */
if (nc->v4_gateway_override) {
gateway = nc->v4_gateway_override;
rtm_protocol = RTPROT_STATIC;
}
if (!gateway)
return;
nc->v4_default_route = l_rtnl_route_new_gateway(gateway);
l_rtnl_route_set_protocol(nc->v4_default_route, rtm_protocol);
L_WARN_ON(!l_rtnl_route_set_prefsrc(nc->v4_default_route, ip));
l_rtnl_route_set_priority(nc->v4_default_route, nc->route_priority);
l_queue_push_tail(nc->routes.current, nc->v4_default_route);
l_queue_push_tail(nc->routes.added, nc->v4_default_route);
}
static void netconfig_add_v6_static_routes(struct l_netconfig *nc,
const char *ip,
uint8_t prefix_len)
{
struct in6_addr in6_addr;
const void *prefix;
struct l_rtnl_route *v6_subnet_route;
struct l_rtnl_route *v6_default_route;
/* Subnet route */
if (L_WARN_ON(inet_pton(AF_INET6, ip, &in6_addr) != 1))
return;
/*
* Zero out host address bits, aka. interface ID, to produce
* the network address or prefix.
*/
prefix = net_prefix_from_ipv6(in6_addr.s6_addr, prefix_len);
/*
* One reason we add a subnet route instead of letting the kernel
* do it, by not specifying IFA_F_NOPREFIXROUTE for the address,
* is that that would force a 0 metric for the route.
*/
v6_subnet_route = netconfig_route_new(nc, AF_INET6, prefix, prefix_len,
NULL, RTPROT_STATIC);
l_queue_push_tail(nc->routes.current, v6_subnet_route);
l_queue_push_tail(nc->routes.added, v6_subnet_route);
/* Gateway route */
if (!nc->v6_gateway_override)
return;
v6_default_route = l_rtnl_route_new_gateway(nc->v6_gateway_override);
l_rtnl_route_set_protocol(v6_default_route, RTPROT_STATIC);
L_WARN_ON(!l_rtnl_route_set_prefsrc(v6_default_route, ip));
l_queue_push_tail(nc->routes.current, v6_default_route);
l_queue_push_tail(nc->routes.added, v6_default_route);
}
static bool netconfig_route_exists(struct l_queue *list,
const struct l_rtnl_route *route)
{
const struct l_queue_entry *entry;
for (entry = l_queue_get_entries(list); entry;
entry = entry->next)
if ((const struct l_rtnl_route *) entry->data == route)
return true;
return false;
}
static void netconfig_add_dhcp_address_routes(struct l_netconfig *nc)
{
const struct l_dhcp_lease *lease =
l_dhcp_client_get_lease(nc->dhcp_client);
_auto_(l_free) char *ip = NULL;
_auto_(l_free) char *broadcast = NULL;
_auto_(l_free) char *gateway = NULL;
uint32_t prefix_len;
ip = l_dhcp_lease_get_address(lease);
broadcast = l_dhcp_lease_get_broadcast(lease);
prefix_len = l_dhcp_lease_get_prefix_length(lease);
if (!prefix_len)
prefix_len = 24;
nc->v4_address = l_rtnl_address_new(ip, prefix_len);
if (L_WARN_ON(!nc->v4_address))
return;
l_rtnl_address_set_noprefixroute(nc->v4_address, true);
if (broadcast)
l_rtnl_address_set_broadcast(nc->v4_address, broadcast);
l_queue_push_tail(nc->addresses.current, nc->v4_address);
l_queue_push_tail(nc->addresses.added, nc->v4_address);
gateway = l_dhcp_lease_get_gateway(lease);
netconfig_add_v4_routes(nc, ip, prefix_len, gateway, RTPROT_DHCP);
}
static void netconfig_set_dhcp_lifetimes(struct l_netconfig *nc, bool updated)
{
const struct l_dhcp_lease *lease =
l_dhcp_client_get_lease(nc->dhcp_client);
uint32_t lifetime = l_dhcp_lease_get_lifetime(lease);
uint64_t expiry = l_dhcp_lease_get_start_time(lease) +
lifetime * L_USEC_PER_SEC;
l_rtnl_address_set_lifetimes(nc->v4_address, 0, lifetime);
l_rtnl_address_set_expiry(nc->v4_address, 0, expiry);
if (updated)
l_queue_push_tail(nc->addresses.updated, nc->v4_address);
l_rtnl_route_set_lifetime(nc->v4_subnet_route, lifetime);
l_rtnl_route_set_expiry(nc->v4_subnet_route, expiry);
if (updated)
l_queue_push_tail(nc->routes.updated, nc->v4_subnet_route);
if (!nc->v4_default_route)
return;
l_rtnl_route_set_lifetime(nc->v4_default_route, lifetime);
l_rtnl_route_set_expiry(nc->v4_default_route, expiry);
if (updated)
l_queue_push_tail(nc->routes.updated, nc->v4_default_route);
}
static void netconfig_remove_dhcp_address_routes(struct l_netconfig *nc)
{
l_queue_remove(nc->addresses.current, nc->v4_address);
l_queue_push_tail(nc->addresses.removed, nc->v4_address);
nc->v4_address = NULL;
l_queue_remove(nc->routes.current, nc->v4_subnet_route);
l_queue_push_tail(nc->routes.removed, nc->v4_subnet_route);
nc->v4_subnet_route = NULL;
if (nc->v4_default_route) {
l_queue_remove(nc->routes.current, nc->v4_default_route);
l_queue_push_tail(nc->routes.removed, nc->v4_default_route);
nc->v4_default_route = NULL;
}
}
static void netconfig_dhcp_event_handler(struct l_dhcp_client *client,
enum l_dhcp_client_event event,
void *user_data)
{
struct l_netconfig *nc = user_data;
switch (event) {
case L_DHCP_CLIENT_EVENT_IP_CHANGED:
if (L_WARN_ON(!nc->v4_configured))
break;
netconfig_remove_dhcp_address_routes(nc);
netconfig_add_dhcp_address_routes(nc);
netconfig_set_dhcp_lifetimes(nc, false);
netconfig_emit_event(nc, AF_INET, L_NETCONFIG_EVENT_UPDATE);
break;
case L_DHCP_CLIENT_EVENT_LEASE_OBTAINED:
if (L_WARN_ON(nc->v4_configured))
break;
netconfig_add_dhcp_address_routes(nc);
netconfig_set_dhcp_lifetimes(nc, false);
nc->v4_configured = true;
netconfig_emit_event(nc, AF_INET, L_NETCONFIG_EVENT_CONFIGURE);
break;
case L_DHCP_CLIENT_EVENT_LEASE_RENEWED:
if (L_WARN_ON(!nc->v4_configured))
break;
netconfig_set_dhcp_lifetimes(nc, true);
netconfig_emit_event(nc, AF_INET, L_NETCONFIG_EVENT_UPDATE);
break;
case L_DHCP_CLIENT_EVENT_LEASE_EXPIRED:
if (L_WARN_ON(!nc->v4_configured))
break;
netconfig_remove_dhcp_address_routes(nc);
nc->v4_configured = false;
if (l_dhcp_client_start(nc->dhcp_client))
/* TODO: also start a new timeout */
netconfig_emit_event(nc, AF_INET,
L_NETCONFIG_EVENT_UNCONFIGURE);
else
netconfig_emit_event(nc, AF_INET,
L_NETCONFIG_EVENT_FAILED);
break;
case L_DHCP_CLIENT_EVENT_NO_LEASE:
L_WARN_ON(nc->v4_configured);
/*
* The requested address is no longer available, try to restart
* the client.
*
* TODO: this may need to be delayed so we don't flood the
* network with DISCOVERs and NAKs. Also add a retry limit or
* better yet a configurable timeout.
*/
if (!l_dhcp_client_start(nc->dhcp_client))
netconfig_emit_event(nc, AF_INET,
L_NETCONFIG_EVENT_FAILED);
break;
}
}
static struct l_rtnl_route *netconfig_find_icmp6_route(
struct l_netconfig *nc,
const uint8_t *gateway,
const struct route_info *dst)
{
const struct l_queue_entry *entry;
for (entry = l_queue_get_entries(nc->routes.current); entry;
entry = entry->next) {
struct l_rtnl_route *route = entry->data;
const uint8_t *route_gateway;
const uint8_t *route_dst;
uint8_t route_prefix_len = 0;
if (l_rtnl_route_get_family(route) != AF_INET6 ||
l_rtnl_route_get_protocol(route) != RTPROT_RA)
continue;
route_gateway = l_rtnl_route_get_gateway_in_addr(route);
if ((gateway || route_gateway) &&
(!gateway || !route_gateway ||
memcmp(gateway, route_gateway, 16)))
continue;
route_dst = l_rtnl_route_get_dst_in_addr(route,
&route_prefix_len);
if ((dst || route_prefix_len) &&
(!dst || !route_prefix_len ||
dst->prefix_len != route_prefix_len ||
memcmp(dst->address, route_dst, 16)))
continue;
return route;
}
return NULL;
}
static struct l_rtnl_route *netconfig_add_icmp6_route(struct l_netconfig *nc,
const uint8_t *gateway,
const struct route_info *dst,
uint8_t preference)
{
struct l_rtnl_route *rt;
rt = netconfig_route_new(nc, AF_INET6, dst->address, dst->prefix_len,
gateway, RTPROT_RA);
if (L_WARN_ON(!rt))
return NULL;
l_rtnl_route_set_preference(rt, preference);
l_queue_push_tail(nc->routes.current, rt);
l_queue_push_tail(nc->routes.added, rt);
return rt;
}
static void netconfig_set_icmp6_route_data(struct l_netconfig *nc,
struct l_rtnl_route *rt,
uint64_t start_time,
uint32_t preferred_lifetime,
uint32_t valid_lifetime,
uint32_t mtu, bool updated)
{
uint64_t expiry = start_time + valid_lifetime * L_USEC_PER_SEC;
uint64_t old_expiry = l_rtnl_route_get_expiry(rt);
bool differs = false;
if (mtu != l_rtnl_route_get_mtu(rt)) {
l_rtnl_route_set_mtu(rt, mtu);
differs = true;
}
/*
* valid_lifetime of 0 from a route_info means the route is being
* removed so we wouldn't be here. valid_lifetime of 0xffffffff
* means no timeout. Check if the lifetime is changing between
* finite and infinite, or two finite values that result in expiry
* time difference of more than a second -- to avoid emitting
* updates for changes resulting only from the valid_lifetime one
* second resolution and RA transmission jitter. As RFC4861
* Section 6.2.7 puts it: "Due to link propagation delays and
* potentially poorly synchronized clocks between the routers such
* comparison SHOULD allow some time skew." The RFC talks about
* routers processing one another's RAs but the same logic applies
* here.
*/
if (valid_lifetime == 0xffffffff)
expiry = 0;
if ((expiry || old_expiry) &&
(!expiry || !old_expiry ||
l_time_diff(expiry, old_expiry) > L_USEC_PER_SEC)) {
l_rtnl_route_set_lifetime(rt, valid_lifetime);
l_rtnl_route_set_expiry(rt, expiry);
differs = true;
}
if (updated && differs && !netconfig_route_exists(nc->routes.added, rt))
l_queue_push_tail(nc->routes.updated, rt);
}
static void netconfig_remove_icmp6_route(struct l_netconfig *nc,
struct l_rtnl_route *route)
{
l_queue_remove(nc->routes.current, route);
l_queue_push_tail(nc->routes.removed, route);
}
static void netconfig_icmp6_event_handler(struct l_icmp6_client *client,
enum l_icmp6_client_event event,
void *event_data,
void *user_data)
{
struct l_netconfig *nc = user_data;
const struct l_icmp6_router *r;
struct l_rtnl_route *default_route;
unsigned int i;
if (event != L_ICMP6_CLIENT_EVENT_ROUTER_FOUND)
return;
r = event_data;
/*
* Note: If this is the first RA received, the l_dhcp6_client
* will have received the event before us and will be acting
* on it by now.
*/
if (nc->v6_gateway_override)
return;
/* Process the default gateway information */
default_route = netconfig_find_icmp6_route(nc, r->address, NULL);
if (!default_route && r->lifetime) {
default_route = netconfig_add_icmp6_route(nc, r->address, NULL,
r->pref);
if (unlikely(!default_route))
return;
/*
* r->lifetime is 16-bit only so there's no risk it gets
* confused for the special 0xffffffff value in
* netconfig_set_icmp6_route_data.
*/
netconfig_set_icmp6_route_data(nc, default_route, r->start_time,
r->lifetime, r->lifetime,
r->mtu, false);
} else if (default_route && r->lifetime)
netconfig_set_icmp6_route_data(nc, default_route, r->start_time,
r->lifetime, r->lifetime,
r->mtu, true);
else if (default_route && !r->lifetime)
netconfig_remove_icmp6_route(nc, default_route);
/*
* Process the onlink and offlink routes, from the Router
* Advertisement's Prefix Information options and Route
* Information options respectively.
*/
for (i = 0; i < r->n_routes; i++) {
const struct route_info *info = &r->routes[i];
const uint8_t *gateway = info->onlink ? NULL : r->address;
struct l_rtnl_route *route =
netconfig_find_icmp6_route(nc, gateway, info);
if (!route && info->valid_lifetime) {
route = netconfig_add_icmp6_route(nc, gateway, info,
info->preference);
if (unlikely(!route))
continue;
netconfig_set_icmp6_route_data(nc, route, r->start_time,
info->preferred_lifetime,
info->valid_lifetime,
gateway ? r->mtu : 0, false);
} else if (route && info->valid_lifetime)
netconfig_set_icmp6_route_data(nc, route, r->start_time,
info->preferred_lifetime,
info->valid_lifetime,
gateway ? r->mtu : 0, true);
else if (route && !info->valid_lifetime)
netconfig_remove_icmp6_route(nc, route);
}
/*
* Note: we may be emitting this before L_NETCONFIG_EVENT_CONFIGURE.
* We should probably instead save the affected routes in separate
* lists and add them to the _CONFIGURE event, suppressing any _UPDATE
* events while nc->v6_configured is false.
*/
if (!l_queue_isempty(nc->routes.added) ||
!l_queue_isempty(nc->routes.updated) ||
!l_queue_isempty(nc->routes.removed))
netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_UPDATE);
}
LIB_EXPORT struct l_netconfig *l_netconfig_new(uint32_t ifindex)
{
struct l_netconfig *nc;
nc = l_new(struct l_netconfig, 1);
nc->ifindex = ifindex;
nc->v4_enabled = true;
nc->addresses.current = l_queue_new();
nc->addresses.added = l_queue_new();
nc->addresses.updated = l_queue_new();
nc->addresses.removed = l_queue_new();
nc->routes.current = l_queue_new();
nc->routes.added = l_queue_new();
nc->routes.updated = l_queue_new();
nc->routes.removed = l_queue_new();
nc->dhcp_client = l_dhcp_client_new(ifindex);
l_dhcp_client_set_event_handler(nc->dhcp_client,
netconfig_dhcp_event_handler,
nc, NULL);
nc->dhcp6_client = l_dhcp6_client_new(ifindex);
nc->icmp6_client = l_dhcp6_client_get_icmp6(nc->dhcp6_client);
l_icmp6_client_add_event_handler(nc->icmp6_client,
netconfig_icmp6_event_handler,
nc, NULL);
return nc;
}
LIB_EXPORT void l_netconfig_destroy(struct l_netconfig *netconfig)
{
if (unlikely(!netconfig))
return;
l_netconfig_stop(netconfig);
l_netconfig_set_static_addr(netconfig, AF_INET, NULL);
l_netconfig_set_gateway_override(netconfig, AF_INET, NULL);
l_netconfig_set_dns_override(netconfig, AF_INET, NULL);
l_netconfig_set_domain_names_override(netconfig, AF_INET, NULL);
l_netconfig_set_static_addr(netconfig, AF_INET6, NULL);
l_netconfig_set_gateway_override(netconfig, AF_INET6, NULL);
l_netconfig_set_dns_override(netconfig, AF_INET6, NULL);
l_netconfig_set_domain_names_override(netconfig, AF_INET6, NULL);
l_dhcp_client_destroy(netconfig->dhcp_client);
l_dhcp6_client_destroy(netconfig->dhcp6_client);
l_netconfig_set_event_handler(netconfig, NULL, NULL, NULL);
l_queue_destroy(netconfig->addresses.current, NULL);
l_queue_destroy(netconfig->addresses.added, NULL);
l_queue_destroy(netconfig->addresses.updated, NULL);
l_queue_destroy(netconfig->addresses.removed, NULL);
l_queue_destroy(netconfig->routes.current, NULL);
l_queue_destroy(netconfig->routes.added, NULL);
l_queue_destroy(netconfig->routes.updated, NULL);
l_queue_destroy(netconfig->routes.removed, NULL);
l_free(netconfig);
}
/*
* The following l_netconfig_set_* functions configure the l_netconfig's
* client settings. The setters can be called independently, without
* following a specific order. Most of the setters will not validate the
* values passed, l_netconfig_start() will fail if settings are incorrect
* or inconsistent between themselves, e.g. if the static local IP and
* gateway IP are not in the same subnet. Alternatively
* l_netconfig_check_config() can be called at any point to validate the
* current configuration. The configuration can only be changed while
* the l_netconfig state machine is stopped, i.e. before
* l_netconfig_start() and after l_netconfig_stop().
*
* l_netconfig_set_hostname, l_netconfig_set_static_addr,
* l_netconfig_set_gateway_override, l_netconfig_set_dns_override and
* l_netconfig_set_domain_names_override can be passed NULL to unset a
* value that had been set before (revert to auto). This is why the
* family parameter is needed even when it could otherwise be derived
* from the new value that is passed.
*/
LIB_EXPORT bool l_netconfig_set_family_enabled(struct l_netconfig *netconfig,
uint8_t family, bool enabled)
{
if (unlikely(!netconfig || netconfig->started))
return false;
switch (family) {
case AF_INET:
netconfig->v4_enabled = enabled;
return true;
case AF_INET6:
netconfig->v6_enabled = enabled;
return true;
}
return false;
}
LIB_EXPORT bool l_netconfig_set_hostname(struct l_netconfig *netconfig,
const char *hostname)
{
if (unlikely(!netconfig || netconfig->started))
return false;
return l_dhcp_client_set_hostname(netconfig->dhcp_client, hostname);
}
LIB_EXPORT bool l_netconfig_set_route_priority(struct l_netconfig *netconfig,
uint32_t priority)
{
if (unlikely(!netconfig || netconfig->started))
return false;
netconfig->route_priority = priority;
return true;
}
LIB_EXPORT bool l_netconfig_set_static_addr(struct l_netconfig *netconfig,
uint8_t family,
const struct l_rtnl_address *addr)
{
struct l_rtnl_address **ptr;
if (unlikely(!netconfig || netconfig->started))
return false;
if (addr && l_rtnl_address_get_family(addr) != family)
return false;
switch (family) {
case AF_INET:
ptr = &netconfig->v4_static_addr;
break;
case AF_INET6:
ptr = &netconfig->v6_static_addr;
break;
default:
return false;
}
l_rtnl_address_free(*ptr);
*ptr = NULL;
if (!addr)
return true;
*ptr = l_rtnl_address_clone(addr);
l_rtnl_address_set_lifetimes(*ptr, 0, 0);
l_rtnl_address_set_noprefixroute(*ptr, true);
return true;
}
LIB_EXPORT bool l_netconfig_set_gateway_override(struct l_netconfig *netconfig,
uint8_t family,
const char *gateway_str)
{
char **ptr;
if (unlikely(!netconfig || netconfig->started))
return false;
switch (family) {
case AF_INET:
ptr = &netconfig->v4_gateway_override;
break;
case AF_INET6:
ptr = &netconfig->v6_gateway_override;
break;
default:
return false;
}
l_free(*ptr);
*ptr = NULL;
if (!gateway_str)
return true;
*ptr = l_strdup(gateway_str);
return true;
}
LIB_EXPORT bool l_netconfig_set_dns_override(struct l_netconfig *netconfig,
uint8_t family, char **dns_list)
{
char ***ptr;
if (unlikely(!netconfig || netconfig->started))
return false;
switch (family) {
case AF_INET:
ptr = &netconfig->v4_dns_override;
break;
case AF_INET6:
ptr = &netconfig->v6_dns_override;
break;
default:
return false;
}
l_strv_free(*ptr);
*ptr = NULL;
if (!dns_list)
return true;
*ptr = l_strv_copy(dns_list);
return true;
}
LIB_EXPORT bool l_netconfig_set_domain_names_override(
struct l_netconfig *netconfig,
uint8_t family, char **names)
{
char ***ptr;
if (unlikely(!netconfig || netconfig->started))
return false;
switch (family) {
case AF_INET:
ptr = &netconfig->v4_domain_names_override;
break;
case AF_INET6:
ptr = &netconfig->v6_domain_names_override;
break;
default:
return false;
}
l_strv_free(*ptr);
*ptr = NULL;
if (!names)
return true;
*ptr = l_strv_copy(names);
return true;
}
static bool netconfig_check_family_config(struct l_netconfig *nc,
uint8_t family)
{
struct l_rtnl_address *static_addr = (family == AF_INET) ?
nc->v4_static_addr : nc->v6_static_addr;
char *gateway_override = (family == AF_INET) ?
nc->v4_gateway_override : nc->v6_gateway_override;
char **dns_override = (family == AF_INET) ?
nc->v4_dns_override : nc->v6_dns_override;
unsigned int dns_num = 0;
if (static_addr && family == AF_INET) {
uint8_t prefix_len =
l_rtnl_address_get_prefix_length(static_addr);
if (prefix_len > 30)
return false;
}
if (gateway_override) {
union netconfig_addr gateway;
if (inet_pton(family, gateway_override, &gateway) != 1)
return false;
}
if (dns_override && (dns_num = l_strv_length(dns_override))) {
unsigned int i;
_auto_(l_free) union netconfig_addr *dns_list =
l_new(union netconfig_addr, dns_num);
for (i = 0; i < dns_num; i++)
if (inet_pton(family, dns_override[i],
&dns_list[i]) != 1)
return false;
}
return true;
}
static bool netconfig_check_config(struct l_netconfig *nc)
{
/* TODO: error reporting through a debug log handler or otherwise */
return netconfig_check_family_config(nc, AF_INET) &&
netconfig_check_family_config(nc, AF_INET6);
}
LIB_EXPORT bool l_netconfig_check_config(struct l_netconfig *netconfig)
{
if (unlikely(!netconfig || netconfig->started))
return false;
return netconfig_check_config(netconfig);
}
static void netconfig_add_v4_static_address_routes(struct l_netconfig *nc)
{
char ip[INET_ADDRSTRLEN];
uint32_t prefix_len;
nc->v4_address = l_rtnl_address_clone(nc->v4_static_addr);
l_queue_push_tail(nc->addresses.current, nc->v4_address);
l_queue_push_tail(nc->addresses.added, nc->v4_address);
l_rtnl_address_get_address(nc->v4_static_addr, ip);
prefix_len = l_rtnl_address_get_prefix_length(nc->v4_static_addr);
netconfig_add_v4_routes(nc, ip, prefix_len, NULL, RTPROT_STATIC);
}
/*
* Just mirror the IPv4 behaviour with static IPv6 configuration. It would
* be more logical to let the user choose between static IPv6 address and
* DHCPv6, and, completely independently, choose between static routes
* (if a static prefix length and/or gateway address is set) and ICMPv6.
* Yet a mechanism identical with IPv4 is easier to understand for a typical
* user so providing a static address just disables all automatic
* configuration.
*/
static void netconfig_add_v6_static_address_routes(struct l_netconfig *nc)
{
char ip[INET6_ADDRSTRLEN];
uint32_t prefix_len;
nc->v6_address = l_rtnl_address_clone(nc->v6_static_addr);
l_queue_push_tail(nc->addresses.current, nc->v6_address);
l_queue_push_tail(nc->addresses.added, nc->v6_address);
l_rtnl_address_get_address(nc->v6_static_addr, ip);
prefix_len = l_rtnl_address_get_prefix_length(nc->v6_static_addr);
netconfig_add_v6_static_routes(nc, ip, prefix_len);
}
static void netconfig_do_static_config(struct l_idle *idle, void *user_data)
{
struct l_netconfig *nc = user_data;
l_idle_remove(l_steal_ptr(nc->do_static_work));
if (nc->v4_static_addr && !nc->v4_configured) {
netconfig_add_v4_static_address_routes(nc);
nc->v4_configured = true;
netconfig_emit_event(nc, AF_INET, L_NETCONFIG_EVENT_CONFIGURE);
}
if (nc->v6_static_addr && !nc->v6_configured) {
netconfig_add_v6_static_address_routes(nc);
nc->v6_configured = true;
netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_CONFIGURE);
}
}
LIB_EXPORT bool l_netconfig_start(struct l_netconfig *netconfig)
{
if (unlikely(!netconfig || netconfig->started))
return false;
if (!netconfig_check_config(netconfig))
return false;
if (!netconfig->v4_enabled)
goto configure_ipv6;
if (netconfig->v4_static_addr) {
/*
* We're basically ready to configure the interface
* but do this in an idle callback.
*/
netconfig->do_static_work = l_idle_create(
netconfig_do_static_config,
netconfig, NULL);
goto configure_ipv6;
}
if (!l_dhcp_client_start(netconfig->dhcp_client))
return false;
configure_ipv6:
if (!netconfig->v6_enabled)
goto done;
if (netconfig->v6_static_addr) {
/*
* We're basically ready to configure the interface
* but do this in an idle callback.
*/
if (!netconfig->do_static_work)
netconfig->do_static_work = l_idle_create(
netconfig_do_static_config,
netconfig, NULL);
goto done;
}
if (!l_dhcp6_client_start(netconfig->dhcp6_client))
goto stop_ipv4;
done:
netconfig->started = true;
return true;
stop_ipv4:
if (netconfig->v4_enabled) {
if (netconfig->v4_static_addr)
l_idle_remove(l_steal_ptr(netconfig->do_static_work));
else
l_dhcp_client_stop(netconfig->dhcp_client);
}
return false;
}
LIB_EXPORT void l_netconfig_stop(struct l_netconfig *netconfig)
{
if (unlikely(!netconfig || !netconfig->started))
return;
netconfig->started = false;
if (netconfig->do_static_work)
l_idle_remove(l_steal_ptr(netconfig->do_static_work));
netconfig_update_cleanup(netconfig);
l_queue_clear(netconfig->routes.current,
(l_queue_destroy_func_t) l_rtnl_route_free);
l_queue_clear(netconfig->addresses.current,
(l_queue_destroy_func_t) l_rtnl_address_free);
netconfig->v4_address = NULL;
netconfig->v4_subnet_route = NULL;
netconfig->v4_default_route = NULL;
netconfig->v6_address = NULL;
l_dhcp_client_stop(netconfig->dhcp_client);
l_dhcp6_client_stop(netconfig->dhcp6_client);
}
LIB_EXPORT struct l_dhcp_client *l_netconfig_get_dhcp_client(
struct l_netconfig *netconfig)
{
if (unlikely(!netconfig))
return NULL;
return netconfig->dhcp_client;
}
LIB_EXPORT void l_netconfig_set_event_handler(struct l_netconfig *netconfig,
l_netconfig_event_cb_t handler,
void *user_data,
l_netconfig_destroy_cb_t destroy)
{
if (unlikely(!netconfig))
return;
if (netconfig->handler.destroy)
netconfig->handler.destroy(netconfig->handler.user_data);
netconfig->handler.callback = handler;
netconfig->handler.user_data = user_data;
netconfig->handler.destroy = destroy;
}
LIB_EXPORT void l_netconfig_apply_rtnl(struct l_netconfig *netconfig,
struct l_netlink *rtnl)
{
const struct l_queue_entry *entry;
for (entry = l_queue_get_entries(netconfig->addresses.removed); entry;
entry = entry->next)
l_rtnl_ifaddr_delete(rtnl, netconfig->ifindex, entry->data,
NULL, NULL, NULL);
for (entry = l_queue_get_entries(netconfig->addresses.added); entry;
entry = entry->next)
l_rtnl_ifaddr_add(rtnl, netconfig->ifindex, entry->data,
NULL, NULL, NULL);
/* We can use l_rtnl_ifaddr_add here since that uses NLM_F_REPLACE */
for (entry = l_queue_get_entries(netconfig->addresses.updated); entry;
entry = entry->next)
l_rtnl_ifaddr_add(rtnl, netconfig->ifindex, entry->data,
NULL, NULL, NULL);
for (entry = l_queue_get_entries(netconfig->routes.removed); entry;
entry = entry->next)
l_rtnl_route_delete(rtnl, netconfig->ifindex, entry->data,
NULL, NULL, NULL);
for (entry = l_queue_get_entries(netconfig->routes.added); entry;
entry = entry->next)
l_rtnl_route_add(rtnl, netconfig->ifindex, entry->data,
NULL, NULL, NULL);
/* We can use l_rtnl_route_add here since that uses NLM_F_REPLACE */
for (entry = l_queue_get_entries(netconfig->routes.updated); entry;
entry = entry->next)
l_rtnl_route_add(rtnl, netconfig->ifindex, entry->data,
NULL, NULL, NULL);
}
LIB_EXPORT struct l_queue *l_netconfig_get_addresses(
struct l_netconfig *netconfig,
struct l_queue **out_added,
struct l_queue **out_updated,
struct l_queue **out_removed)
{
if (out_added)
*out_added = netconfig->addresses.added;
if (out_updated)
*out_updated = netconfig->addresses.updated;
if (out_removed)
*out_removed = netconfig->addresses.removed;
return netconfig->addresses.current;
}
LIB_EXPORT struct l_queue *l_netconfig_get_routes(struct l_netconfig *netconfig,
struct l_queue **out_added,
struct l_queue **out_updated,
struct l_queue **out_removed)
{
if (out_added)
*out_added = netconfig->routes.added;
if (out_updated)
*out_updated = netconfig->routes.updated;
if (out_removed)
*out_removed = netconfig->routes.removed;
return netconfig->routes.current;
}