blob: aa18a27ef63df16f236a24c7a2f15145e57356cf [file] [log] [blame]
/** @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/random.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);
}