zephyr/subsys/net/ip/ipv4_autoconf.c
Robert Lubos f39ba57474 net: ipv4: Fix subnet mask setting when autoconf is used
When IPv4 autoconf is enabled and IPv4 LL address is configured on an
interface, the subnet mask should be updated accordingly, otherwise it
can lead to unexpected behaviour (like for example not identifying peer
LL address as a part of the same subnet).

Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
2023-02-06 10:10:54 +01:00

399 lines
9.6 KiB
C

/** @file
* @brief IPv4 autoconf related functions
*/
/*
* Copyright (c) 2017 Matthias Boesl
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_ipv4_autoconf, CONFIG_NET_IPV4_AUTO_LOG_LEVEL);
#include "net_private.h"
#include <errno.h>
#include "../l2/ethernet/arp.h"
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/net_core.h>
#include <zephyr/net/net_if.h>
#include <zephyr/random/rand32.h>
#include "ipv4_autoconf_internal.h"
/* Have only one timer in order to save memory */
static struct k_work_delayable ipv4auto_timer;
/* Track currently active timers */
static sys_slist_t ipv4auto_ifaces;
#define BUF_ALLOC_TIMEOUT K_MSEC(100)
static struct net_pkt *ipv4_autoconf_prepare_arp(struct net_if *iface)
{
struct net_if_config *cfg = net_if_get_config(iface);
struct net_pkt *pkt;
/* We provide AF_UNSPEC to the allocator: this packet does not
* need space for any IPv4 header.
*/
pkt = net_pkt_alloc_with_buffer(iface, sizeof(struct net_arp_hdr),
AF_UNSPEC, 0, BUF_ALLOC_TIMEOUT);
if (!pkt) {
return NULL;
}
net_pkt_set_family(pkt, AF_INET);
net_pkt_set_ipv4_auto(pkt, true);
return net_arp_prepare(pkt, &cfg->ipv4auto.requested_ip,
&cfg->ipv4auto.current_ip);
}
static void ipv4_autoconf_send_probe(struct net_if_ipv4_autoconf *ipv4auto)
{
struct net_pkt *pkt;
pkt = ipv4_autoconf_prepare_arp(ipv4auto->iface);
if (!pkt) {
NET_DBG("Failed to prepare probe %p", ipv4auto->iface);
return;
}
NET_DBG("Probing pkt %p", pkt);
if (net_if_send_data(ipv4auto->iface, pkt) == NET_DROP) {
net_pkt_unref(pkt);
} else {
ipv4auto->probe_cnt++;
ipv4auto->state = NET_IPV4_AUTOCONF_PROBE;
}
}
static void ipv4_autoconf_send_announcement(
struct net_if_ipv4_autoconf *ipv4auto)
{
struct net_pkt *pkt;
pkt = ipv4_autoconf_prepare_arp(ipv4auto->iface);
if (!pkt) {
NET_DBG("Failed to prepare announcement %p", ipv4auto->iface);
return;
}
NET_DBG("Announcing pkt %p", pkt);
if (net_if_send_data(ipv4auto->iface, pkt) == NET_DROP) {
net_pkt_unref(pkt);
} else {
ipv4auto->announce_cnt++;
ipv4auto->state = NET_IPV4_AUTOCONF_ANNOUNCE;
}
}
enum net_verdict net_ipv4_autoconf_input(struct net_if *iface,
struct net_pkt *pkt)
{
struct net_if_config *cfg;
struct net_arp_hdr *arp_hdr;
cfg = net_if_get_config(iface);
if (!cfg) {
NET_DBG("Interface %p configuration missing!", iface);
return NET_DROP;
}
if (net_pkt_get_len(pkt) < sizeof(struct net_arp_hdr)) {
NET_DBG("Invalid ARP header (len %zu, min %zu bytes)",
net_pkt_get_len(pkt), sizeof(struct net_arp_hdr));
return NET_DROP;
}
arp_hdr = NET_ARP_HDR(pkt);
if (!net_ipv4_addr_cmp_raw(arp_hdr->dst_ipaddr,
(uint8_t *)&cfg->ipv4auto.requested_ip)) {
/* No conflict */
return NET_CONTINUE;
}
if (!net_ipv4_addr_cmp_raw(arp_hdr->src_ipaddr,
(uint8_t *)&cfg->ipv4auto.requested_ip)) {
/* No need to defend */
return NET_CONTINUE;
}
NET_DBG("Conflict detected from %s for %s, state %d",
net_sprint_ll_addr((uint8_t *)&arp_hdr->src_hwaddr,
arp_hdr->hwlen),
net_sprint_ipv4_addr(&arp_hdr->dst_ipaddr),
cfg->ipv4auto.state);
cfg->ipv4auto.conflict_cnt++;
switch (cfg->ipv4auto.state) {
case NET_IPV4_AUTOCONF_PROBE:
/* restart probing with renewed IP */
net_ipv4_autoconf_start(iface);
break;
case NET_IPV4_AUTOCONF_ANNOUNCE:
case NET_IPV4_AUTOCONF_ASSIGNED:
if (cfg->ipv4auto.conflict_cnt == 1U) {
/* defend IP */
ipv4_autoconf_send_announcement(&cfg->ipv4auto);
} else {
/* unset host ip */
if (!net_if_ipv4_addr_rm(iface,
&cfg->ipv4auto.requested_ip)) {
NET_DBG("Failed to remove addr from iface");
}
/* restart probing after second conflict */
net_ipv4_autoconf_start(iface);
}
break;
default:
break;
}
return NET_DROP;
}
static inline void ipv4_autoconf_addr_set(struct net_if_ipv4_autoconf *ipv4auto)
{
struct in_addr netmask = { { { 255, 255, 0, 0 } } };
if (ipv4auto->announce_cnt <=
(IPV4_AUTOCONF_ANNOUNCE_NUM - 1)) {
net_ipaddr_copy(&ipv4auto->current_ip,
&ipv4auto->requested_ip);
ipv4_autoconf_send_announcement(ipv4auto);
return;
}
/* Success, add new IPv4 address. */
if (!net_if_ipv4_addr_add(ipv4auto->iface,
&ipv4auto->requested_ip,
NET_ADDR_AUTOCONF, 0)) {
NET_DBG("Failed to add IPv4 addr to iface %p",
ipv4auto->iface);
return;
}
net_if_ipv4_set_netmask(ipv4auto->iface, &netmask);
ipv4auto->state = NET_IPV4_AUTOCONF_ASSIGNED;
}
static void ipv4_autoconf_send(struct net_if_ipv4_autoconf *ipv4auto)
{
switch (ipv4auto->state) {
case NET_IPV4_AUTOCONF_INIT:
ipv4auto->probe_cnt = 0U;
ipv4auto->announce_cnt = 0U;
ipv4auto->conflict_cnt = 0U;
(void)memset(&ipv4auto->current_ip, 0, sizeof(struct in_addr));
ipv4auto->requested_ip.s4_addr[0] = 169U;
ipv4auto->requested_ip.s4_addr[1] = 254U;
ipv4auto->requested_ip.s4_addr[2] = sys_rand32_get() % 254;
ipv4auto->requested_ip.s4_addr[3] = sys_rand32_get() % 254;
NET_DBG("%s: Starting probe for 169.254.%d.%d", "Init",
ipv4auto->requested_ip.s4_addr[2],
ipv4auto->requested_ip.s4_addr[3]);
ipv4_autoconf_send_probe(ipv4auto);
break;
case NET_IPV4_AUTOCONF_RENEW:
ipv4auto->probe_cnt = 0U;
ipv4auto->announce_cnt = 0U;
ipv4auto->conflict_cnt = 0U;
(void)memset(&ipv4auto->current_ip, 0, sizeof(struct in_addr));
NET_DBG("%s: Starting probe for 169.254.%d.%d", "Renew",
ipv4auto->requested_ip.s4_addr[2],
ipv4auto->requested_ip.s4_addr[3]);
ipv4_autoconf_send_probe(ipv4auto);
break;
case NET_IPV4_AUTOCONF_PROBE:
/* schedule next probe */
if (ipv4auto->probe_cnt <= (IPV4_AUTOCONF_PROBE_NUM - 1)) {
ipv4_autoconf_send_probe(ipv4auto);
break;
}
__fallthrough;
case NET_IPV4_AUTOCONF_ANNOUNCE:
ipv4_autoconf_addr_set(ipv4auto);
break;
default:
break;
}
}
static uint32_t ipv4_autoconf_get_timeout(struct net_if_ipv4_autoconf *ipv4auto)
{
switch (ipv4auto->state) {
case NET_IPV4_AUTOCONF_PROBE:
if (ipv4auto->conflict_cnt >= IPV4_AUTOCONF_MAX_CONFLICTS) {
NET_DBG("Rate limiting");
return MSEC_PER_SEC * IPV4_AUTOCONF_RATE_LIMIT_INTERVAL;
} else if (ipv4auto->probe_cnt == IPV4_AUTOCONF_PROBE_NUM) {
return MSEC_PER_SEC * IPV4_AUTOCONF_ANNOUNCE_INTERVAL;
}
return IPV4_AUTOCONF_PROBE_WAIT * MSEC_PER_SEC +
(sys_rand32_get() % MSEC_PER_SEC);
case NET_IPV4_AUTOCONF_ANNOUNCE:
return MSEC_PER_SEC * IPV4_AUTOCONF_ANNOUNCE_INTERVAL;
default:
break;
}
return 0;
}
static void ipv4_autoconf_submit_work(uint32_t timeout)
{
k_work_cancel_delayable(&ipv4auto_timer);
k_work_reschedule(&ipv4auto_timer, K_MSEC(timeout));
NET_DBG("Next wakeup in %d ms",
k_ticks_to_ms_ceil32(
k_work_delayable_remaining_get(&ipv4auto_timer)));
}
static bool ipv4_autoconf_check_timeout(int64_t start, uint32_t time, int64_t timeout)
{
start += time;
if (start < 0) {
start = -start;
}
if (start > timeout) {
return false;
}
return true;
}
static bool ipv4_autoconf_timedout(struct net_if_ipv4_autoconf *ipv4auto,
int64_t timeout)
{
return ipv4_autoconf_check_timeout(ipv4auto->timer_start,
ipv4auto->timer_timeout,
timeout);
}
static uint32_t ipv4_autoconf_manage_timeouts(
struct net_if_ipv4_autoconf *ipv4auto,
int64_t timeout)
{
if (ipv4_autoconf_timedout(ipv4auto, timeout)) {
ipv4_autoconf_send(ipv4auto);
}
ipv4auto->timer_timeout = ipv4_autoconf_get_timeout(ipv4auto);
return ipv4auto->timer_timeout;
}
static void ipv4_autoconf_timeout(struct k_work *work)
{
uint32_t timeout_update = UINT32_MAX - 1;
int64_t timeout = k_uptime_get();
struct net_if_ipv4_autoconf *current, *next;
ARG_UNUSED(work);
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&ipv4auto_ifaces, current, next,
node) {
uint32_t next_timeout;
next_timeout = ipv4_autoconf_manage_timeouts(current, timeout);
if (next_timeout < timeout_update) {
timeout_update = next_timeout;
}
}
if (timeout_update != UINT32_MAX && timeout_update > 0) {
NET_DBG("Waiting for %u ms", timeout_update);
k_work_reschedule(&ipv4auto_timer, K_MSEC(timeout_update));
}
}
static void ipv4_autoconf_start_timer(struct net_if *iface,
struct net_if_ipv4_autoconf *ipv4auto)
{
sys_slist_append(&ipv4auto_ifaces, &ipv4auto->node);
ipv4auto->timer_start = k_uptime_get();
ipv4auto->timer_timeout = MSEC_PER_SEC * IPV4_AUTOCONF_START_DELAY;
ipv4auto->iface = iface;
ipv4_autoconf_submit_work(ipv4auto->timer_timeout);
}
void net_ipv4_autoconf_start(struct net_if *iface)
{
/* Initialize interface and start probing */
struct net_if_config *cfg;
if (!net_if_flag_is_set(iface, NET_IF_IPV4)) {
return;
}
cfg = net_if_get_config(iface);
if (!cfg) {
return;
}
/* Remove the existing registration if found */
if (cfg->ipv4auto.iface == iface) {
net_ipv4_autoconf_reset(iface);
}
NET_DBG("Starting IPv4 autoconf for iface %p", iface);
if (cfg->ipv4auto.state == NET_IPV4_AUTOCONF_ASSIGNED) {
cfg->ipv4auto.state = NET_IPV4_AUTOCONF_RENEW;
} else {
cfg->ipv4auto.state = NET_IPV4_AUTOCONF_INIT;
}
ipv4_autoconf_start_timer(iface, &cfg->ipv4auto);
}
void net_ipv4_autoconf_reset(struct net_if *iface)
{
struct net_if_config *cfg;
cfg = net_if_get_config(iface);
if (!cfg) {
return;
}
/* Initialize interface and start probing */
if (cfg->ipv4auto.state == NET_IPV4_AUTOCONF_ASSIGNED) {
net_if_ipv4_addr_rm(iface, &cfg->ipv4auto.current_ip);
}
NET_DBG("Autoconf reset for %p", iface);
/* Cancel any ongoing probing/announcing attempt*/
sys_slist_find_and_remove(&ipv4auto_ifaces, &cfg->ipv4auto.node);
if (sys_slist_is_empty(&ipv4auto_ifaces)) {
k_work_cancel_delayable(&ipv4auto_timer);
}
}
void net_ipv4_autoconf_init(void)
{
k_work_init_delayable(&ipv4auto_timer, ipv4_autoconf_timeout);
}