mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-03 14:41:59 +00:00
When the ARP message is received when the device is starting up, the network interface might not yet have IPv4 address setup correctly. In this case, the IP address pointer could be NULL and we must not use it for anything. Fixes #752 Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
508 lines
12 KiB
C
508 lines
12 KiB
C
/** @file
|
|
* @brief ARP related functions
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2016 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#if defined(CONFIG_NET_DEBUG_ARP)
|
|
#define SYS_LOG_DOMAIN "net/arp"
|
|
#define NET_LOG_ENABLED 1
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <net/net_core.h>
|
|
#include <net/net_pkt.h>
|
|
#include <net/net_if.h>
|
|
#include <net/net_stats.h>
|
|
#include <net/arp.h>
|
|
#include "net_private.h"
|
|
|
|
struct arp_entry {
|
|
u32_t time; /* FIXME - implement timeout functionality */
|
|
struct net_if *iface;
|
|
struct net_pkt *pending;
|
|
struct in_addr ip;
|
|
struct net_eth_addr eth;
|
|
};
|
|
|
|
static struct arp_entry arp_table[CONFIG_NET_ARP_TABLE_SIZE];
|
|
|
|
static inline struct arp_entry *find_entry(struct net_if *iface,
|
|
struct in_addr *dst,
|
|
struct arp_entry **free_entry,
|
|
struct arp_entry **non_pending)
|
|
{
|
|
int i;
|
|
|
|
NET_DBG("dst %s", net_sprint_ipv4_addr(dst));
|
|
|
|
for (i = 0; i < CONFIG_NET_ARP_TABLE_SIZE; i++) {
|
|
|
|
NET_DBG("[%d] iface %p dst %s ll %s pending %p", i, iface,
|
|
net_sprint_ipv4_addr(&arp_table[i].ip),
|
|
net_sprint_ll_addr((u8_t *)&arp_table[i].eth.addr,
|
|
sizeof(struct net_eth_addr)),
|
|
arp_table[i].pending);
|
|
|
|
if (arp_table[i].iface == iface &&
|
|
net_ipv4_addr_cmp(&arp_table[i].ip, dst)) {
|
|
/* Is there already pending operation for this
|
|
* IP address.
|
|
*/
|
|
if (arp_table[i].pending) {
|
|
NET_DBG("ARP already pending to %s ll %s",
|
|
net_sprint_ipv4_addr(dst),
|
|
net_sprint_ll_addr((u8_t *)
|
|
&arp_table[i].eth.addr,
|
|
sizeof(struct net_eth_addr)));
|
|
*free_entry = NULL;
|
|
*non_pending = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
return &arp_table[i];
|
|
}
|
|
|
|
/* We return also the first free entry */
|
|
if (!*free_entry && !arp_table[i].pending &&
|
|
!arp_table[i].iface) {
|
|
*free_entry = &arp_table[i];
|
|
}
|
|
|
|
/* And also first non pending entry */
|
|
if (!*non_pending && !arp_table[i].pending) {
|
|
*non_pending = &arp_table[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline struct in_addr *if_get_addr(struct net_if *iface)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NET_IF_MAX_IPV4_ADDR; i++) {
|
|
if (iface->ipv4.unicast[i].is_used &&
|
|
iface->ipv4.unicast[i].address.family == AF_INET &&
|
|
iface->ipv4.unicast[i].addr_state == NET_ADDR_PREFERRED) {
|
|
return &iface->ipv4.unicast[i].address.in_addr;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline struct net_pkt *prepare_arp(struct net_if *iface,
|
|
struct in_addr *next_addr,
|
|
struct arp_entry *entry,
|
|
struct net_pkt *pending)
|
|
{
|
|
struct net_pkt *pkt;
|
|
struct net_buf *frag;
|
|
struct net_arp_hdr *hdr;
|
|
struct net_eth_hdr *eth;
|
|
struct in_addr *my_addr;
|
|
|
|
pkt = net_pkt_get_reserve_tx(sizeof(struct net_eth_hdr), K_FOREVER);
|
|
if (!pkt) {
|
|
goto fail;
|
|
}
|
|
|
|
frag = net_pkt_get_frag(pkt, K_FOREVER);
|
|
if (!frag) {
|
|
goto fail;
|
|
}
|
|
|
|
net_pkt_frag_add(pkt, frag);
|
|
net_pkt_set_iface(pkt, iface);
|
|
net_pkt_set_family(pkt, AF_INET);
|
|
|
|
hdr = NET_ARP_HDR(pkt);
|
|
eth = NET_ETH_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) {
|
|
entry->pending = net_pkt_ref(pending);
|
|
entry->iface = net_pkt_iface(pkt);
|
|
|
|
net_ipaddr_copy(&entry->ip, next_addr);
|
|
|
|
memcpy(ð->src.addr,
|
|
net_if_get_link_addr(entry->iface)->addr,
|
|
sizeof(struct net_eth_addr));
|
|
} else {
|
|
memcpy(ð->src.addr,
|
|
net_if_get_link_addr(iface)->addr,
|
|
sizeof(struct net_eth_addr));
|
|
}
|
|
|
|
eth->type = htons(NET_ETH_PTYPE_ARP);
|
|
memset(ð->dst.addr, 0xff, 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);
|
|
|
|
memset(&hdr->dst_hwaddr.addr, 0x00, sizeof(struct net_eth_addr));
|
|
|
|
net_ipaddr_copy(&hdr->dst_ipaddr, next_addr);
|
|
|
|
memcpy(hdr->src_hwaddr.addr, eth->src.addr,
|
|
sizeof(struct net_eth_addr));
|
|
|
|
if (entry) {
|
|
my_addr = if_get_addr(entry->iface);
|
|
} else {
|
|
my_addr = &NET_IPV4_HDR(pending)->src;
|
|
}
|
|
|
|
if (my_addr) {
|
|
net_ipaddr_copy(&hdr->src_ipaddr, my_addr);
|
|
} else {
|
|
memset(&hdr->src_ipaddr, 0, sizeof(struct in_addr));
|
|
}
|
|
|
|
net_buf_add(frag, sizeof(struct net_arp_hdr));
|
|
|
|
return pkt;
|
|
|
|
fail:
|
|
net_pkt_unref(pkt);
|
|
net_pkt_unref(pending);
|
|
return NULL;
|
|
}
|
|
|
|
struct net_pkt *net_arp_prepare(struct net_pkt *pkt)
|
|
{
|
|
struct net_buf *frag;
|
|
struct arp_entry *entry, *free_entry = NULL, *non_pending = NULL;
|
|
struct net_linkaddr *ll;
|
|
struct net_eth_hdr *hdr;
|
|
struct in_addr *addr;
|
|
|
|
if (!pkt || !pkt->frags) {
|
|
return NULL;
|
|
}
|
|
|
|
if (net_pkt_ll_reserve(pkt) != sizeof(struct net_eth_hdr)) {
|
|
/* Add the ethernet header if it is missing. */
|
|
struct net_buf *header;
|
|
struct net_linkaddr *ll;
|
|
|
|
net_pkt_set_ll_reserve(pkt, sizeof(struct net_eth_hdr));
|
|
|
|
header = net_pkt_get_frag(pkt, K_FOREVER);
|
|
|
|
hdr = (struct net_eth_hdr *)(header->data -
|
|
net_pkt_ll_reserve(pkt));
|
|
|
|
hdr->type = htons(NET_ETH_PTYPE_IP);
|
|
|
|
ll = net_pkt_ll_dst(pkt);
|
|
if (ll->addr) {
|
|
memcpy(&hdr->dst.addr, ll->addr,
|
|
sizeof(struct net_eth_addr));
|
|
}
|
|
|
|
ll = net_pkt_ll_src(pkt);
|
|
if (ll->addr) {
|
|
memcpy(&hdr->src.addr, ll->addr,
|
|
sizeof(struct net_eth_addr));
|
|
}
|
|
|
|
net_pkt_frag_insert(pkt, header);
|
|
|
|
net_pkt_compact(pkt);
|
|
}
|
|
|
|
hdr = (struct net_eth_hdr *)net_pkt_ll(pkt);
|
|
|
|
/* Is the destination in the local network, if not route via
|
|
* the gateway address.
|
|
*/
|
|
if (!net_if_ipv4_addr_mask_cmp(net_pkt_iface(pkt),
|
|
&NET_IPV4_HDR(pkt)->dst)) {
|
|
addr = &net_pkt_iface(pkt)->ipv4.gw;
|
|
} else {
|
|
addr = &NET_IPV4_HDR(pkt)->dst;
|
|
}
|
|
|
|
/* If the destination address is already known, we do not need
|
|
* to send any ARP packet.
|
|
*/
|
|
entry = find_entry(net_pkt_iface(pkt),
|
|
addr, &free_entry, &non_pending);
|
|
if (!entry) {
|
|
if (!free_entry) {
|
|
/* So all the slots are occupied, use the first
|
|
* that can be taken.
|
|
*/
|
|
if (!non_pending) {
|
|
/* 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.
|
|
*/
|
|
struct net_pkt *req;
|
|
|
|
req = prepare_arp(net_pkt_iface(pkt),
|
|
addr, NULL, pkt);
|
|
NET_DBG("Resending ARP %p", req);
|
|
|
|
net_pkt_unref(pkt);
|
|
|
|
return req;
|
|
}
|
|
|
|
free_entry = non_pending;
|
|
}
|
|
|
|
return prepare_arp(net_pkt_iface(pkt), addr, free_entry, pkt);
|
|
}
|
|
|
|
ll = net_if_get_link_addr(entry->iface);
|
|
|
|
NET_DBG("ARP using ll %s for IP %s",
|
|
net_sprint_ll_addr(ll->addr, sizeof(struct net_eth_addr)),
|
|
net_sprint_ipv4_addr(&NET_IPV4_HDR(pkt)->src));
|
|
|
|
frag = pkt->frags;
|
|
while (frag) {
|
|
/* If there is no room for link layer header, then
|
|
* just send the packet as is.
|
|
*/
|
|
if (!net_buf_headroom(frag)) {
|
|
frag = frag->frags;
|
|
continue;
|
|
}
|
|
|
|
hdr = (struct net_eth_hdr *)(frag->data -
|
|
net_pkt_ll_reserve(pkt));
|
|
hdr->type = htons(NET_ETH_PTYPE_IP);
|
|
|
|
memcpy(&hdr->src.addr, ll->addr,
|
|
sizeof(struct net_eth_addr));
|
|
memcpy(&hdr->dst.addr, &entry->eth.addr,
|
|
sizeof(struct net_eth_addr));
|
|
|
|
frag = frag->frags;
|
|
}
|
|
|
|
return pkt;
|
|
}
|
|
|
|
static inline void send_pending(struct net_if *iface, struct net_pkt **pkt)
|
|
{
|
|
struct net_pkt *pending = *pkt;
|
|
|
|
NET_DBG("dst %s pending %p frag %p",
|
|
net_sprint_ipv4_addr(&NET_IPV4_HDR(pending)->dst), pending,
|
|
pending->frags);
|
|
|
|
*pkt = NULL;
|
|
|
|
if (net_if_send_data(iface, pending) == NET_DROP) {
|
|
/* This is to unref the original ref */
|
|
net_pkt_unref(pending);
|
|
}
|
|
|
|
/* The pending pkt was referenced when
|
|
* it was added to cache so we need to
|
|
* unref it now when it is removed from
|
|
* the cache.
|
|
*/
|
|
net_pkt_unref(pending);
|
|
}
|
|
|
|
static inline void arp_update(struct net_if *iface,
|
|
struct in_addr *src,
|
|
struct net_eth_addr *hwaddr)
|
|
{
|
|
int i;
|
|
|
|
NET_DBG("src %s", net_sprint_ipv4_addr(src));
|
|
|
|
for (i = 0; i < CONFIG_NET_ARP_TABLE_SIZE; i++) {
|
|
|
|
NET_DBG("[%d] iface %p dst %s ll %s pending %p", i, iface,
|
|
net_sprint_ipv4_addr(&arp_table[i].ip),
|
|
net_sprint_ll_addr((u8_t *)&arp_table[i].eth.addr,
|
|
sizeof(struct net_eth_addr)),
|
|
arp_table[i].pending);
|
|
|
|
if (arp_table[i].iface != iface ||
|
|
!net_ipv4_addr_cmp(&arp_table[i].ip, src)) {
|
|
continue;
|
|
}
|
|
|
|
if (arp_table[i].pending) {
|
|
/* We only update the ARP cache if we were
|
|
* initiating a request.
|
|
*/
|
|
memcpy(&arp_table[i].eth, hwaddr,
|
|
sizeof(struct net_eth_addr));
|
|
|
|
/* Set the dst in the pending packet */
|
|
net_pkt_ll_dst(arp_table[i].pending)->len =
|
|
sizeof(struct net_eth_addr);
|
|
net_pkt_ll_dst(arp_table[i].pending)->addr =
|
|
(u8_t *)
|
|
&NET_ETH_HDR(arp_table[i].pending)->dst.addr;
|
|
|
|
send_pending(iface, &arp_table[i].pending);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
static inline struct net_pkt *prepare_arp_reply(struct net_if *iface,
|
|
struct net_pkt *req)
|
|
{
|
|
struct net_pkt *pkt;
|
|
struct net_buf *frag;
|
|
struct net_arp_hdr *hdr, *query;
|
|
struct net_eth_hdr *eth, *eth_query;
|
|
|
|
pkt = net_pkt_get_reserve_tx(sizeof(struct net_eth_hdr), K_FOREVER);
|
|
if (!pkt) {
|
|
goto fail;
|
|
}
|
|
|
|
frag = net_pkt_get_frag(pkt, K_FOREVER);
|
|
if (!frag) {
|
|
goto fail;
|
|
}
|
|
|
|
net_pkt_frag_add(pkt, frag);
|
|
net_pkt_set_iface(pkt, iface);
|
|
net_pkt_set_family(pkt, AF_INET);
|
|
|
|
hdr = NET_ARP_HDR(pkt);
|
|
eth = NET_ETH_HDR(pkt);
|
|
query = NET_ARP_HDR(req);
|
|
eth_query = NET_ETH_HDR(req);
|
|
|
|
eth->type = htons(NET_ETH_PTYPE_ARP);
|
|
|
|
memcpy(ð->dst.addr, ð_query->src.addr,
|
|
sizeof(struct net_eth_addr));
|
|
memcpy(ð->src.addr, net_if_get_link_addr(iface)->addr,
|
|
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_REPLY);
|
|
|
|
memcpy(&hdr->dst_hwaddr.addr, ð_query->src.addr,
|
|
sizeof(struct net_eth_addr));
|
|
memcpy(&hdr->src_hwaddr.addr, ð->src.addr,
|
|
sizeof(struct net_eth_addr));
|
|
|
|
net_ipaddr_copy(&hdr->dst_ipaddr, &query->src_ipaddr);
|
|
net_ipaddr_copy(&hdr->src_ipaddr, &query->dst_ipaddr);
|
|
|
|
net_buf_add(frag, sizeof(struct net_arp_hdr));
|
|
|
|
return pkt;
|
|
|
|
fail:
|
|
net_pkt_unref(pkt);
|
|
return NULL;
|
|
}
|
|
|
|
enum net_verdict net_arp_input(struct net_pkt *pkt)
|
|
{
|
|
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_ll_reserve(pkt))) {
|
|
NET_DBG("Invalid ARP header (len %zu, min %zu bytes)",
|
|
net_pkt_get_len(pkt),
|
|
sizeof(struct net_arp_hdr) -
|
|
net_pkt_ll_reserve(pkt));
|
|
return NET_DROP;
|
|
}
|
|
|
|
arp_hdr = NET_ARP_HDR(pkt);
|
|
|
|
switch (ntohs(arp_hdr->opcode)) {
|
|
case NET_ARP_REQUEST:
|
|
/* Someone wants to know our ll address */
|
|
addr = if_get_addr(net_pkt_iface(pkt));
|
|
if (!addr) {
|
|
return NET_DROP;
|
|
}
|
|
|
|
if (!net_ipv4_addr_cmp(&arp_hdr->dst_ipaddr, addr)) {
|
|
/* Not for us so drop the packet silently */
|
|
return NET_DROP;
|
|
}
|
|
|
|
#if defined(CONFIG_NET_DEBUG_ARP)
|
|
do {
|
|
char out[sizeof("xxx.xxx.xxx.xxx")];
|
|
snprintk(out, sizeof(out), "%s",
|
|
net_sprint_ipv4_addr(&arp_hdr->src_ipaddr));
|
|
NET_DBG("ARP request from %s [%s] for %s",
|
|
out,
|
|
net_sprint_ll_addr(
|
|
(u8_t *)&arp_hdr->src_hwaddr,
|
|
arp_hdr->hwlen),
|
|
net_sprint_ipv4_addr(&arp_hdr->dst_ipaddr));
|
|
} while (0);
|
|
#endif /* CONFIG_NET_DEBUG_ARP */
|
|
|
|
/* Send reply */
|
|
reply = prepare_arp_reply(net_pkt_iface(pkt), pkt);
|
|
if (reply) {
|
|
net_if_queue_tx(net_pkt_iface(reply), reply);
|
|
}
|
|
break;
|
|
|
|
case NET_ARP_REPLY:
|
|
if (net_is_my_ipv4_addr(&arp_hdr->dst_ipaddr)) {
|
|
arp_update(net_pkt_iface(pkt), &arp_hdr->src_ipaddr,
|
|
&arp_hdr->src_hwaddr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
net_pkt_unref(pkt);
|
|
|
|
return NET_OK;
|
|
}
|
|
|
|
void net_arp_clear_cache(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < CONFIG_NET_ARP_TABLE_SIZE; i++) {
|
|
if (arp_table[i].pending) {
|
|
net_pkt_unref(arp_table[i].pending);
|
|
}
|
|
}
|
|
|
|
memset(&arp_table, 0, sizeof(arp_table));
|
|
}
|
|
|
|
void net_arp_init(void)
|
|
{
|
|
net_arp_clear_cache();
|
|
}
|