blob: 899d98a8f7e23dac1296529a0fbb9a0ec4a18a42 [file] [log] [blame]
/** @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/nbuf.h>
#include <net/net_if.h>
#include <net/net_stats.h>
#include <net/arp.h>
#include "net_private.h"
struct arp_entry {
uint32_t time; /* FIXME - implement timeout functionality */
struct net_if *iface;
struct net_buf *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((uint8_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((uint8_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_buf *prepare_arp(struct net_if *iface,
struct in_addr *next_addr,
struct arp_entry *entry,
struct net_buf *pending)
{
struct net_buf *buf, *frag;
struct net_arp_hdr *hdr;
struct net_eth_hdr *eth;
struct in_addr *my_addr;
buf = net_nbuf_get_reserve_tx(sizeof(struct net_eth_hdr), K_FOREVER);
if (!buf) {
goto fail;
}
frag = net_nbuf_get_frag(buf, K_FOREVER);
if (!frag) {
goto fail;
}
net_buf_frag_add(buf, frag);
net_nbuf_set_iface(buf, iface);
net_nbuf_set_family(buf, AF_INET);
hdr = NET_ARP_BUF(buf);
eth = NET_ETH_BUF(buf);
/* If entry is not set, then we are just about to send
* an ARP request using the data in pending net_buf.
* This can happen if there is already a pending ARP
* request and we want to send it again.
*/
if (entry) {
entry->pending = net_nbuf_ref(pending);
entry->iface = net_nbuf_iface(buf);
net_ipaddr_copy(&entry->ip, next_addr);
memcpy(&eth->src.addr,
net_if_get_link_addr(entry->iface)->addr,
sizeof(struct net_eth_addr));
} else {
memcpy(&eth->src.addr,
net_if_get_link_addr(iface)->addr,
sizeof(struct net_eth_addr));
}
eth->type = htons(NET_ETH_PTYPE_ARP);
memset(&eth->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_BUF(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 buf;
fail:
net_nbuf_unref(buf);
net_nbuf_unref(pending);
return NULL;
}
struct net_buf *net_arp_prepare(struct net_buf *buf)
{
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 (!buf || !buf->frags) {
return NULL;
}
if (net_nbuf_ll_reserve(buf) != sizeof(struct net_eth_hdr)) {
/* Add the ethernet header if it is missing. */
struct net_buf *header;
struct net_linkaddr *ll;
net_nbuf_set_ll_reserve(buf, sizeof(struct net_eth_hdr));
header = net_nbuf_get_frag(buf, K_FOREVER);
hdr = (struct net_eth_hdr *)(header->data -
net_nbuf_ll_reserve(buf));
hdr->type = htons(NET_ETH_PTYPE_IP);
ll = net_nbuf_ll_dst(buf);
if (ll->addr) {
memcpy(&hdr->dst.addr, ll->addr,
sizeof(struct net_eth_addr));
}
ll = net_nbuf_ll_src(buf);
if (ll->addr) {
memcpy(&hdr->src.addr, ll->addr,
sizeof(struct net_eth_addr));
}
net_buf_frag_insert(buf, header);
net_nbuf_compact(buf);
}
hdr = (struct net_eth_hdr *)net_nbuf_ll(buf);
/* Is the destination in the local network, if not route via
* the gateway address.
*/
if (!net_if_ipv4_addr_mask_cmp(net_nbuf_iface(buf),
&NET_IPV4_BUF(buf)->dst)) {
addr = &net_nbuf_iface(buf)->ipv4.gw;
} else {
addr = &NET_IPV4_BUF(buf)->dst;
}
/* If the destination address is already known, we do not need
* to send any ARP packet.
*/
entry = find_entry(net_nbuf_iface(buf),
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_buf *req;
req = prepare_arp(net_nbuf_iface(buf),
addr, NULL, buf);
NET_DBG("Resending ARP %p", req);
net_nbuf_unref(buf);
return req;
}
free_entry = non_pending;
}
return prepare_arp(net_nbuf_iface(buf), addr, free_entry, buf);
}
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_BUF(buf)->src));
frag = buf->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_nbuf_ll_reserve(buf));
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 buf;
}
static inline void send_pending(struct net_if *iface, struct net_buf **buf)
{
struct net_buf *pending = *buf;
NET_DBG("dst %s pending %p frag %p",
net_sprint_ipv4_addr(&NET_IPV4_BUF(pending)->dst), pending,
pending->frags);
*buf = NULL;
if (net_if_send_data(iface, pending) == NET_DROP) {
/* This is to unref the original ref */
net_nbuf_unref(pending);
}
/* The pending buf was referenced when
* it was added to cache so we need to
* unref it now when it is removed from
* the cache.
*/
net_nbuf_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((uint8_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)) {
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_nbuf_ll_dst(arp_table[i].pending)->len =
sizeof(struct net_eth_addr);
net_nbuf_ll_dst(arp_table[i].pending)->addr =
(uint8_t *)
&NET_ETH_BUF(arp_table[i].pending)->dst.addr;
send_pending(iface, &arp_table[i].pending);
}
return;
}
}
}
static inline struct net_buf *prepare_arp_reply(struct net_if *iface,
struct net_buf *req)
{
struct net_buf *buf, *frag;
struct net_arp_hdr *hdr, *query;
struct net_eth_hdr *eth, *eth_query;
buf = net_nbuf_get_reserve_tx(sizeof(struct net_eth_hdr), K_FOREVER);
if (!buf) {
goto fail;
}
frag = net_nbuf_get_frag(buf, K_FOREVER);
if (!frag) {
goto fail;
}
net_buf_frag_add(buf, frag);
net_nbuf_set_iface(buf, iface);
net_nbuf_set_family(buf, AF_INET);
hdr = NET_ARP_BUF(buf);
eth = NET_ETH_BUF(buf);
query = NET_ARP_BUF(req);
eth_query = NET_ETH_BUF(req);
eth->type = htons(NET_ETH_PTYPE_ARP);
memcpy(&eth->dst.addr, &eth_query->src.addr,
sizeof(struct net_eth_addr));
memcpy(&eth->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, &eth_query->src.addr,
sizeof(struct net_eth_addr));
memcpy(&hdr->src_hwaddr.addr, &eth->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 buf;
fail:
net_nbuf_unref(buf);
return NULL;
}
enum net_verdict net_arp_input(struct net_buf *buf)
{
struct net_arp_hdr *arp_hdr;
struct net_buf *reply;
if (net_buf_frags_len(buf) < (sizeof(struct net_arp_hdr) -
net_nbuf_ll_reserve(buf))) {
NET_DBG("Invalid ARP header (len %zu, min %zu bytes)",
net_buf_frags_len(buf),
sizeof(struct net_arp_hdr) -
net_nbuf_ll_reserve(buf));
return NET_DROP;
}
arp_hdr = NET_ARP_BUF(buf);
switch (ntohs(arp_hdr->opcode)) {
case NET_ARP_REQUEST:
/* Someone wants to know our ll address */
if (!net_ipv4_addr_cmp(&arp_hdr->dst_ipaddr,
if_get_addr(net_nbuf_iface(buf)))) {
/* 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(
(uint8_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_nbuf_iface(buf), buf);
if (reply) {
net_if_queue_tx(net_nbuf_iface(reply), reply);
}
break;
case NET_ARP_REPLY:
if (net_is_my_ipv4_addr(&arp_hdr->dst_ipaddr)) {
arp_update(net_nbuf_iface(buf), &arp_hdr->src_ipaddr,
&arp_hdr->src_hwaddr);
}
break;
}
return NET_DROP;
}
void net_arp_init(void)
{
memset(&arp_table, 0, sizeof(arp_table));
}