1006 lines
25 KiB
C
1006 lines
25 KiB
C
/** @file
|
|
* @brief ARP related functions
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2016 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(net_arp, CONFIG_NET_ARP_LOG_LEVEL);
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/net/net_core.h>
|
|
#include <zephyr/net/net_pkt.h>
|
|
#include <zephyr/net/net_if.h>
|
|
#include <zephyr/net/net_stats.h>
|
|
#include <zephyr/net/net_mgmt.h>
|
|
|
|
#include "arp.h"
|
|
#include "net_private.h"
|
|
|
|
#define NET_BUF_TIMEOUT K_MSEC(100)
|
|
#define ARP_REQUEST_TIMEOUT (2 * MSEC_PER_SEC)
|
|
|
|
static bool arp_cache_initialized;
|
|
static struct arp_entry arp_entries[CONFIG_NET_ARP_TABLE_SIZE];
|
|
|
|
static sys_slist_t arp_free_entries;
|
|
static sys_slist_t arp_pending_entries;
|
|
static sys_slist_t arp_table;
|
|
|
|
static struct k_work_delayable arp_request_timer;
|
|
|
|
static struct k_mutex arp_mutex;
|
|
|
|
#if defined(CONFIG_NET_ARP_GRATUITOUS_TRANSMISSION)
|
|
static struct net_mgmt_event_callback iface_event_cb;
|
|
static struct net_mgmt_event_callback ipv4_event_cb;
|
|
static struct k_work_delayable arp_gratuitous_work;
|
|
#endif /* defined(CONFIG_NET_ARP_GRATUITOUS_TRANSMISSION) */
|
|
|
|
static void arp_entry_cleanup(struct arp_entry *entry, bool pending)
|
|
{
|
|
NET_DBG("entry %p", entry);
|
|
|
|
if (pending) {
|
|
struct net_pkt *pkt;
|
|
|
|
while (!k_fifo_is_empty(&entry->pending_queue)) {
|
|
pkt = k_fifo_get(&entry->pending_queue, K_FOREVER);
|
|
NET_DBG("Releasing pending pkt %p (ref %ld)",
|
|
pkt,
|
|
atomic_get(&pkt->atomic_ref) - 1);
|
|
net_pkt_unref(pkt);
|
|
}
|
|
}
|
|
|
|
entry->iface = NULL;
|
|
|
|
(void)memset(&entry->ip, 0, sizeof(struct in_addr));
|
|
(void)memset(&entry->eth, 0, sizeof(struct net_eth_addr));
|
|
}
|
|
|
|
static struct arp_entry *arp_entry_find(sys_slist_t *list,
|
|
struct net_if *iface,
|
|
struct in_addr *dst,
|
|
sys_snode_t **previous)
|
|
{
|
|
struct arp_entry *entry;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(list, entry, node) {
|
|
NET_DBG("iface %d (%p) dst %s",
|
|
net_if_get_by_iface(iface), iface,
|
|
net_sprint_ipv4_addr(&entry->ip));
|
|
|
|
if (entry->iface == iface &&
|
|
net_ipv4_addr_cmp(&entry->ip, dst)) {
|
|
return entry;
|
|
}
|
|
|
|
if (previous) {
|
|
*previous = &entry->node;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline struct arp_entry *arp_entry_find_move_first(struct net_if *iface,
|
|
struct in_addr *dst)
|
|
{
|
|
sys_snode_t *prev = NULL;
|
|
struct arp_entry *entry;
|
|
|
|
NET_DBG("dst %s", net_sprint_ipv4_addr(dst));
|
|
|
|
entry = arp_entry_find(&arp_table, iface, dst, &prev);
|
|
if (entry) {
|
|
/* Let's assume the target is going to be accessed
|
|
* more than once here in a short time frame. So we
|
|
* place the entry first in position into the table
|
|
* in order to reduce subsequent find.
|
|
*/
|
|
if (&entry->node != sys_slist_peek_head(&arp_table)) {
|
|
sys_slist_remove(&arp_table, prev, &entry->node);
|
|
sys_slist_prepend(&arp_table, &entry->node);
|
|
}
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
static inline
|
|
struct arp_entry *arp_entry_find_pending(struct net_if *iface,
|
|
struct in_addr *dst)
|
|
{
|
|
NET_DBG("dst %s", net_sprint_ipv4_addr(dst));
|
|
|
|
return arp_entry_find(&arp_pending_entries, iface, dst, NULL);
|
|
}
|
|
|
|
static struct arp_entry *arp_entry_get_pending(struct net_if *iface,
|
|
struct in_addr *dst)
|
|
{
|
|
sys_snode_t *prev = NULL;
|
|
struct arp_entry *entry;
|
|
|
|
NET_DBG("dst %s", net_sprint_ipv4_addr(dst));
|
|
|
|
entry = arp_entry_find(&arp_pending_entries, iface, dst, &prev);
|
|
if (entry) {
|
|
/* We remove the entry from the pending list */
|
|
sys_slist_remove(&arp_pending_entries, prev, &entry->node);
|
|
}
|
|
|
|
if (sys_slist_is_empty(&arp_pending_entries)) {
|
|
k_work_cancel_delayable(&arp_request_timer);
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
static struct arp_entry *arp_entry_get_free(void)
|
|
{
|
|
sys_snode_t *node;
|
|
|
|
node = sys_slist_peek_head(&arp_free_entries);
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
|
|
/* We remove the node from the free list */
|
|
sys_slist_remove(&arp_free_entries, NULL, node);
|
|
|
|
return CONTAINER_OF(node, struct arp_entry, node);
|
|
}
|
|
|
|
static struct arp_entry *arp_entry_get_last_from_table(void)
|
|
{
|
|
sys_snode_t *node;
|
|
|
|
/* We assume last entry is the oldest one,
|
|
* so is the preferred one to be taken out.
|
|
*/
|
|
|
|
node = sys_slist_peek_tail(&arp_table);
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
|
|
sys_slist_find_and_remove(&arp_table, node);
|
|
|
|
return CONTAINER_OF(node, struct arp_entry, node);
|
|
}
|
|
|
|
|
|
static void arp_entry_register_pending(struct arp_entry *entry)
|
|
{
|
|
NET_DBG("dst %s", net_sprint_ipv4_addr(&entry->ip));
|
|
|
|
sys_slist_append(&arp_pending_entries, &entry->node);
|
|
|
|
entry->req_start = k_uptime_get_32();
|
|
|
|
/* Let's start the timer if necessary */
|
|
if (!k_work_delayable_remaining_get(&arp_request_timer)) {
|
|
k_work_reschedule(&arp_request_timer,
|
|
K_MSEC(ARP_REQUEST_TIMEOUT));
|
|
}
|
|
}
|
|
|
|
static void arp_request_timeout(struct k_work *work)
|
|
{
|
|
uint32_t current = k_uptime_get_32();
|
|
struct arp_entry *entry, *next;
|
|
|
|
ARG_UNUSED(work);
|
|
|
|
k_mutex_lock(&arp_mutex, K_FOREVER);
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&arp_pending_entries,
|
|
entry, next, node) {
|
|
if ((int32_t)(entry->req_start +
|
|
ARP_REQUEST_TIMEOUT - current) > 0) {
|
|
break;
|
|
}
|
|
|
|
arp_entry_cleanup(entry, true);
|
|
|
|
sys_slist_remove(&arp_pending_entries, NULL, &entry->node);
|
|
sys_slist_append(&arp_free_entries, &entry->node);
|
|
|
|
entry = NULL;
|
|
}
|
|
|
|
if (entry) {
|
|
k_work_reschedule(&arp_request_timer,
|
|
K_MSEC(entry->req_start +
|
|
ARP_REQUEST_TIMEOUT - current));
|
|
}
|
|
|
|
k_mutex_unlock(&arp_mutex);
|
|
}
|
|
|
|
static inline struct in_addr *if_get_addr(struct net_if *iface,
|
|
struct in_addr *addr)
|
|
{
|
|
struct net_if_ipv4 *ipv4 = iface->config.ip.ipv4;
|
|
|
|
if (!ipv4) {
|
|
return NULL;
|
|
}
|
|
|
|
ARRAY_FOR_EACH(ipv4->unicast, i) {
|
|
if (ipv4->unicast[i].ipv4.is_used &&
|
|
ipv4->unicast[i].ipv4.address.family == AF_INET &&
|
|
ipv4->unicast[i].ipv4.addr_state == NET_ADDR_PREFERRED &&
|
|
(!addr ||
|
|
net_ipv4_addr_cmp(addr,
|
|
&ipv4->unicast[i].ipv4.address.in_addr))) {
|
|
return &ipv4->unicast[i].ipv4.address.in_addr;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline struct net_pkt *arp_prepare(struct net_if *iface,
|
|
struct in_addr *next_addr,
|
|
struct arp_entry *entry,
|
|
struct net_pkt *pending,
|
|
struct in_addr *current_ip)
|
|
{
|
|
struct net_arp_hdr *hdr;
|
|
struct in_addr *my_addr;
|
|
struct net_pkt *pkt;
|
|
|
|
if (current_ip) {
|
|
/* This is the IPv4 autoconf case where we have already
|
|
* things setup so no need to allocate new net_pkt
|
|
*/
|
|
pkt = pending;
|
|
} else {
|
|
pkt = net_pkt_alloc_with_buffer(iface,
|
|
sizeof(struct net_arp_hdr),
|
|
AF_UNSPEC, 0, NET_BUF_TIMEOUT);
|
|
if (!pkt) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Avoid recursive loop with network packet capturing */
|
|
if (IS_ENABLED(CONFIG_NET_CAPTURE) && pending) {
|
|
net_pkt_set_captured(pkt, net_pkt_is_captured(pending));
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_NET_VLAN) && pending) {
|
|
net_pkt_set_vlan_tag(pkt, net_pkt_vlan_tag(pending));
|
|
}
|
|
}
|
|
|
|
net_buf_add(pkt->buffer, sizeof(struct net_arp_hdr));
|
|
|
|
hdr = NET_ARP_HDR(pkt);
|
|
|
|
/* If entry is not set, then we are just about to send
|
|
* an ARP request using the data in pending net_pkt.
|
|
* This can happen if there is already a pending ARP
|
|
* request and we want to send it again.
|
|
*/
|
|
if (entry) {
|
|
if (!net_pkt_ipv4_acd(pkt)) {
|
|
k_fifo_put(&entry->pending_queue, net_pkt_ref(pending));
|
|
}
|
|
|
|
entry->iface = net_pkt_iface(pkt);
|
|
|
|
net_ipaddr_copy(&entry->ip, next_addr);
|
|
|
|
net_pkt_lladdr_src(pkt)->addr =
|
|
(uint8_t *)net_if_get_link_addr(entry->iface)->addr;
|
|
|
|
arp_entry_register_pending(entry);
|
|
} else {
|
|
net_pkt_lladdr_src(pkt)->addr =
|
|
(uint8_t *)net_if_get_link_addr(iface)->addr;
|
|
}
|
|
|
|
net_pkt_lladdr_src(pkt)->len = sizeof(struct net_eth_addr);
|
|
|
|
net_pkt_lladdr_dst(pkt)->addr = (uint8_t *)net_eth_broadcast_addr();
|
|
net_pkt_lladdr_dst(pkt)->len = sizeof(struct net_eth_addr);
|
|
|
|
hdr->hwtype = htons(NET_ARP_HTYPE_ETH);
|
|
hdr->protocol = htons(NET_ETH_PTYPE_IP);
|
|
hdr->hwlen = sizeof(struct net_eth_addr);
|
|
hdr->protolen = sizeof(struct in_addr);
|
|
hdr->opcode = htons(NET_ARP_REQUEST);
|
|
|
|
(void)memset(&hdr->dst_hwaddr.addr, 0x00, sizeof(struct net_eth_addr));
|
|
|
|
net_ipv4_addr_copy_raw(hdr->dst_ipaddr, (uint8_t *)next_addr);
|
|
|
|
memcpy(hdr->src_hwaddr.addr, net_pkt_lladdr_src(pkt)->addr,
|
|
sizeof(struct net_eth_addr));
|
|
|
|
if (net_pkt_ipv4_acd(pkt)) {
|
|
my_addr = current_ip;
|
|
} else if (!entry) {
|
|
my_addr = (struct in_addr *)NET_IPV4_HDR(pending)->src;
|
|
} else {
|
|
my_addr = if_get_addr(entry->iface, current_ip);
|
|
}
|
|
|
|
if (my_addr) {
|
|
net_ipv4_addr_copy_raw(hdr->src_ipaddr, (uint8_t *)my_addr);
|
|
} else {
|
|
(void)memset(&hdr->src_ipaddr, 0, sizeof(struct in_addr));
|
|
}
|
|
|
|
NET_DBG("Generating request for %s", net_sprint_ipv4_addr(next_addr));
|
|
return pkt;
|
|
}
|
|
|
|
struct net_pkt *net_arp_prepare(struct net_pkt *pkt,
|
|
struct in_addr *request_ip,
|
|
struct in_addr *current_ip)
|
|
{
|
|
bool is_ipv4_ll_used = false;
|
|
struct arp_entry *entry;
|
|
struct in_addr *addr;
|
|
|
|
if (!pkt || !pkt->buffer) {
|
|
return NULL;
|
|
}
|
|
|
|
if (net_pkt_ipv4_acd(pkt)) {
|
|
return arp_prepare(net_pkt_iface(pkt), request_ip, NULL,
|
|
pkt, current_ip);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_NET_IPV4_AUTO)) {
|
|
is_ipv4_ll_used = net_ipv4_is_ll_addr((struct in_addr *)
|
|
&NET_IPV4_HDR(pkt)->src) ||
|
|
net_ipv4_is_ll_addr((struct in_addr *)
|
|
&NET_IPV4_HDR(pkt)->dst);
|
|
}
|
|
|
|
/* Is the destination in the local network, if not route via
|
|
* the gateway address.
|
|
*/
|
|
if (!current_ip && !is_ipv4_ll_used &&
|
|
!net_if_ipv4_addr_mask_cmp(net_pkt_iface(pkt), request_ip)) {
|
|
struct net_if_ipv4 *ipv4 = net_pkt_iface(pkt)->config.ip.ipv4;
|
|
|
|
if (ipv4) {
|
|
addr = &ipv4->gw;
|
|
if (net_ipv4_is_addr_unspecified(addr)) {
|
|
NET_ERR("Gateway not set for iface %p",
|
|
net_pkt_iface(pkt));
|
|
|
|
return NULL;
|
|
}
|
|
} else {
|
|
addr = request_ip;
|
|
}
|
|
} else {
|
|
addr = request_ip;
|
|
}
|
|
|
|
k_mutex_lock(&arp_mutex, K_FOREVER);
|
|
|
|
/* If the destination address is already known, we do not need
|
|
* to send any ARP packet.
|
|
*/
|
|
entry = arp_entry_find_move_first(net_pkt_iface(pkt), addr);
|
|
if (!entry) {
|
|
struct net_pkt *req;
|
|
|
|
entry = arp_entry_find_pending(net_pkt_iface(pkt), addr);
|
|
if (!entry) {
|
|
/* No pending, let's try to get a new entry */
|
|
entry = arp_entry_get_free();
|
|
if (!entry) {
|
|
/* Then let's take one from table? */
|
|
entry = arp_entry_get_last_from_table();
|
|
}
|
|
} else {
|
|
/* There is a pending ARP request already, check if this packet is already
|
|
* in the pending list and if so, resend the request, otherwise just
|
|
* append the packet to the request fifo list.
|
|
*/
|
|
if (k_queue_unique_append(&entry->pending_queue._queue,
|
|
net_pkt_ref(pkt))) {
|
|
NET_DBG("Pending ARP request for %s, queuing pkt %p",
|
|
net_sprint_ipv4_addr(addr), pkt);
|
|
k_mutex_unlock(&arp_mutex);
|
|
return NULL;
|
|
}
|
|
|
|
entry = NULL;
|
|
}
|
|
|
|
req = arp_prepare(net_pkt_iface(pkt), addr, entry, pkt,
|
|
current_ip);
|
|
|
|
if (!entry) {
|
|
/* We cannot send the packet, the ARP cache is full
|
|
* or there is already a pending query to this IP
|
|
* address, so this packet must be discarded.
|
|
*/
|
|
NET_DBG("Resending ARP %p", req);
|
|
}
|
|
|
|
if (!req && entry) {
|
|
/* Add the arp entry back to arp_free_entries, to avoid the
|
|
* arp entry is leak due to ARP packet allocated failed.
|
|
*/
|
|
sys_slist_prepend(&arp_free_entries, &entry->node);
|
|
}
|
|
|
|
k_mutex_unlock(&arp_mutex);
|
|
return req;
|
|
}
|
|
|
|
k_mutex_unlock(&arp_mutex);
|
|
|
|
net_pkt_lladdr_src(pkt)->addr =
|
|
(uint8_t *)net_if_get_link_addr(entry->iface)->addr;
|
|
net_pkt_lladdr_src(pkt)->len = sizeof(struct net_eth_addr);
|
|
|
|
net_pkt_lladdr_dst(pkt)->addr = (uint8_t *)&entry->eth;
|
|
net_pkt_lladdr_dst(pkt)->len = sizeof(struct net_eth_addr);
|
|
|
|
NET_DBG("ARP using ll %s for IP %s",
|
|
net_sprint_ll_addr(net_pkt_lladdr_dst(pkt)->addr,
|
|
sizeof(struct net_eth_addr)),
|
|
net_sprint_ipv4_addr(&NET_IPV4_HDR(pkt)->dst));
|
|
|
|
return pkt;
|
|
}
|
|
|
|
static void arp_gratuitous(struct net_if *iface,
|
|
struct in_addr *src,
|
|
struct net_eth_addr *hwaddr)
|
|
{
|
|
sys_snode_t *prev = NULL;
|
|
struct arp_entry *entry;
|
|
|
|
entry = arp_entry_find(&arp_table, iface, src, &prev);
|
|
if (entry) {
|
|
NET_DBG("Gratuitous ARP hwaddr %s -> %s",
|
|
net_sprint_ll_addr((const uint8_t *)&entry->eth,
|
|
sizeof(struct net_eth_addr)),
|
|
net_sprint_ll_addr((const uint8_t *)hwaddr,
|
|
sizeof(struct net_eth_addr)));
|
|
|
|
memcpy(&entry->eth, hwaddr, sizeof(struct net_eth_addr));
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_NET_ARP_GRATUITOUS_TRANSMISSION)
|
|
static void arp_gratuitous_send(struct net_if *iface,
|
|
struct in_addr *ipaddr)
|
|
{
|
|
struct net_arp_hdr *hdr;
|
|
struct net_pkt *pkt;
|
|
|
|
pkt = net_pkt_alloc_with_buffer(iface, sizeof(struct net_arp_hdr),
|
|
AF_UNSPEC, 0, NET_BUF_TIMEOUT);
|
|
if (!pkt) {
|
|
return;
|
|
}
|
|
|
|
net_buf_add(pkt->buffer, sizeof(struct net_arp_hdr));
|
|
net_pkt_set_vlan_tag(pkt, net_eth_get_vlan_tag(iface));
|
|
|
|
hdr = NET_ARP_HDR(pkt);
|
|
|
|
hdr->hwtype = htons(NET_ARP_HTYPE_ETH);
|
|
hdr->protocol = htons(NET_ETH_PTYPE_IP);
|
|
hdr->hwlen = sizeof(struct net_eth_addr);
|
|
hdr->protolen = sizeof(struct in_addr);
|
|
hdr->opcode = htons(NET_ARP_REQUEST);
|
|
|
|
memcpy(&hdr->dst_hwaddr.addr, net_eth_broadcast_addr(),
|
|
sizeof(struct net_eth_addr));
|
|
memcpy(&hdr->src_hwaddr.addr, net_if_get_link_addr(iface)->addr,
|
|
sizeof(struct net_eth_addr));
|
|
|
|
net_ipv4_addr_copy_raw(hdr->dst_ipaddr, (uint8_t *)ipaddr);
|
|
net_ipv4_addr_copy_raw(hdr->src_ipaddr, (uint8_t *)ipaddr);
|
|
|
|
net_pkt_lladdr_src(pkt)->addr = net_if_get_link_addr(iface)->addr;
|
|
net_pkt_lladdr_src(pkt)->len = sizeof(struct net_eth_addr);
|
|
|
|
net_pkt_lladdr_dst(pkt)->addr = (uint8_t *)net_eth_broadcast_addr();
|
|
net_pkt_lladdr_dst(pkt)->len = sizeof(struct net_eth_addr);
|
|
|
|
NET_DBG("Sending gratuitous ARP pkt %p", pkt);
|
|
|
|
if (net_if_send_data(iface, pkt) == NET_DROP) {
|
|
net_pkt_unref(pkt);
|
|
}
|
|
}
|
|
|
|
static void notify_all_ipv4_addr(struct net_if *iface)
|
|
{
|
|
struct net_if_ipv4 *ipv4 = iface->config.ip.ipv4;
|
|
int i;
|
|
|
|
if (!ipv4) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < NET_IF_MAX_IPV4_ADDR; i++) {
|
|
if (ipv4->unicast[i].ipv4.is_used &&
|
|
ipv4->unicast[i].ipv4.address.family == AF_INET &&
|
|
ipv4->unicast[i].ipv4.addr_state == NET_ADDR_PREFERRED) {
|
|
arp_gratuitous_send(iface,
|
|
&ipv4->unicast[i].ipv4.address.in_addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void iface_event_handler(struct net_mgmt_event_callback *cb,
|
|
uint32_t mgmt_event, struct net_if *iface)
|
|
{
|
|
ARG_UNUSED(cb);
|
|
|
|
if (!(net_if_l2(iface) == &NET_L2_GET_NAME(ETHERNET) ||
|
|
net_eth_is_vlan_interface(iface))) {
|
|
return;
|
|
}
|
|
|
|
if (mgmt_event != NET_EVENT_IF_UP) {
|
|
return;
|
|
}
|
|
|
|
notify_all_ipv4_addr(iface);
|
|
}
|
|
|
|
static void ipv4_event_handler(struct net_mgmt_event_callback *cb,
|
|
uint32_t mgmt_event, struct net_if *iface)
|
|
{
|
|
struct in_addr *ipaddr;
|
|
|
|
if (!(net_if_l2(iface) == &NET_L2_GET_NAME(ETHERNET) ||
|
|
net_eth_is_vlan_interface(iface))) {
|
|
return;
|
|
}
|
|
|
|
if (!net_if_is_up(iface)) {
|
|
return;
|
|
}
|
|
|
|
if (mgmt_event != NET_EVENT_IPV4_ADDR_ADD) {
|
|
return;
|
|
}
|
|
|
|
if (cb->info_length != sizeof(struct in_addr)) {
|
|
return;
|
|
}
|
|
|
|
ipaddr = (struct in_addr *)cb->info;
|
|
|
|
arp_gratuitous_send(iface, ipaddr);
|
|
}
|
|
|
|
static void iface_cb(struct net_if *iface, void *user_data)
|
|
{
|
|
ARG_UNUSED(user_data);
|
|
|
|
if (!(net_if_l2(iface) == &NET_L2_GET_NAME(ETHERNET) ||
|
|
net_eth_is_vlan_interface(iface))) {
|
|
return;
|
|
}
|
|
|
|
if (!net_if_is_up(iface)) {
|
|
return;
|
|
}
|
|
|
|
notify_all_ipv4_addr(iface);
|
|
}
|
|
|
|
static void arp_gratuitous_work_handler(struct k_work *work)
|
|
{
|
|
ARG_UNUSED(work);
|
|
|
|
net_if_foreach(iface_cb, NULL);
|
|
|
|
k_work_reschedule(&arp_gratuitous_work,
|
|
K_SECONDS(CONFIG_NET_ARP_GRATUITOUS_INTERVAL));
|
|
}
|
|
#endif /* defined(CONFIG_NET_ARP_GRATUITOUS_TRANSMISSION) */
|
|
|
|
void net_arp_update(struct net_if *iface,
|
|
struct in_addr *src,
|
|
struct net_eth_addr *hwaddr,
|
|
bool gratuitous,
|
|
bool force)
|
|
{
|
|
struct arp_entry *entry;
|
|
struct net_pkt *pkt;
|
|
|
|
NET_DBG("iface %d (%p) src %s", net_if_get_by_iface(iface), iface,
|
|
net_sprint_ipv4_addr(src));
|
|
net_if_tx_lock(iface);
|
|
k_mutex_lock(&arp_mutex, K_FOREVER);
|
|
|
|
entry = arp_entry_get_pending(iface, src);
|
|
if (!entry) {
|
|
if (IS_ENABLED(CONFIG_NET_ARP_GRATUITOUS) && gratuitous) {
|
|
arp_gratuitous(iface, src, hwaddr);
|
|
}
|
|
|
|
if (force) {
|
|
sys_snode_t *prev = NULL;
|
|
struct arp_entry *arp_ent;
|
|
|
|
arp_ent = arp_entry_find(&arp_table, iface, src, &prev);
|
|
if (arp_ent) {
|
|
memcpy(&arp_ent->eth, hwaddr,
|
|
sizeof(struct net_eth_addr));
|
|
} else {
|
|
/* Add new entry as it was not found and force
|
|
* was set.
|
|
*/
|
|
arp_ent = arp_entry_get_free();
|
|
if (!arp_ent) {
|
|
/* Then let's take one from table? */
|
|
arp_ent = arp_entry_get_last_from_table();
|
|
}
|
|
|
|
if (arp_ent) {
|
|
arp_ent->req_start = k_uptime_get_32();
|
|
arp_ent->iface = iface;
|
|
net_ipaddr_copy(&arp_ent->ip, src);
|
|
memcpy(&arp_ent->eth, hwaddr, sizeof(arp_ent->eth));
|
|
sys_slist_prepend(&arp_table, &arp_ent->node);
|
|
}
|
|
}
|
|
}
|
|
|
|
k_mutex_unlock(&arp_mutex);
|
|
net_if_tx_unlock(iface);
|
|
return;
|
|
}
|
|
|
|
memcpy(&entry->eth, hwaddr, sizeof(struct net_eth_addr));
|
|
|
|
/* Inserting entry into the table */
|
|
sys_slist_prepend(&arp_table, &entry->node);
|
|
|
|
while (!k_fifo_is_empty(&entry->pending_queue)) {
|
|
int ret;
|
|
|
|
pkt = k_fifo_get(&entry->pending_queue, K_FOREVER);
|
|
|
|
/* Set the dst in the pending packet */
|
|
net_pkt_lladdr_dst(pkt)->len = sizeof(struct net_eth_addr);
|
|
net_pkt_lladdr_dst(pkt)->addr =
|
|
(uint8_t *) &NET_ETH_HDR(pkt)->dst.addr;
|
|
|
|
NET_DBG("iface %d (%p) dst %s pending %p frag %p",
|
|
net_if_get_by_iface(iface), iface,
|
|
net_sprint_ipv4_addr(&entry->ip),
|
|
pkt, pkt->frags);
|
|
|
|
/* We directly send the packet without first queueing it.
|
|
* The pkt has already been queued for sending, once by
|
|
* net_if and second time in the ARP queue. We must not
|
|
* queue it twice in net_if so that the statistics of
|
|
* the pkt are not counted twice and the packet filter
|
|
* callbacks are only called once.
|
|
*/
|
|
ret = net_if_l2(iface)->send(iface, pkt);
|
|
if (ret < 0) {
|
|
net_pkt_unref(pkt);
|
|
}
|
|
}
|
|
|
|
k_mutex_unlock(&arp_mutex);
|
|
net_if_tx_unlock(iface);
|
|
}
|
|
|
|
static inline struct net_pkt *arp_prepare_reply(struct net_if *iface,
|
|
struct net_pkt *req,
|
|
struct net_eth_hdr *eth_query,
|
|
struct net_eth_addr *dst_addr)
|
|
{
|
|
struct net_arp_hdr *hdr, *query;
|
|
struct net_pkt *pkt;
|
|
|
|
pkt = net_pkt_alloc_with_buffer(iface, sizeof(struct net_arp_hdr),
|
|
AF_UNSPEC, 0, NET_BUF_TIMEOUT);
|
|
if (!pkt) {
|
|
return NULL;
|
|
}
|
|
|
|
net_buf_add(pkt->buffer, sizeof(struct net_arp_hdr));
|
|
|
|
hdr = NET_ARP_HDR(pkt);
|
|
query = NET_ARP_HDR(req);
|
|
|
|
if (IS_ENABLED(CONFIG_NET_VLAN)) {
|
|
net_pkt_set_vlan_tag(pkt, net_pkt_vlan_tag(req));
|
|
}
|
|
|
|
hdr->hwtype = htons(NET_ARP_HTYPE_ETH);
|
|
hdr->protocol = htons(NET_ETH_PTYPE_IP);
|
|
hdr->hwlen = sizeof(struct net_eth_addr);
|
|
hdr->protolen = sizeof(struct in_addr);
|
|
hdr->opcode = htons(NET_ARP_REPLY);
|
|
|
|
memcpy(&hdr->dst_hwaddr.addr, &dst_addr->addr,
|
|
sizeof(struct net_eth_addr));
|
|
memcpy(&hdr->src_hwaddr.addr, net_if_get_link_addr(iface)->addr,
|
|
sizeof(struct net_eth_addr));
|
|
|
|
net_ipv4_addr_copy_raw(hdr->dst_ipaddr, query->src_ipaddr);
|
|
net_ipv4_addr_copy_raw(hdr->src_ipaddr, query->dst_ipaddr);
|
|
|
|
net_pkt_lladdr_src(pkt)->addr = net_if_get_link_addr(iface)->addr;
|
|
net_pkt_lladdr_src(pkt)->len = sizeof(struct net_eth_addr);
|
|
|
|
net_pkt_lladdr_dst(pkt)->addr = (uint8_t *)&hdr->dst_hwaddr.addr;
|
|
net_pkt_lladdr_dst(pkt)->len = sizeof(struct net_eth_addr);
|
|
|
|
return pkt;
|
|
}
|
|
|
|
static bool arp_hdr_check(struct net_arp_hdr *arp_hdr)
|
|
{
|
|
if (ntohs(arp_hdr->hwtype) != NET_ARP_HTYPE_ETH ||
|
|
ntohs(arp_hdr->protocol) != NET_ETH_PTYPE_IP ||
|
|
arp_hdr->hwlen != sizeof(struct net_eth_addr) ||
|
|
arp_hdr->protolen != NET_ARP_IPV4_PTYPE_SIZE ||
|
|
net_ipv4_is_addr_loopback((struct in_addr *)arp_hdr->src_ipaddr)) {
|
|
NET_DBG("DROP: Invalid ARP header");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
enum net_verdict net_arp_input(struct net_pkt *pkt,
|
|
struct net_eth_hdr *eth_hdr)
|
|
{
|
|
struct net_eth_addr *dst_hw_addr;
|
|
struct net_arp_hdr *arp_hdr;
|
|
struct net_pkt *reply;
|
|
struct in_addr *addr;
|
|
|
|
if (net_pkt_get_len(pkt) < (sizeof(struct net_arp_hdr) -
|
|
(net_pkt_ip_data(pkt) - (uint8_t *)eth_hdr))) {
|
|
NET_DBG("Invalid ARP header (len %zu, min %zu bytes) %p",
|
|
net_pkt_get_len(pkt), sizeof(struct net_arp_hdr) -
|
|
(net_pkt_ip_data(pkt) - (uint8_t *)eth_hdr), pkt);
|
|
return NET_DROP;
|
|
}
|
|
|
|
arp_hdr = NET_ARP_HDR(pkt);
|
|
if (!arp_hdr_check(arp_hdr)) {
|
|
return NET_DROP;
|
|
}
|
|
|
|
switch (ntohs(arp_hdr->opcode)) {
|
|
case NET_ARP_REQUEST:
|
|
/* If ARP request sender hw address is our address,
|
|
* we must drop the packet.
|
|
*/
|
|
if (memcmp(&arp_hdr->src_hwaddr,
|
|
net_if_get_link_addr(net_pkt_iface(pkt))->addr,
|
|
sizeof(struct net_eth_addr)) == 0) {
|
|
return NET_DROP;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_NET_ARP_GRATUITOUS)) {
|
|
if (net_eth_is_addr_broadcast(ð_hdr->dst) &&
|
|
(net_eth_is_addr_broadcast(&arp_hdr->dst_hwaddr) ||
|
|
net_eth_is_addr_all_zeroes(&arp_hdr->dst_hwaddr)) &&
|
|
net_ipv4_addr_cmp_raw(arp_hdr->dst_ipaddr,
|
|
arp_hdr->src_ipaddr)) {
|
|
/* If the IP address is in our cache,
|
|
* then update it here.
|
|
*/
|
|
net_arp_update(net_pkt_iface(pkt),
|
|
(struct in_addr *)arp_hdr->src_ipaddr,
|
|
&arp_hdr->src_hwaddr,
|
|
true, false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Discard ARP request if Ethernet address is broadcast
|
|
* and Source IP address is Multicast address.
|
|
*/
|
|
if (memcmp(ð_hdr->dst, net_eth_broadcast_addr(),
|
|
sizeof(struct net_eth_addr)) == 0 &&
|
|
net_ipv4_is_addr_mcast((struct in_addr *)arp_hdr->src_ipaddr)) {
|
|
NET_DBG("DROP: eth addr is bcast, src addr is mcast");
|
|
return NET_DROP;
|
|
}
|
|
|
|
/* Someone wants to know our ll address */
|
|
addr = if_get_addr(net_pkt_iface(pkt),
|
|
(struct in_addr *)arp_hdr->dst_ipaddr);
|
|
if (!addr) {
|
|
/* Not for us so drop the packet silently */
|
|
return NET_DROP;
|
|
}
|
|
|
|
NET_DBG("ARP request from %s [%s] for %s",
|
|
net_sprint_ipv4_addr(&arp_hdr->src_ipaddr),
|
|
net_sprint_ll_addr((uint8_t *)&arp_hdr->src_hwaddr,
|
|
arp_hdr->hwlen),
|
|
net_sprint_ipv4_addr(&arp_hdr->dst_ipaddr));
|
|
|
|
/* Update the ARP cache if the sender MAC address has
|
|
* changed. In this case the target MAC address is all zeros
|
|
* and the target IP address is our address.
|
|
*/
|
|
if (net_eth_is_addr_unspecified(&arp_hdr->dst_hwaddr)) {
|
|
NET_DBG("Updating ARP cache for %s [%s] iface %d",
|
|
net_sprint_ipv4_addr(&arp_hdr->src_ipaddr),
|
|
net_sprint_ll_addr((uint8_t *)&arp_hdr->src_hwaddr,
|
|
arp_hdr->hwlen),
|
|
net_if_get_by_iface(net_pkt_iface(pkt)));
|
|
|
|
net_arp_update(net_pkt_iface(pkt),
|
|
(struct in_addr *)arp_hdr->src_ipaddr,
|
|
&arp_hdr->src_hwaddr,
|
|
false, true);
|
|
|
|
dst_hw_addr = &arp_hdr->src_hwaddr;
|
|
} else {
|
|
dst_hw_addr = ð_hdr->src;
|
|
}
|
|
|
|
/* Send reply */
|
|
reply = arp_prepare_reply(net_pkt_iface(pkt), pkt, eth_hdr,
|
|
dst_hw_addr);
|
|
if (reply) {
|
|
net_if_queue_tx(net_pkt_iface(reply), reply);
|
|
} else {
|
|
NET_DBG("Cannot send ARP reply");
|
|
}
|
|
break;
|
|
|
|
case NET_ARP_REPLY:
|
|
if (net_ipv4_is_my_addr((struct in_addr *)arp_hdr->dst_ipaddr)) {
|
|
NET_DBG("Received ll %s for IP %s",
|
|
net_sprint_ll_addr(arp_hdr->src_hwaddr.addr,
|
|
sizeof(struct net_eth_addr)),
|
|
net_sprint_ipv4_addr(arp_hdr->src_ipaddr));
|
|
net_arp_update(net_pkt_iface(pkt),
|
|
(struct in_addr *)arp_hdr->src_ipaddr,
|
|
&arp_hdr->src_hwaddr,
|
|
false, false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
net_pkt_unref(pkt);
|
|
|
|
return NET_OK;
|
|
}
|
|
|
|
void net_arp_clear_cache(struct net_if *iface)
|
|
{
|
|
sys_snode_t *prev = NULL;
|
|
struct arp_entry *entry, *next;
|
|
|
|
NET_DBG("Flushing ARP table");
|
|
|
|
k_mutex_lock(&arp_mutex, K_FOREVER);
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&arp_table, entry, next, node) {
|
|
if (iface && iface != entry->iface) {
|
|
prev = &entry->node;
|
|
continue;
|
|
}
|
|
|
|
arp_entry_cleanup(entry, false);
|
|
|
|
sys_slist_remove(&arp_table, prev, &entry->node);
|
|
sys_slist_prepend(&arp_free_entries, &entry->node);
|
|
}
|
|
|
|
prev = NULL;
|
|
|
|
NET_DBG("Flushing ARP pending requests");
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&arp_pending_entries,
|
|
entry, next, node) {
|
|
if (iface && iface != entry->iface) {
|
|
prev = &entry->node;
|
|
continue;
|
|
}
|
|
|
|
arp_entry_cleanup(entry, true);
|
|
|
|
sys_slist_remove(&arp_pending_entries, prev, &entry->node);
|
|
sys_slist_prepend(&arp_free_entries, &entry->node);
|
|
}
|
|
|
|
if (sys_slist_is_empty(&arp_pending_entries)) {
|
|
k_work_cancel_delayable(&arp_request_timer);
|
|
}
|
|
|
|
k_mutex_unlock(&arp_mutex);
|
|
}
|
|
|
|
int net_arp_clear_pending(struct net_if *iface, struct in_addr *dst)
|
|
{
|
|
struct arp_entry *entry = arp_entry_find_pending(iface, dst);
|
|
|
|
if (!entry) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
arp_entry_cleanup(entry, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int net_arp_foreach(net_arp_cb_t cb, void *user_data)
|
|
{
|
|
int ret = 0;
|
|
struct arp_entry *entry;
|
|
|
|
k_mutex_lock(&arp_mutex, K_FOREVER);
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&arp_table, entry, node) {
|
|
ret++;
|
|
cb(entry, user_data);
|
|
}
|
|
|
|
k_mutex_unlock(&arp_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void net_arp_init(void)
|
|
{
|
|
int i;
|
|
|
|
if (arp_cache_initialized) {
|
|
return;
|
|
}
|
|
|
|
sys_slist_init(&arp_free_entries);
|
|
sys_slist_init(&arp_pending_entries);
|
|
sys_slist_init(&arp_table);
|
|
|
|
for (i = 0; i < CONFIG_NET_ARP_TABLE_SIZE; i++) {
|
|
/* Inserting entry as free with initialised packet queue */
|
|
k_fifo_init(&arp_entries[i].pending_queue);
|
|
sys_slist_prepend(&arp_free_entries, &arp_entries[i].node);
|
|
}
|
|
|
|
k_work_init_delayable(&arp_request_timer, arp_request_timeout);
|
|
|
|
k_mutex_init(&arp_mutex);
|
|
|
|
arp_cache_initialized = true;
|
|
|
|
#if defined(CONFIG_NET_ARP_GRATUITOUS_TRANSMISSION)
|
|
net_mgmt_init_event_callback(&iface_event_cb, iface_event_handler,
|
|
NET_EVENT_IF_UP);
|
|
net_mgmt_init_event_callback(&ipv4_event_cb, ipv4_event_handler,
|
|
NET_EVENT_IPV4_ADDR_ADD);
|
|
|
|
net_mgmt_add_event_callback(&iface_event_cb);
|
|
net_mgmt_add_event_callback(&ipv4_event_cb);
|
|
|
|
k_work_init_delayable(&arp_gratuitous_work,
|
|
arp_gratuitous_work_handler);
|
|
k_work_reschedule(&arp_gratuitous_work,
|
|
K_SECONDS(CONFIG_NET_ARP_GRATUITOUS_INTERVAL));
|
|
#endif /* defined(CONFIG_NET_ARP_GRATUITOUS_TRANSMISSION) */
|
|
}
|