/** @file * @brief IPv4 autoconf related functions */ /* * Copyright (c) 2017 Matthias Boesl * Copyright (c) 2018 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_ipv4_autoconf, CONFIG_NET_IPV4_AUTO_LOG_LEVEL); #include "net_private.h" #include #include "../l2/ethernet/arp.h" #include #include #include #include #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 = net_if_get_config(iface); 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(&arp_hdr->dst_ipaddr, &cfg->ipv4auto.requested_ip)) { /* No conflict */ return NET_CONTINUE; } if (!net_ipv4_addr_cmp(&arp_hdr->src_ipaddr, &cfg->ipv4auto.requested_ip)) { /* No need to defend */ return NET_CONTINUE; } NET_DBG("Conflict detected from %s for %s, state %d", log_strdup(net_sprint_ll_addr((uint8_t *)&arp_hdr->src_hwaddr, arp_hdr->hwlen)), log_strdup(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 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: if (ipv4auto->announce_cnt <= (IPV4_AUTOCONF_ANNOUNCE_NUM - 1)) { net_ipaddr_copy(&ipv4auto->current_ip, &ipv4auto->requested_ip); ipv4_autoconf_send_announcement(ipv4auto); break; } /* 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; } ipv4auto->state = NET_IPV4_AUTOCONF_ASSIGNED; 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; 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); }