Instead of having one delayed_work struct / IP address, use only one delayed_work struct for lifetime timer. This saves over 20 bytes / allocated address struct. Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
4358 lines
101 KiB
C
4358 lines
101 KiB
C
/** @file
|
|
* @brief ICMPv6 related functions
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2016 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#if defined(CONFIG_NET_DEBUG_IPV6)
|
|
#define SYS_LOG_DOMAIN "net/ipv6"
|
|
#define NET_LOG_ENABLED 1
|
|
|
|
/* By default this prints too much data, set the value to 1 to see
|
|
* neighbor cache contents.
|
|
*/
|
|
#define NET_DEBUG_NBR 0
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <net/net_core.h>
|
|
#include <net/net_pkt.h>
|
|
#include <net/net_stats.h>
|
|
#include <net/net_context.h>
|
|
#include <net/net_mgmt.h>
|
|
#include <net/tcp.h>
|
|
#include "net_private.h"
|
|
#include "connection.h"
|
|
#include "icmpv6.h"
|
|
#include "udp_internal.h"
|
|
#include "tcp_internal.h"
|
|
#include "ipv6.h"
|
|
#include "nbr.h"
|
|
#include "6lo.h"
|
|
#include "route.h"
|
|
#include "rpl.h"
|
|
#include "net_stats.h"
|
|
|
|
/* Timeout value to be used when allocating net buffer during various
|
|
* neighbor discovery procedures.
|
|
*/
|
|
#define ND_NET_BUF_TIMEOUT K_MSEC(100)
|
|
|
|
/* Timeout for various buffer allocations in this file. */
|
|
#define NET_BUF_TIMEOUT K_MSEC(50)
|
|
|
|
/* Maximum reachable time value specified in RFC 4861 section
|
|
* 6.2.1. Router Configuration Variables, AdvReachableTime
|
|
*/
|
|
#define MAX_REACHABLE_TIME 3600000
|
|
|
|
/* IPv6 minimum link MTU specified in RFC 8200 section 5
|
|
* Packet Size Issues
|
|
*/
|
|
#define MIN_IPV6_MTU NET_IPV6_MTU
|
|
#define MAX_IPV6_MTU 0xffff
|
|
|
|
/* IPv6 wildcard and loopback address defined by RFC2553 */
|
|
const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
|
|
const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
|
|
|
|
#if defined(CONFIG_NET_IPV6_ND)
|
|
static void nd_reachable_timeout(struct k_work *work);
|
|
#endif
|
|
|
|
#if defined(CONFIG_NET_IPV6_NBR_CACHE)
|
|
|
|
#define MAX_MULTICAST_SOLICIT 3
|
|
#define MAX_UNICAST_SOLICIT 3
|
|
#define DELAY_FIRST_PROBE_TIME K_SECONDS(5) /* RFC 4861 ch 10 */
|
|
#define RETRANS_TIMER K_MSEC(1000) /* in ms, RFC 4861 ch 10 */
|
|
|
|
extern void net_neighbor_data_remove(struct net_nbr *nbr);
|
|
extern void net_neighbor_table_clear(struct net_nbr_table *table);
|
|
|
|
NET_NBR_POOL_INIT(net_neighbor_pool,
|
|
CONFIG_NET_IPV6_MAX_NEIGHBORS,
|
|
sizeof(struct net_ipv6_nbr_data),
|
|
net_neighbor_data_remove,
|
|
0);
|
|
|
|
NET_NBR_TABLE_INIT(NET_NBR_GLOBAL,
|
|
neighbor,
|
|
net_neighbor_pool,
|
|
net_neighbor_table_clear);
|
|
|
|
const char *net_ipv6_nbr_state2str(enum net_ipv6_nbr_state state)
|
|
{
|
|
switch (state) {
|
|
case NET_IPV6_NBR_STATE_INCOMPLETE:
|
|
return "incomplete";
|
|
case NET_IPV6_NBR_STATE_REACHABLE:
|
|
return "reachable";
|
|
case NET_IPV6_NBR_STATE_STALE:
|
|
return "stale";
|
|
case NET_IPV6_NBR_STATE_DELAY:
|
|
return "delay";
|
|
case NET_IPV6_NBR_STATE_PROBE:
|
|
return "probe";
|
|
case NET_IPV6_NBR_STATE_STATIC:
|
|
return "static";
|
|
}
|
|
|
|
return "<invalid state>";
|
|
}
|
|
|
|
static void ipv6_nbr_set_state(struct net_nbr *nbr,
|
|
enum net_ipv6_nbr_state new_state)
|
|
{
|
|
if (new_state == net_ipv6_nbr_data(nbr)->state ||
|
|
net_ipv6_nbr_data(nbr)->state == NET_IPV6_NBR_STATE_STATIC) {
|
|
return;
|
|
}
|
|
|
|
NET_DBG("nbr %p %s -> %s", nbr,
|
|
net_ipv6_nbr_state2str(net_ipv6_nbr_data(nbr)->state),
|
|
net_ipv6_nbr_state2str(new_state));
|
|
|
|
net_ipv6_nbr_data(nbr)->state = new_state;
|
|
}
|
|
|
|
static inline bool net_is_solicited(struct net_pkt *pkt)
|
|
{
|
|
struct net_icmpv6_na_hdr na_hdr;
|
|
int ret;
|
|
|
|
ret = net_icmpv6_get_na_hdr(pkt, &na_hdr);
|
|
if (ret < 0) {
|
|
NET_ERR("could not get na_hdr");
|
|
return false;
|
|
}
|
|
|
|
return na_hdr.flags & NET_ICMPV6_NA_FLAG_SOLICITED;
|
|
}
|
|
|
|
static inline bool net_is_router(struct net_pkt *pkt)
|
|
{
|
|
struct net_icmpv6_na_hdr na_hdr;
|
|
int ret;
|
|
|
|
ret = net_icmpv6_get_na_hdr(pkt, &na_hdr);
|
|
if (ret < 0) {
|
|
NET_ERR("could not get na_hdr");
|
|
return false;
|
|
}
|
|
|
|
return na_hdr.flags & NET_ICMPV6_NA_FLAG_ROUTER;
|
|
}
|
|
|
|
static inline bool net_is_override(struct net_pkt *pkt)
|
|
{
|
|
struct net_icmpv6_na_hdr na_hdr;
|
|
int ret;
|
|
|
|
ret = net_icmpv6_get_na_hdr(pkt, &na_hdr);
|
|
if (ret < 0) {
|
|
NET_ERR("could not get na_hdr");
|
|
return false;
|
|
}
|
|
|
|
return na_hdr.flags & NET_ICMPV6_NA_FLAG_OVERRIDE;
|
|
}
|
|
|
|
static inline struct net_nbr *get_nbr(int idx)
|
|
{
|
|
return &net_neighbor_pool[idx].nbr;
|
|
}
|
|
|
|
static inline struct net_nbr *get_nbr_from_data(struct net_ipv6_nbr_data *data)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) {
|
|
struct net_nbr *nbr = get_nbr(i);
|
|
|
|
if (nbr->data == (u8_t *)data) {
|
|
return nbr;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct iface_cb_data {
|
|
net_nbr_cb_t cb;
|
|
void *user_data;
|
|
};
|
|
|
|
static void iface_cb(struct net_if *iface, void *user_data)
|
|
{
|
|
struct iface_cb_data *data = user_data;
|
|
int i;
|
|
|
|
for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) {
|
|
struct net_nbr *nbr = get_nbr(i);
|
|
|
|
if (!nbr->ref || nbr->iface != iface) {
|
|
continue;
|
|
}
|
|
|
|
data->cb(nbr, data->user_data);
|
|
}
|
|
}
|
|
|
|
void net_ipv6_nbr_foreach(net_nbr_cb_t cb, void *user_data)
|
|
{
|
|
struct iface_cb_data cb_data = {
|
|
.cb = cb,
|
|
.user_data = user_data,
|
|
};
|
|
|
|
/* Return the neighbors according to network interface. This makes it
|
|
* easier in the callback to use the neighbor information.
|
|
*/
|
|
net_if_foreach(iface_cb, &cb_data);
|
|
}
|
|
|
|
#if NET_DEBUG_NBR
|
|
void nbr_print(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) {
|
|
struct net_nbr *nbr = get_nbr(i);
|
|
|
|
if (!nbr->ref) {
|
|
continue;
|
|
}
|
|
|
|
NET_DBG("[%d] %p %d/%d/%d/%d/%d pending %p iface %p idx %d "
|
|
"ll %s addr %s",
|
|
i, nbr, nbr->ref, net_ipv6_nbr_data(nbr)->ns_count,
|
|
net_ipv6_nbr_data(nbr)->is_router,
|
|
net_ipv6_nbr_data(nbr)->state,
|
|
net_ipv6_nbr_data(nbr)->link_metric,
|
|
net_ipv6_nbr_data(nbr)->pending,
|
|
nbr->iface, nbr->idx,
|
|
nbr->idx == NET_NBR_LLADDR_UNKNOWN ? "?" :
|
|
net_sprint_ll_addr(
|
|
net_nbr_get_lladdr(nbr->idx)->addr,
|
|
net_nbr_get_lladdr(nbr->idx)->len),
|
|
net_sprint_ipv6_addr(&net_ipv6_nbr_data(nbr)->addr));
|
|
}
|
|
}
|
|
#else
|
|
#define nbr_print(...)
|
|
#endif
|
|
|
|
static struct net_nbr *nbr_lookup(struct net_nbr_table *table,
|
|
struct net_if *iface,
|
|
struct in6_addr *addr)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) {
|
|
struct net_nbr *nbr = get_nbr(i);
|
|
|
|
if (!nbr->ref) {
|
|
continue;
|
|
}
|
|
|
|
if (iface && nbr->iface != iface) {
|
|
continue;
|
|
}
|
|
|
|
if (net_ipv6_addr_cmp(&net_ipv6_nbr_data(nbr)->addr, addr)) {
|
|
return nbr;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline void nbr_clear_ns_pending(struct net_ipv6_nbr_data *data)
|
|
{
|
|
k_delayed_work_cancel(&data->send_ns);
|
|
|
|
if (data->pending) {
|
|
net_pkt_unref(data->pending);
|
|
data->pending = NULL;
|
|
}
|
|
}
|
|
|
|
static inline void nbr_free(struct net_nbr *nbr)
|
|
{
|
|
NET_DBG("nbr %p", nbr);
|
|
|
|
nbr_clear_ns_pending(net_ipv6_nbr_data(nbr));
|
|
|
|
k_delayed_work_cancel(&net_ipv6_nbr_data(nbr)->reachable);
|
|
|
|
net_nbr_unref(nbr);
|
|
net_nbr_unlink(nbr, NULL);
|
|
}
|
|
|
|
bool net_ipv6_nbr_rm(struct net_if *iface, struct in6_addr *addr)
|
|
{
|
|
struct net_nbr *nbr;
|
|
#if defined(CONFIG_NET_MGMT_EVENT_INFO)
|
|
struct net_event_ipv6_nbr info;
|
|
#endif
|
|
|
|
nbr = nbr_lookup(&net_neighbor.table, iface, addr);
|
|
if (!nbr) {
|
|
return false;
|
|
}
|
|
|
|
/* Remove any routes with nbr as nexthop in first place */
|
|
net_route_del_by_nexthop(iface, addr);
|
|
|
|
nbr_free(nbr);
|
|
|
|
#if defined(CONFIG_NET_MGMT_EVENT_INFO)
|
|
info.idx = -1;
|
|
net_ipaddr_copy(&info.addr, addr);
|
|
net_mgmt_event_notify_with_info(NET_EVENT_IPV6_NBR_DEL,
|
|
iface, (void *) &info,
|
|
sizeof(struct net_event_ipv6_nbr));
|
|
#else
|
|
net_mgmt_event_notify(NET_EVENT_IPV6_NBR_DEL, iface);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
#define NS_REPLY_TIMEOUT K_SECONDS(1)
|
|
|
|
static void ns_reply_timeout(struct k_work *work)
|
|
{
|
|
/* We did not receive reply to a sent NS */
|
|
struct net_ipv6_nbr_data *data = CONTAINER_OF(work,
|
|
struct net_ipv6_nbr_data,
|
|
send_ns);
|
|
|
|
struct net_nbr *nbr = get_nbr_from_data(data);
|
|
|
|
if (!nbr) {
|
|
NET_DBG("NS timeout but no nbr data");
|
|
return;
|
|
}
|
|
|
|
if (!data->pending) {
|
|
/* Silently return, this is not an error as the work
|
|
* cannot be cancelled in certain cases.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
NET_DBG("NS nbr %p pending %p timeout to %s", nbr, data->pending,
|
|
net_sprint_ipv6_addr(&NET_IPV6_HDR(data->pending)->dst));
|
|
|
|
/* To unref when pending variable was set */
|
|
net_pkt_unref(data->pending);
|
|
|
|
/* To unref the original pkt allocation */
|
|
net_pkt_unref(data->pending);
|
|
|
|
data->pending = NULL;
|
|
|
|
net_nbr_unref(nbr);
|
|
}
|
|
|
|
static void nbr_init(struct net_nbr *nbr, struct net_if *iface,
|
|
struct in6_addr *addr, bool is_router,
|
|
enum net_ipv6_nbr_state state)
|
|
{
|
|
nbr->idx = NET_NBR_LLADDR_UNKNOWN;
|
|
nbr->iface = iface;
|
|
|
|
net_ipaddr_copy(&net_ipv6_nbr_data(nbr)->addr, addr);
|
|
ipv6_nbr_set_state(nbr, state);
|
|
net_ipv6_nbr_data(nbr)->is_router = is_router;
|
|
net_ipv6_nbr_data(nbr)->pending = NULL;
|
|
|
|
#if defined(CONFIG_NET_IPV6_ND)
|
|
k_delayed_work_init(&net_ipv6_nbr_data(nbr)->reachable,
|
|
nd_reachable_timeout);
|
|
#endif
|
|
k_delayed_work_init(&net_ipv6_nbr_data(nbr)->send_ns,
|
|
ns_reply_timeout);
|
|
}
|
|
|
|
static struct net_nbr *nbr_new(struct net_if *iface,
|
|
struct in6_addr *addr, bool is_router,
|
|
enum net_ipv6_nbr_state state)
|
|
{
|
|
struct net_nbr *nbr = net_nbr_get(&net_neighbor.table);
|
|
|
|
if (!nbr) {
|
|
return NULL;
|
|
}
|
|
|
|
nbr_init(nbr, iface, addr, true, state);
|
|
|
|
NET_DBG("nbr %p iface %p state %d IPv6 %s",
|
|
nbr, iface, state, net_sprint_ipv6_addr(addr));
|
|
|
|
return nbr;
|
|
}
|
|
|
|
#if defined(CONFIG_NET_DEBUG_IPV6)
|
|
static inline void dbg_update_neighbor_lladdr(struct net_linkaddr *new_lladdr,
|
|
struct net_linkaddr_storage *old_lladdr,
|
|
struct in6_addr *addr)
|
|
{
|
|
char out[sizeof("xx:xx:xx:xx:xx:xx:xx:xx")];
|
|
|
|
snprintk(out, sizeof(out), "%s",
|
|
net_sprint_ll_addr(old_lladdr->addr, old_lladdr->len));
|
|
|
|
NET_DBG("Updating neighbor %s lladdr %s (was %s)",
|
|
net_sprint_ipv6_addr(addr),
|
|
net_sprint_ll_addr(new_lladdr->addr, new_lladdr->len),
|
|
out);
|
|
}
|
|
|
|
static inline void dbg_update_neighbor_lladdr_raw(u8_t *new_lladdr,
|
|
struct net_linkaddr_storage *old_lladdr,
|
|
struct in6_addr *addr)
|
|
{
|
|
struct net_linkaddr lladdr = {
|
|
.len = old_lladdr->len,
|
|
.addr = new_lladdr,
|
|
};
|
|
|
|
dbg_update_neighbor_lladdr(&lladdr, old_lladdr, addr);
|
|
}
|
|
|
|
#define dbg_addr(action, pkt_str, src, dst) \
|
|
do { \
|
|
NET_DBG("%s %s from %s to %s", action, pkt_str, \
|
|
net_sprint_ipv6_addr(src), \
|
|
net_sprint_ipv6_addr(dst)); \
|
|
\
|
|
} while (0)
|
|
|
|
#define dbg_addr_recv(pkt_str, src, dst) \
|
|
dbg_addr("Received", pkt_str, src, dst)
|
|
|
|
#define dbg_addr_sent(pkt_str, src, dst) \
|
|
dbg_addr("Sent", pkt_str, src, dst)
|
|
|
|
#define dbg_addr_with_tgt(action, pkt_str, src, dst, target) \
|
|
do { \
|
|
NET_DBG("%s %s from %s to %s, target %s", action, \
|
|
pkt_str, \
|
|
net_sprint_ipv6_addr(src), \
|
|
net_sprint_ipv6_addr(dst), \
|
|
net_sprint_ipv6_addr(target)); \
|
|
\
|
|
} while (0)
|
|
|
|
#define dbg_addr_recv_tgt(pkt_str, src, dst, tgt) \
|
|
dbg_addr_with_tgt("Received", pkt_str, src, dst, tgt)
|
|
|
|
#define dbg_addr_sent_tgt(pkt_str, src, dst, tgt) \
|
|
dbg_addr_with_tgt("Sent", pkt_str, src, dst, tgt)
|
|
#else
|
|
#define dbg_update_neighbor_lladdr(...)
|
|
#define dbg_update_neighbor_lladdr_raw(...)
|
|
#define dbg_addr(...)
|
|
#define dbg_addr_recv(...)
|
|
#define dbg_addr_sent(...)
|
|
|
|
#define dbg_addr_with_tgt(...)
|
|
#define dbg_addr_recv_tgt(...)
|
|
#define dbg_addr_sent_tgt(...)
|
|
#endif /* CONFIG_NET_DEBUG_IPV6 */
|
|
|
|
struct net_nbr *net_ipv6_nbr_add(struct net_if *iface,
|
|
struct in6_addr *addr,
|
|
struct net_linkaddr *lladdr,
|
|
bool is_router,
|
|
enum net_ipv6_nbr_state state)
|
|
{
|
|
struct net_nbr *nbr;
|
|
int ret;
|
|
#if defined(CONFIG_NET_MGMT_EVENT_INFO)
|
|
struct net_event_ipv6_nbr info;
|
|
#endif
|
|
|
|
nbr = nbr_lookup(&net_neighbor.table, iface, addr);
|
|
if (!nbr) {
|
|
nbr = nbr_new(iface, addr, is_router, state);
|
|
if (!nbr) {
|
|
NET_ERR("Could not add router neighbor %s [%s]",
|
|
net_sprint_ipv6_addr(addr),
|
|
net_sprint_ll_addr(lladdr->addr, lladdr->len));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (net_nbr_link(nbr, iface, lladdr) == -EALREADY &&
|
|
net_ipv6_nbr_data(nbr)->state != NET_IPV6_NBR_STATE_STATIC) {
|
|
/* Update the lladdr if the node was already known */
|
|
struct net_linkaddr_storage *cached_lladdr;
|
|
|
|
cached_lladdr = net_nbr_get_lladdr(nbr->idx);
|
|
|
|
if (memcmp(cached_lladdr->addr, lladdr->addr, lladdr->len)) {
|
|
dbg_update_neighbor_lladdr(lladdr, cached_lladdr, addr);
|
|
|
|
net_linkaddr_set(cached_lladdr, lladdr->addr,
|
|
lladdr->len);
|
|
|
|
ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_STALE);
|
|
} else if (net_ipv6_nbr_data(nbr)->state ==
|
|
NET_IPV6_NBR_STATE_INCOMPLETE) {
|
|
ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_STALE);
|
|
}
|
|
}
|
|
|
|
if (net_ipv6_nbr_data(nbr)->state == NET_IPV6_NBR_STATE_INCOMPLETE) {
|
|
/* Send NS so that we can verify that the neighbor is
|
|
* reachable.
|
|
*/
|
|
ret = net_ipv6_send_ns(iface, NULL, NULL, NULL, addr, false);
|
|
if (ret < 0) {
|
|
NET_DBG("Cannot send NS (%d)", ret);
|
|
}
|
|
}
|
|
|
|
NET_DBG("[%d] nbr %p state %d router %d IPv6 %s ll %s iface %p",
|
|
nbr->idx, nbr, state, is_router,
|
|
net_sprint_ipv6_addr(addr),
|
|
net_sprint_ll_addr(lladdr->addr, lladdr->len),
|
|
nbr->iface);
|
|
|
|
#if defined(CONFIG_NET_MGMT_EVENT_INFO)
|
|
info.idx = nbr->idx;
|
|
net_ipaddr_copy(&info.addr, addr);
|
|
net_mgmt_event_notify_with_info(NET_EVENT_IPV6_NBR_ADD,
|
|
iface, (void *) &info,
|
|
sizeof(struct net_event_ipv6_nbr));
|
|
#else
|
|
net_mgmt_event_notify(NET_EVENT_IPV6_NBR_ADD, iface);
|
|
#endif
|
|
|
|
return nbr;
|
|
}
|
|
|
|
static inline struct net_nbr *nbr_add(struct net_pkt *pkt,
|
|
struct net_linkaddr *lladdr,
|
|
bool is_router,
|
|
enum net_ipv6_nbr_state state)
|
|
{
|
|
return net_ipv6_nbr_add(net_pkt_iface(pkt), &NET_IPV6_HDR(pkt)->src,
|
|
lladdr, is_router, state);
|
|
}
|
|
|
|
void net_neighbor_data_remove(struct net_nbr *nbr)
|
|
{
|
|
NET_DBG("Neighbor %p removed", nbr);
|
|
|
|
return;
|
|
}
|
|
|
|
void net_neighbor_table_clear(struct net_nbr_table *table)
|
|
{
|
|
NET_DBG("Neighbor table %p cleared", table);
|
|
}
|
|
|
|
struct in6_addr *net_ipv6_nbr_lookup_by_index(struct net_if *iface,
|
|
u8_t idx)
|
|
{
|
|
int i;
|
|
|
|
if (idx == NET_NBR_LLADDR_UNKNOWN) {
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) {
|
|
struct net_nbr *nbr = get_nbr(i);
|
|
|
|
if (!nbr->ref) {
|
|
continue;
|
|
}
|
|
|
|
if (iface && nbr->iface != iface) {
|
|
continue;
|
|
}
|
|
|
|
if (nbr->idx == idx) {
|
|
return &net_ipv6_nbr_data(nbr)->addr;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
#else
|
|
const char *net_ipv6_nbr_state2str(enum net_ipv6_nbr_state state)
|
|
{
|
|
return "<unknown state>";
|
|
}
|
|
#endif /* CONFIG_NET_IPV6_NBR_CACHE */
|
|
|
|
int net_ipv6_find_last_ext_hdr(struct net_pkt *pkt, u16_t *next_hdr_idx,
|
|
u16_t *last_hdr_idx)
|
|
{
|
|
struct net_buf *next_hdr_frag;
|
|
struct net_buf *last_hdr_frag;
|
|
struct net_buf *frag;
|
|
u16_t pkt_offset;
|
|
u16_t offset;
|
|
u16_t length;
|
|
u8_t next_hdr;
|
|
u8_t next;
|
|
|
|
if (!pkt || !pkt->frags || !next_hdr_idx || !last_hdr_idx) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
next = NET_IPV6_HDR(pkt)->nexthdr;
|
|
|
|
/* Initial value if no extension fragments are found */
|
|
*next_hdr_idx = 6;
|
|
*last_hdr_idx = sizeof(struct net_ipv6_hdr);
|
|
|
|
/* First check the simplest case where there is no extension headers
|
|
* in the packet. There cannot be any extensions after the normal or
|
|
* typical IP protocols
|
|
*/
|
|
if (next == IPPROTO_ICMPV6 || next == IPPROTO_UDP ||
|
|
next == IPPROTO_TCP || next == NET_IPV6_NEXTHDR_NONE) {
|
|
return 0;
|
|
}
|
|
|
|
frag = pkt->frags;
|
|
offset = *last_hdr_idx;
|
|
*next_hdr_idx = *last_hdr_idx;
|
|
next_hdr_frag = last_hdr_frag = frag;
|
|
|
|
while (frag) {
|
|
frag = net_frag_read_u8(frag, offset, &offset, &next_hdr);
|
|
if (!frag) {
|
|
goto fail;
|
|
}
|
|
|
|
switch (next) {
|
|
case NET_IPV6_NEXTHDR_FRAG:
|
|
frag = net_frag_skip(frag, offset, &offset, 7);
|
|
if (!frag) {
|
|
goto fail;
|
|
}
|
|
|
|
break;
|
|
|
|
case NET_IPV6_NEXTHDR_HBHO:
|
|
length = 0;
|
|
frag = net_frag_read_u8(frag, offset, &offset,
|
|
(u8_t *)&length);
|
|
if (!frag) {
|
|
goto fail;
|
|
}
|
|
|
|
length = length * 8 + 8;
|
|
|
|
frag = net_frag_skip(frag, offset, &offset, length - 2);
|
|
if (!frag) {
|
|
goto fail;
|
|
}
|
|
|
|
break;
|
|
|
|
case NET_IPV6_NEXTHDR_NONE:
|
|
case IPPROTO_ICMPV6:
|
|
case IPPROTO_UDP:
|
|
case IPPROTO_TCP:
|
|
goto out;
|
|
|
|
default:
|
|
/* TODO: Add more IPv6 extension headers to check */
|
|
goto fail;
|
|
}
|
|
|
|
*next_hdr_idx = *last_hdr_idx;
|
|
next_hdr_frag = last_hdr_frag;
|
|
|
|
*last_hdr_idx = offset;
|
|
last_hdr_frag = frag;
|
|
|
|
next = next_hdr;
|
|
}
|
|
|
|
fail:
|
|
return -EINVAL;
|
|
|
|
out:
|
|
/* Current next_hdr_idx offset is based on respective fragment, but we
|
|
* need to calculate next_hdr_idx offset based on whole packet.
|
|
*/
|
|
pkt_offset = 0;
|
|
frag = pkt->frags;
|
|
while (frag) {
|
|
if (next_hdr_frag == frag) {
|
|
*next_hdr_idx += pkt_offset;
|
|
break;
|
|
}
|
|
|
|
pkt_offset += frag->len;
|
|
frag = frag->frags;
|
|
}
|
|
|
|
/* Current last_hdr_idx offset is based on respective fragment, but we
|
|
* need to calculate last_hdr_idx offset based on whole packet.
|
|
*/
|
|
pkt_offset = 0;
|
|
frag = pkt->frags;
|
|
while (frag) {
|
|
if (last_hdr_frag == frag) {
|
|
*last_hdr_idx += pkt_offset;
|
|
break;
|
|
}
|
|
|
|
pkt_offset += frag->len;
|
|
frag = frag->frags;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct in6_addr *net_ipv6_unspecified_address(void)
|
|
{
|
|
return &in6addr_any;
|
|
}
|
|
|
|
struct net_pkt *net_ipv6_create(struct net_pkt *pkt,
|
|
const struct in6_addr *src,
|
|
const struct in6_addr *dst,
|
|
struct net_if *iface,
|
|
u8_t next_header_proto)
|
|
{
|
|
struct net_buf *header;
|
|
|
|
header = net_pkt_get_frag(pkt, NET_BUF_TIMEOUT);
|
|
if (!header) {
|
|
return NULL;
|
|
}
|
|
|
|
net_pkt_frag_insert(pkt, header);
|
|
|
|
NET_IPV6_HDR(pkt)->vtc = 0x60;
|
|
NET_IPV6_HDR(pkt)->tcflow = 0;
|
|
NET_IPV6_HDR(pkt)->flow = 0;
|
|
|
|
NET_IPV6_HDR(pkt)->nexthdr = 0;
|
|
|
|
/* User can tweak the default hop limit if needed */
|
|
NET_IPV6_HDR(pkt)->hop_limit = net_pkt_ipv6_hop_limit(pkt);
|
|
if (NET_IPV6_HDR(pkt)->hop_limit == 0) {
|
|
NET_IPV6_HDR(pkt)->hop_limit =
|
|
net_if_ipv6_get_hop_limit(iface);
|
|
}
|
|
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->dst, dst);
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, src);
|
|
|
|
net_pkt_set_ipv6_ext_len(pkt, 0);
|
|
NET_IPV6_HDR(pkt)->nexthdr = next_header_proto;
|
|
|
|
net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr));
|
|
net_pkt_set_family(pkt, AF_INET6);
|
|
|
|
net_buf_add(header, sizeof(struct net_ipv6_hdr));
|
|
|
|
return pkt;
|
|
}
|
|
|
|
int net_ipv6_finalize(struct net_pkt *pkt, u8_t next_header_proto)
|
|
{
|
|
/* Set the length of the IPv6 header */
|
|
size_t total_len;
|
|
int ret;
|
|
|
|
#if defined(CONFIG_NET_UDP) && defined(CONFIG_NET_RPL_INSERT_HBH_OPTION)
|
|
if (next_header_proto != IPPROTO_TCP &&
|
|
next_header_proto != IPPROTO_ICMPV6) {
|
|
/* Check if we need to add RPL header to sent UDP packet. */
|
|
if (net_rpl_insert_header(pkt) < 0) {
|
|
NET_DBG("RPL HBHO insert failed");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
#endif
|
|
net_pkt_compact(pkt);
|
|
|
|
total_len = net_pkt_get_len(pkt) - sizeof(struct net_ipv6_hdr);
|
|
|
|
NET_IPV6_HDR(pkt)->len = htons(total_len);
|
|
|
|
#if defined(CONFIG_NET_UDP)
|
|
if (next_header_proto == IPPROTO_UDP &&
|
|
net_if_need_calc_tx_checksum(net_pkt_iface(pkt))) {
|
|
net_udp_set_chksum(pkt, pkt->frags);
|
|
} else
|
|
#endif
|
|
|
|
#if defined(CONFIG_NET_TCP)
|
|
if (next_header_proto == IPPROTO_TCP &&
|
|
net_if_need_calc_tx_checksum(net_pkt_iface(pkt))) {
|
|
net_tcp_set_chksum(pkt, pkt->frags);
|
|
} else
|
|
#endif
|
|
|
|
if (next_header_proto == IPPROTO_ICMPV6) {
|
|
ret = net_icmpv6_set_chksum(pkt);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_NET_IPV6_DAD)
|
|
int net_ipv6_start_dad(struct net_if *iface, struct net_if_addr *ifaddr)
|
|
{
|
|
return net_ipv6_send_ns(iface, NULL, NULL, NULL,
|
|
&ifaddr->address.in6_addr, true);
|
|
}
|
|
|
|
static inline bool dad_failed(struct net_if *iface, struct in6_addr *addr)
|
|
{
|
|
if (net_is_ipv6_ll_addr(addr)) {
|
|
NET_ERR("DAD failed, no ll IPv6 address!");
|
|
return false;
|
|
}
|
|
|
|
net_if_ipv6_dad_failed(iface, addr);
|
|
|
|
return true;
|
|
}
|
|
#endif /* CONFIG_NET_IPV6_DAD */
|
|
|
|
#if defined(CONFIG_NET_IPV6_NBR_CACHE)
|
|
|
|
/* If the reserve has changed, we need to adjust it accordingly in the
|
|
* fragment chain. This can only happen in IEEE 802.15.4 where the link
|
|
* layer header size can change if the destination address changes.
|
|
* Thus we need to check it here. Note that this cannot happen for IPv4
|
|
* as 802.15.4 supports IPv6 only.
|
|
*/
|
|
static struct net_pkt *update_ll_reserve(struct net_pkt *pkt,
|
|
struct in6_addr *addr)
|
|
{
|
|
/* We need to go through all the fragments and adjust the
|
|
* fragment data size.
|
|
*/
|
|
u16_t reserve, room_len, copy_len, pos;
|
|
struct net_buf *orig_frag, *frag;
|
|
|
|
/* No need to do anything if we are forwarding the packet
|
|
* as we already know everything about the destination of
|
|
* the packet, but only if both src and dest are using
|
|
* same technology meaning that link address length is the same.
|
|
*/
|
|
if (net_pkt_forwarding(pkt) &&
|
|
net_pkt_orig_iface(pkt) == net_pkt_iface(pkt)) {
|
|
return pkt;
|
|
}
|
|
|
|
reserve = net_if_get_ll_reserve(net_pkt_iface(pkt), addr);
|
|
if (reserve == net_pkt_ll_reserve(pkt)) {
|
|
return pkt;
|
|
}
|
|
|
|
NET_DBG("Adjust reserve old %d new %d",
|
|
net_pkt_ll_reserve(pkt), reserve);
|
|
|
|
/* Normally these debug prints are not needed so we do not print them
|
|
* always. If your packets get dropped for some reason by L2, then
|
|
* you can enable this block to see the IPv6 and LL addresses that
|
|
* are used.
|
|
*/
|
|
if (0) {
|
|
NET_DBG("ll src %s",
|
|
net_sprint_ll_addr(net_pkt_ll_src(pkt)->addr,
|
|
net_pkt_ll_src(pkt)->len));
|
|
NET_DBG("ll dst %s",
|
|
net_sprint_ll_addr(net_pkt_ll_dst(pkt)->addr,
|
|
net_pkt_ll_dst(pkt)->len));
|
|
NET_DBG("ip src %s",
|
|
net_sprint_ipv6_addr(&NET_IPV6_HDR(pkt)->src));
|
|
NET_DBG("ip dst %s",
|
|
net_sprint_ipv6_addr(&NET_IPV6_HDR(pkt)->dst));
|
|
}
|
|
|
|
net_pkt_set_ll_reserve(pkt, reserve);
|
|
|
|
orig_frag = pkt->frags;
|
|
copy_len = orig_frag->len;
|
|
pos = 0;
|
|
|
|
pkt->frags = NULL;
|
|
room_len = 0;
|
|
frag = NULL;
|
|
|
|
while (orig_frag) {
|
|
if (!room_len) {
|
|
frag = net_pkt_get_frag(pkt, NET_BUF_TIMEOUT);
|
|
if (!frag) {
|
|
net_pkt_unref(pkt);
|
|
net_pkt_frag_unref(orig_frag);
|
|
return NULL;
|
|
}
|
|
|
|
net_pkt_frag_add(pkt, frag);
|
|
|
|
room_len = net_buf_tailroom(frag);
|
|
}
|
|
|
|
if (room_len >= copy_len) {
|
|
memcpy(net_buf_add(frag, copy_len),
|
|
orig_frag->data + pos, copy_len);
|
|
|
|
room_len -= copy_len;
|
|
copy_len = 0;
|
|
} else {
|
|
memcpy(net_buf_add(frag, room_len),
|
|
orig_frag->data + pos, room_len);
|
|
|
|
copy_len -= room_len;
|
|
pos += room_len;
|
|
room_len = 0;
|
|
}
|
|
|
|
if (!copy_len) {
|
|
struct net_buf *tmp = orig_frag;
|
|
|
|
orig_frag = orig_frag->frags;
|
|
|
|
tmp->frags = NULL;
|
|
net_pkt_frag_unref(tmp);
|
|
|
|
if (!orig_frag) {
|
|
break;
|
|
}
|
|
|
|
copy_len = orig_frag->len;
|
|
pos = 0;
|
|
}
|
|
}
|
|
|
|
return pkt;
|
|
}
|
|
|
|
static struct in6_addr *check_route(struct net_if *iface,
|
|
struct in6_addr *dst,
|
|
bool *try_route)
|
|
{
|
|
struct in6_addr *nexthop = NULL;
|
|
struct net_route_entry *route;
|
|
struct net_if_router *router;
|
|
|
|
route = net_route_lookup(iface, dst);
|
|
if (route) {
|
|
nexthop = net_route_get_nexthop(route);
|
|
|
|
NET_DBG("Route %p nexthop %s", route,
|
|
nexthop ? net_sprint_ipv6_addr(nexthop) : "<unknown>");
|
|
|
|
if (!nexthop) {
|
|
net_route_del(route);
|
|
|
|
net_rpl_global_repair(route);
|
|
|
|
NET_DBG("No route to host %s",
|
|
net_sprint_ipv6_addr(dst));
|
|
|
|
return NULL;
|
|
}
|
|
} else {
|
|
/* No specific route to this host, use the default
|
|
* route instead.
|
|
*/
|
|
router = net_if_ipv6_router_find_default(NULL, dst);
|
|
if (!router) {
|
|
NET_DBG("No default route to %s",
|
|
net_sprint_ipv6_addr(dst));
|
|
|
|
/* Try to send the packet anyway */
|
|
nexthop = dst;
|
|
if (try_route) {
|
|
*try_route = true;
|
|
}
|
|
|
|
return nexthop;
|
|
}
|
|
|
|
nexthop = &router->address.in6_addr;
|
|
|
|
NET_DBG("Router %p nexthop %s", router,
|
|
net_sprint_ipv6_addr(nexthop));
|
|
}
|
|
|
|
return nexthop;
|
|
}
|
|
|
|
struct net_pkt *net_ipv6_prepare_for_send(struct net_pkt *pkt)
|
|
{
|
|
struct in6_addr *nexthop = NULL;
|
|
struct net_if *iface = NULL;
|
|
struct net_nbr *nbr;
|
|
int ret;
|
|
|
|
NET_ASSERT(pkt && pkt->frags);
|
|
|
|
#if defined(CONFIG_NET_IPV6_FRAGMENT)
|
|
/* If we have already fragmented the packet, the fragment id will
|
|
* contain a proper value and we can skip other checks.
|
|
*/
|
|
if (net_pkt_ipv6_fragment_id(pkt) == 0) {
|
|
size_t pkt_len = net_pkt_get_len(pkt);
|
|
|
|
if (pkt_len > NET_IPV6_MTU) {
|
|
ret = net_ipv6_send_fragmented_pkt(net_pkt_iface(pkt),
|
|
pkt, pkt_len);
|
|
if (ret < 0) {
|
|
NET_DBG("Cannot fragment IPv6 pkt (%d)", ret);
|
|
|
|
if (ret == -ENOMEM) {
|
|
/* Try to send the packet if we could
|
|
* not allocate enough network packets
|
|
* and hope the original large packet
|
|
* can be sent ok.
|
|
*/
|
|
goto ignore_frag_error;
|
|
}
|
|
}
|
|
|
|
/* We "fake" the sending of the packet here so that
|
|
* tcp.c:tcp_retry_expired() will increase the ref
|
|
* count when re-sending the packet. This is crucial
|
|
* thing to do here and will cause free memory access
|
|
* if not done.
|
|
*/
|
|
net_pkt_set_sent(pkt, true);
|
|
|
|
/* We need to unref here because we simulate the packet
|
|
* sending.
|
|
*/
|
|
net_pkt_unref(pkt);
|
|
|
|
/* No need to continue with the sending as the packet
|
|
* is now split and its fragments will be sent
|
|
* separately to network.
|
|
*/
|
|
return NULL;
|
|
}
|
|
}
|
|
ignore_frag_error:
|
|
#endif /* CONFIG_NET_IPV6_FRAGMENT */
|
|
|
|
/* Workaround Linux bug, see:
|
|
* https://github.com/zephyrproject-rtos/zephyr/issues/3111
|
|
*/
|
|
if (atomic_test_bit(net_pkt_iface(pkt)->if_dev->flags,
|
|
NET_IF_POINTOPOINT)) {
|
|
/* Update RPL header */
|
|
if (net_rpl_update_header(pkt, &NET_IPV6_HDR(pkt)->dst) < 0) {
|
|
net_pkt_unref(pkt);
|
|
return NULL;
|
|
}
|
|
|
|
return pkt;
|
|
}
|
|
|
|
/* If the IPv6 destination address is not link local, then try to get
|
|
* the next hop from routing table if we have multi interface routing
|
|
* enabled. The reason for this is that the neighbor cache will not
|
|
* contain public IPv6 address information so in that case we should
|
|
* not enter this branch.
|
|
*/
|
|
if ((net_pkt_ll_dst(pkt)->addr &&
|
|
((IS_ENABLED(CONFIG_NET_ROUTING) &&
|
|
net_is_ipv6_ll_addr(&NET_IPV6_HDR(pkt)->dst)) ||
|
|
!IS_ENABLED(CONFIG_NET_ROUTING))) ||
|
|
net_is_ipv6_addr_mcast(&NET_IPV6_HDR(pkt)->dst)) {
|
|
/* Update RPL header */
|
|
if (net_rpl_update_header(pkt, &NET_IPV6_HDR(pkt)->dst) < 0) {
|
|
net_pkt_unref(pkt);
|
|
return NULL;
|
|
}
|
|
|
|
return update_ll_reserve(pkt, &NET_IPV6_HDR(pkt)->dst);
|
|
}
|
|
|
|
if (net_if_ipv6_addr_onlink(&iface,
|
|
&NET_IPV6_HDR(pkt)->dst)) {
|
|
nexthop = &NET_IPV6_HDR(pkt)->dst;
|
|
net_pkt_set_iface(pkt, iface);
|
|
} else {
|
|
/* We need to figure out where the destination
|
|
* host is located.
|
|
*/
|
|
bool try_route = false;
|
|
|
|
nexthop = check_route(NULL, &NET_IPV6_HDR(pkt)->dst,
|
|
&try_route);
|
|
if (!nexthop) {
|
|
net_pkt_unref(pkt);
|
|
return NULL;
|
|
}
|
|
|
|
if (try_route) {
|
|
goto try_send;
|
|
}
|
|
}
|
|
|
|
if (!iface) {
|
|
/* This means that the dst was not onlink, so try to
|
|
* figure out the interface using nexthop instead.
|
|
*/
|
|
if (net_if_ipv6_addr_onlink(&iface, nexthop)) {
|
|
net_pkt_set_iface(pkt, iface);
|
|
} else {
|
|
iface = net_pkt_iface(pkt);
|
|
}
|
|
|
|
/* If the above check returns null, we try to send
|
|
* the packet and hope for the best.
|
|
*/
|
|
}
|
|
|
|
try_send:
|
|
if (net_rpl_update_header(pkt, nexthop) < 0) {
|
|
net_pkt_unref(pkt);
|
|
return NULL;
|
|
}
|
|
|
|
nbr = nbr_lookup(&net_neighbor.table, iface, nexthop);
|
|
|
|
NET_DBG("Neighbor lookup %p (%d) iface %p addr %s state %s", nbr,
|
|
nbr ? nbr->idx : NET_NBR_LLADDR_UNKNOWN, iface,
|
|
net_sprint_ipv6_addr(nexthop),
|
|
nbr ? net_ipv6_nbr_state2str(net_ipv6_nbr_data(nbr)->state) :
|
|
"-");
|
|
|
|
if (nbr && nbr->idx != NET_NBR_LLADDR_UNKNOWN) {
|
|
struct net_linkaddr_storage *lladdr;
|
|
|
|
lladdr = net_nbr_get_lladdr(nbr->idx);
|
|
|
|
net_pkt_ll_dst(pkt)->addr = lladdr->addr;
|
|
net_pkt_ll_dst(pkt)->len = lladdr->len;
|
|
|
|
NET_DBG("Neighbor %p addr %s", nbr,
|
|
net_sprint_ll_addr(lladdr->addr, lladdr->len));
|
|
|
|
/* Start the NUD if we are in STALE state.
|
|
* See RFC 4861 ch 7.3.3 for details.
|
|
*/
|
|
#if defined(CONFIG_NET_IPV6_ND)
|
|
if (net_ipv6_nbr_data(nbr)->state == NET_IPV6_NBR_STATE_STALE) {
|
|
ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_DELAY);
|
|
|
|
k_delayed_work_submit(
|
|
&net_ipv6_nbr_data(nbr)->reachable,
|
|
DELAY_FIRST_PROBE_TIME);
|
|
}
|
|
#endif
|
|
|
|
return update_ll_reserve(pkt, nexthop);
|
|
}
|
|
|
|
#if defined(CONFIG_NET_IPV6_ND)
|
|
/* We need to send NS and wait for NA before sending the packet. */
|
|
ret = net_ipv6_send_ns(net_pkt_iface(pkt), pkt,
|
|
&NET_IPV6_HDR(pkt)->src, NULL,
|
|
nexthop, false);
|
|
if (ret < 0) {
|
|
/* In case of an error, the NS send function will unref
|
|
* the pkt.
|
|
*/
|
|
NET_DBG("Cannot send NS (%d)", ret);
|
|
return NULL;
|
|
}
|
|
|
|
NET_DBG("pkt %p (frag %p) will be sent later", pkt, pkt->frags);
|
|
#else
|
|
ARG_UNUSED(ret);
|
|
|
|
NET_DBG("pkt %p (frag %p) cannot be sent, dropping it.", pkt,
|
|
pkt->frags);
|
|
|
|
net_pkt_unref(pkt);
|
|
#endif /* CONFIG_NET_IPV6_ND */
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct net_nbr *net_ipv6_nbr_lookup(struct net_if *iface,
|
|
struct in6_addr *addr)
|
|
{
|
|
return nbr_lookup(&net_neighbor.table, iface, addr);
|
|
}
|
|
|
|
struct net_nbr *net_ipv6_get_nbr(struct net_if *iface, u8_t idx)
|
|
{
|
|
int i;
|
|
|
|
if (idx == NET_NBR_LLADDR_UNKNOWN) {
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) {
|
|
struct net_nbr *nbr = get_nbr(i);
|
|
|
|
if (nbr->ref) {
|
|
if (iface && nbr->iface != iface) {
|
|
continue;
|
|
}
|
|
|
|
if (nbr->idx == idx) {
|
|
return nbr;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline u8_t get_llao_len(struct net_if *iface)
|
|
{
|
|
if (net_if_get_link_addr(iface)->len == 6) {
|
|
return 8;
|
|
} else if (net_if_get_link_addr(iface)->len == 8) {
|
|
return 16;
|
|
}
|
|
|
|
/* What else could it be? */
|
|
NET_ASSERT_INFO(0, "Invalid link address length %d",
|
|
net_if_get_link_addr(iface)->len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void set_llao(struct net_linkaddr *lladdr,
|
|
u8_t *llao, u8_t llao_len, u8_t type)
|
|
{
|
|
llao[NET_ICMPV6_OPT_TYPE_OFFSET] = type;
|
|
llao[NET_ICMPV6_OPT_LEN_OFFSET] = llao_len >> 3;
|
|
|
|
memcpy(&llao[NET_ICMPV6_OPT_DATA_OFFSET], lladdr->addr, lladdr->len);
|
|
|
|
memset(&llao[NET_ICMPV6_OPT_DATA_OFFSET + lladdr->len], 0,
|
|
llao_len - lladdr->len - 2);
|
|
}
|
|
|
|
static void setup_headers(struct net_pkt *pkt, u8_t nd6_len,
|
|
u8_t icmp_type)
|
|
{
|
|
net_buf_add(pkt->frags,
|
|
sizeof(struct net_ipv6_hdr) +
|
|
sizeof(struct net_icmp_hdr));
|
|
|
|
NET_IPV6_HDR(pkt)->vtc = 0x60;
|
|
NET_IPV6_HDR(pkt)->tcflow = 0;
|
|
NET_IPV6_HDR(pkt)->flow = 0;
|
|
NET_IPV6_HDR(pkt)->len = htons(NET_ICMPH_LEN + nd6_len);
|
|
|
|
NET_IPV6_HDR(pkt)->nexthdr = IPPROTO_ICMPV6;
|
|
NET_IPV6_HDR(pkt)->hop_limit = NET_IPV6_ND_HOP_LIMIT;
|
|
|
|
/* In this special case where we know there are no long extension
|
|
* headers, so we can use this header cast.
|
|
*/
|
|
net_pkt_icmp_data(pkt)->type = icmp_type;
|
|
net_pkt_icmp_data(pkt)->code = 0;
|
|
}
|
|
|
|
static inline struct net_nbr *handle_ns_neighbor(struct net_pkt *pkt,
|
|
u8_t ll_len,
|
|
u16_t sllao_offset)
|
|
{
|
|
struct net_linkaddr_storage lladdr;
|
|
struct net_linkaddr nbr_lladdr;
|
|
struct net_buf *frag;
|
|
u16_t pos;
|
|
|
|
lladdr.len = 8 * ll_len - 2;
|
|
|
|
frag = net_frag_read(pkt->frags, sllao_offset,
|
|
&pos, lladdr.len, lladdr.addr);
|
|
if (!frag && pos == 0xffff) {
|
|
return NULL;
|
|
}
|
|
|
|
nbr_lladdr.len = lladdr.len;
|
|
nbr_lladdr.addr = lladdr.addr;
|
|
|
|
/**
|
|
* IEEE802154 lladdress is 8 bytes long, so it requires
|
|
* 2 * 8 bytes - 2 - padding.
|
|
* The formula above needs to be adjusted.
|
|
*/
|
|
if (net_pkt_ll_src(pkt)->len < nbr_lladdr.len) {
|
|
nbr_lladdr.len = net_pkt_ll_src(pkt)->len;
|
|
}
|
|
|
|
return nbr_add(pkt, &nbr_lladdr, false, NET_IPV6_NBR_STATE_INCOMPLETE);
|
|
}
|
|
|
|
int net_ipv6_send_na(struct net_if *iface, const struct in6_addr *src,
|
|
const struct in6_addr *dst, const struct in6_addr *tgt,
|
|
u8_t flags)
|
|
{
|
|
struct net_icmpv6_na_hdr na_hdr;
|
|
struct net_pkt *pkt;
|
|
struct net_buf *frag;
|
|
u8_t llao_len;
|
|
int ret;
|
|
|
|
pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, dst),
|
|
ND_NET_BUF_TIMEOUT);
|
|
if (!pkt) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
frag = net_pkt_get_frag(pkt, ND_NET_BUF_TIMEOUT);
|
|
if (!frag) {
|
|
net_pkt_unref(pkt);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
net_pkt_frag_add(pkt, frag);
|
|
|
|
net_pkt_set_iface(pkt, iface);
|
|
net_pkt_set_family(pkt, AF_INET6);
|
|
net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr));
|
|
|
|
net_pkt_ll_clear(pkt);
|
|
|
|
llao_len = get_llao_len(iface);
|
|
|
|
net_pkt_set_ipv6_ext_len(pkt, 0);
|
|
|
|
setup_headers(pkt, sizeof(struct net_icmpv6_na_hdr) + llao_len,
|
|
NET_ICMPV6_NA);
|
|
|
|
net_buf_add(frag, sizeof(struct net_icmpv6_na_hdr) + llao_len);
|
|
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, src);
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->dst, dst);
|
|
net_ipaddr_copy(&na_hdr.tgt, tgt);
|
|
|
|
set_llao(net_if_get_link_addr(net_pkt_iface(pkt)),
|
|
(u8_t *)net_pkt_icmp_data(pkt) + sizeof(struct net_icmp_hdr) +
|
|
sizeof(struct net_icmpv6_na_hdr),
|
|
llao_len, NET_ICMPV6_ND_OPT_TLLAO);
|
|
|
|
na_hdr.flags = flags;
|
|
ret = net_icmpv6_set_na_hdr(pkt, &na_hdr);
|
|
if (ret < 0) {
|
|
net_pkt_unref(pkt);
|
|
return ret;
|
|
}
|
|
|
|
pkt->frags->len = NET_IPV6ICMPH_LEN +
|
|
sizeof(struct net_icmpv6_na_hdr) + llao_len;
|
|
|
|
ret = net_icmpv6_set_chksum(pkt);
|
|
if (ret < 0) {
|
|
net_pkt_unref(pkt);
|
|
return ret;
|
|
}
|
|
|
|
dbg_addr_sent_tgt("Neighbor Advertisement",
|
|
&NET_IPV6_HDR(pkt)->src,
|
|
&NET_IPV6_HDR(pkt)->dst,
|
|
&na_hdr.tgt);
|
|
|
|
if (net_send_data(pkt) < 0) {
|
|
goto drop;
|
|
}
|
|
|
|
net_stats_update_ipv6_nd_sent(net_pkt_iface(pkt));
|
|
|
|
return 0;
|
|
|
|
drop:
|
|
net_stats_update_ipv6_nd_drop(net_pkt_iface(pkt));
|
|
net_pkt_unref(pkt);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static void ns_routing_info(struct net_pkt *pkt,
|
|
struct in6_addr *nexthop,
|
|
struct in6_addr *tgt)
|
|
{
|
|
#if defined(CONFIG_NET_DEBUG_IPV6) && (CONFIG_SYS_LOG_NET_LEVEL > 3)
|
|
char out[NET_IPV6_ADDR_LEN];
|
|
|
|
snprintk(out, sizeof(out), "%s", net_sprint_ipv6_addr(nexthop));
|
|
|
|
if (net_ipv6_addr_cmp(nexthop, tgt)) {
|
|
NET_DBG("Routing to %s iface %p", out, net_pkt_iface(pkt));
|
|
} else {
|
|
NET_DBG("Routing to %s via %s iface %p",
|
|
net_sprint_ipv6_addr(tgt), out, net_pkt_iface(pkt));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static enum net_verdict handle_ns_input(struct net_pkt *pkt)
|
|
{
|
|
u16_t total_len = net_pkt_get_len(pkt);
|
|
u8_t prev_opt_len = 0;
|
|
u8_t flags = 0;
|
|
bool routing = false;
|
|
struct net_icmpv6_nd_opt_hdr nd_opt_hdr;
|
|
struct net_icmpv6_ns_hdr ns_hdr;
|
|
struct net_if_addr *ifaddr;
|
|
struct in6_addr *tgt;
|
|
const struct in6_addr *src;
|
|
size_t left_len;
|
|
int ret;
|
|
|
|
ret = net_icmpv6_get_ns_hdr(pkt, &ns_hdr);
|
|
if (ret < 0) {
|
|
NET_ERR("NULL NS header - dropping");
|
|
goto drop;
|
|
}
|
|
|
|
dbg_addr_recv_tgt("Neighbor Solicitation",
|
|
&NET_IPV6_HDR(pkt)->src,
|
|
&NET_IPV6_HDR(pkt)->dst,
|
|
&ns_hdr.tgt);
|
|
|
|
net_stats_update_ipv6_nd_recv(net_pkt_iface(pkt));
|
|
|
|
if ((total_len < (sizeof(struct net_ipv6_hdr) +
|
|
sizeof(struct net_icmp_hdr) +
|
|
sizeof(struct net_icmpv6_ns_hdr))) ||
|
|
(NET_IPV6_HDR(pkt)->hop_limit != NET_IPV6_ND_HOP_LIMIT)) {
|
|
if (net_is_ipv6_addr_mcast(&ns_hdr.tgt)) {
|
|
struct net_icmp_hdr icmp_hdr;
|
|
|
|
ret = net_icmpv6_get_hdr(pkt, &icmp_hdr);
|
|
if (ret < 0 || icmp_hdr.code != 0) {
|
|
NET_DBG("Preliminary check failed %u/%zu, "
|
|
"code %u, hop %u",
|
|
total_len,
|
|
(sizeof(struct net_ipv6_hdr) +
|
|
sizeof(struct net_icmp_hdr) +
|
|
sizeof(struct net_icmpv6_ns_hdr)),
|
|
icmp_hdr.code,
|
|
NET_IPV6_HDR(pkt)->hop_limit);
|
|
goto drop;
|
|
}
|
|
}
|
|
}
|
|
|
|
net_pkt_set_ipv6_ext_opt_len(pkt, sizeof(struct net_icmpv6_ns_hdr));
|
|
|
|
left_len = net_pkt_get_len(pkt) - (sizeof(struct net_ipv6_hdr) +
|
|
sizeof(struct net_icmp_hdr));
|
|
|
|
ret = net_icmpv6_get_nd_opt_hdr(pkt, &nd_opt_hdr);
|
|
|
|
while (!ret && net_pkt_ipv6_ext_opt_len(pkt) < left_len) {
|
|
if (!nd_opt_hdr.len) {
|
|
break;
|
|
}
|
|
|
|
switch (nd_opt_hdr.type) {
|
|
case NET_ICMPV6_ND_OPT_SLLAO:
|
|
if (net_is_ipv6_addr_unspecified(
|
|
&NET_IPV6_HDR(pkt)->src)) {
|
|
goto drop;
|
|
}
|
|
|
|
if (nd_opt_hdr.len > 2) {
|
|
NET_ERR("Too long source link-layer address "
|
|
"in NS option");
|
|
goto drop;
|
|
}
|
|
|
|
if (!handle_ns_neighbor(pkt, nd_opt_hdr.len,
|
|
net_pkt_ip_hdr_len(pkt) +
|
|
net_pkt_ipv6_ext_len(pkt) +
|
|
sizeof(struct net_icmp_hdr) +
|
|
net_pkt_ipv6_ext_opt_len(pkt) +
|
|
1 + 1)) {
|
|
goto drop;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("Unknown ND option 0x%x", nd_opt_hdr.type);
|
|
break;
|
|
}
|
|
|
|
prev_opt_len = net_pkt_ipv6_ext_opt_len(pkt);
|
|
|
|
net_pkt_set_ipv6_ext_opt_len(pkt,
|
|
net_pkt_ipv6_ext_opt_len(pkt) +
|
|
(nd_opt_hdr.len << 3));
|
|
|
|
if (prev_opt_len >= net_pkt_ipv6_ext_opt_len(pkt)) {
|
|
NET_ERR("Corrupted NS message");
|
|
goto drop;
|
|
}
|
|
|
|
ret = net_icmpv6_get_nd_opt_hdr(pkt, &nd_opt_hdr);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_NET_ROUTING)) {
|
|
ifaddr = net_if_ipv6_addr_lookup(&ns_hdr.tgt, NULL);
|
|
} else {
|
|
ifaddr = net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt),
|
|
&ns_hdr.tgt);
|
|
}
|
|
|
|
if (!ifaddr) {
|
|
if (IS_ENABLED(CONFIG_NET_ROUTING)) {
|
|
struct in6_addr *nexthop;
|
|
|
|
nexthop = check_route(NULL, &ns_hdr.tgt, NULL);
|
|
if (nexthop) {
|
|
ns_routing_info(pkt, nexthop, &ns_hdr.tgt);
|
|
|
|
/* Note that the target is not the address of
|
|
* the "nethop" as that is a link-local address
|
|
* which is not routable.
|
|
*/
|
|
tgt = &ns_hdr.tgt;
|
|
|
|
/* Source address must be one of our real
|
|
* interface address where the packet was
|
|
* received.
|
|
*/
|
|
src = net_if_ipv6_select_src_addr(
|
|
net_pkt_iface(pkt),
|
|
&NET_IPV6_HDR(pkt)->src);
|
|
if (!src) {
|
|
NET_DBG("No interface address for "
|
|
"dst %s iface %p",
|
|
net_sprint_ipv6_addr(
|
|
&NET_IPV6_HDR(pkt)->src),
|
|
net_pkt_iface(pkt));
|
|
goto drop;
|
|
}
|
|
|
|
routing = true;
|
|
goto nexthop_found;
|
|
}
|
|
}
|
|
|
|
NET_DBG("No such interface address %s",
|
|
net_sprint_ipv6_addr(&ns_hdr.tgt));
|
|
goto drop;
|
|
} else {
|
|
tgt = &ifaddr->address.in6_addr;
|
|
|
|
/* As we swap the addresses later, the source will correctly
|
|
* have our address.
|
|
*/
|
|
src = &NET_IPV6_HDR(pkt)->src;
|
|
}
|
|
|
|
nexthop_found:
|
|
|
|
#if !defined(CONFIG_NET_IPV6_DAD)
|
|
if (net_is_ipv6_addr_unspecified(&NET_IPV6_HDR(pkt)->src)) {
|
|
goto drop;
|
|
}
|
|
|
|
#else /* CONFIG_NET_IPV6_DAD */
|
|
|
|
/* Do DAD */
|
|
if (net_is_ipv6_addr_unspecified(&NET_IPV6_HDR(pkt)->src)) {
|
|
|
|
if (!net_is_ipv6_addr_solicited_node(&NET_IPV6_HDR(pkt)->dst)) {
|
|
NET_DBG("Not solicited node addr %s",
|
|
net_sprint_ipv6_addr(&NET_IPV6_HDR(pkt)->dst));
|
|
goto drop;
|
|
}
|
|
|
|
if (ifaddr->addr_state == NET_ADDR_TENTATIVE) {
|
|
NET_DBG("DAD failed for %s iface %p",
|
|
net_sprint_ipv6_addr(&ifaddr->address.in6_addr),
|
|
net_pkt_iface(pkt));
|
|
|
|
dad_failed(net_pkt_iface(pkt),
|
|
&ifaddr->address.in6_addr);
|
|
goto drop;
|
|
}
|
|
|
|
/* We reuse the received packet to send the NA */
|
|
net_ipv6_addr_create_ll_allnodes_mcast(&NET_IPV6_HDR(pkt)->dst);
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src,
|
|
net_if_ipv6_select_src_addr(net_pkt_iface(pkt),
|
|
&NET_IPV6_HDR(pkt)->dst));
|
|
flags = NET_ICMPV6_NA_FLAG_OVERRIDE;
|
|
goto send_na;
|
|
}
|
|
#endif /* CONFIG_NET_IPV6_DAD */
|
|
|
|
if (net_is_my_ipv6_addr(&NET_IPV6_HDR(pkt)->src)) {
|
|
NET_DBG("Duplicate IPv6 %s address",
|
|
net_sprint_ipv6_addr(&NET_IPV6_HDR(pkt)->src));
|
|
goto drop;
|
|
}
|
|
|
|
/* Address resolution */
|
|
if (net_is_ipv6_addr_solicited_node(&NET_IPV6_HDR(pkt)->dst)) {
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->dst,
|
|
&NET_IPV6_HDR(pkt)->src);
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, &ns_hdr.tgt);
|
|
flags = NET_ICMPV6_NA_FLAG_SOLICITED |
|
|
NET_ICMPV6_NA_FLAG_OVERRIDE;
|
|
goto send_na;
|
|
}
|
|
|
|
if (routing) {
|
|
/* No need to do NUD here when the target is being routed. */
|
|
goto send_na;
|
|
}
|
|
|
|
/* Neighbor Unreachability Detection (NUD) */
|
|
if (IS_ENABLED(CONFIG_NET_ROUTING)) {
|
|
ifaddr = net_if_ipv6_addr_lookup(&NET_IPV6_HDR(pkt)->dst,
|
|
NULL);
|
|
} else {
|
|
ifaddr = net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt),
|
|
&NET_IPV6_HDR(pkt)->dst);
|
|
}
|
|
|
|
if (ifaddr) {
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->dst,
|
|
&NET_IPV6_HDR(pkt)->src);
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, &ns_hdr.tgt);
|
|
src = &NET_IPV6_HDR(pkt)->src;
|
|
tgt = &ifaddr->address.in6_addr;
|
|
flags = NET_ICMPV6_NA_FLAG_SOLICITED |
|
|
NET_ICMPV6_NA_FLAG_OVERRIDE;
|
|
goto send_na;
|
|
} else {
|
|
NET_DBG("NUD failed");
|
|
goto drop;
|
|
}
|
|
|
|
send_na:
|
|
ret = net_ipv6_send_na(net_pkt_iface(pkt),
|
|
src,
|
|
&NET_IPV6_HDR(pkt)->dst,
|
|
tgt,
|
|
flags);
|
|
if (!ret) {
|
|
net_pkt_unref(pkt);
|
|
return NET_OK;
|
|
}
|
|
|
|
NET_DBG("Cannot send NA (%d)", ret);
|
|
|
|
return NET_DROP;
|
|
|
|
drop:
|
|
net_stats_update_ipv6_nd_drop(net_pkt_iface(pkt));
|
|
|
|
return NET_DROP;
|
|
}
|
|
#endif /* CONFIG_NET_IPV6_NBR_CACHE */
|
|
|
|
#if defined(CONFIG_NET_IPV6_ND)
|
|
static void nd_reachable_timeout(struct k_work *work)
|
|
{
|
|
struct net_ipv6_nbr_data *data = CONTAINER_OF(work,
|
|
struct net_ipv6_nbr_data,
|
|
reachable);
|
|
|
|
struct net_nbr *nbr = get_nbr_from_data(data);
|
|
int ret;
|
|
|
|
if (!data || !nbr) {
|
|
NET_DBG("ND reachable timeout but no nbr data "
|
|
"(nbr %p data %p)", nbr, data);
|
|
return;
|
|
}
|
|
|
|
if (net_rpl_get_interface() && nbr->iface == net_rpl_get_interface()) {
|
|
/* The address belongs to RPL network, no need to activate
|
|
* full neighbor reachable rules in this case.
|
|
* Mark the neighbor always reachable.
|
|
*/
|
|
data->state = NET_IPV6_NBR_STATE_REACHABLE;
|
|
return;
|
|
}
|
|
|
|
switch (data->state) {
|
|
case NET_IPV6_NBR_STATE_STATIC:
|
|
NET_ASSERT_INFO(false, "Static entry shall never timeout");
|
|
break;
|
|
|
|
case NET_IPV6_NBR_STATE_INCOMPLETE:
|
|
if (data->ns_count >= MAX_MULTICAST_SOLICIT) {
|
|
nbr_free(nbr);
|
|
} else {
|
|
data->ns_count++;
|
|
|
|
NET_DBG("nbr %p incomplete count %u", nbr,
|
|
data->ns_count);
|
|
|
|
ret = net_ipv6_send_ns(nbr->iface, NULL, NULL, NULL,
|
|
&data->addr, false);
|
|
if (ret < 0) {
|
|
NET_DBG("Cannot send NS (%d)", ret);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NET_IPV6_NBR_STATE_REACHABLE:
|
|
data->state = NET_IPV6_NBR_STATE_STALE;
|
|
|
|
NET_DBG("nbr %p moving %s state to STALE (%d)",
|
|
nbr, net_sprint_ipv6_addr(&data->addr), data->state);
|
|
break;
|
|
|
|
case NET_IPV6_NBR_STATE_STALE:
|
|
NET_DBG("nbr %p removing stale address %s",
|
|
nbr, net_sprint_ipv6_addr(&data->addr));
|
|
nbr_free(nbr);
|
|
break;
|
|
|
|
case NET_IPV6_NBR_STATE_DELAY:
|
|
data->state = NET_IPV6_NBR_STATE_PROBE;
|
|
data->ns_count = 0;
|
|
|
|
NET_DBG("nbr %p moving %s state to PROBE (%d)",
|
|
nbr, net_sprint_ipv6_addr(&data->addr), data->state);
|
|
|
|
/* Intentionally continuing to probe state */
|
|
|
|
case NET_IPV6_NBR_STATE_PROBE:
|
|
if (data->ns_count >= MAX_UNICAST_SOLICIT) {
|
|
struct net_if_router *router;
|
|
|
|
router = net_if_ipv6_router_lookup(nbr->iface,
|
|
&data->addr);
|
|
if (router && !router->is_infinite) {
|
|
NET_DBG("nbr %p address %s PROBE ended (%d)",
|
|
nbr, net_sprint_ipv6_addr(&data->addr),
|
|
data->state);
|
|
|
|
net_if_ipv6_router_rm(router);
|
|
nbr_free(nbr);
|
|
}
|
|
} else {
|
|
data->ns_count++;
|
|
|
|
NET_DBG("nbr %p probe count %u", nbr,
|
|
data->ns_count);
|
|
|
|
ret = net_ipv6_send_ns(nbr->iface, NULL, NULL, NULL,
|
|
&data->addr, false);
|
|
if (ret < 0) {
|
|
NET_DBG("Cannot send NS (%d)", ret);
|
|
}
|
|
|
|
k_delayed_work_submit(
|
|
&net_ipv6_nbr_data(nbr)->reachable,
|
|
RETRANS_TIMER);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void net_ipv6_nbr_set_reachable_timer(struct net_if *iface,
|
|
struct net_nbr *nbr)
|
|
{
|
|
u32_t time;
|
|
|
|
time = net_if_ipv6_get_reachable_time(iface);
|
|
|
|
NET_ASSERT_INFO(time, "Zero reachable timeout!");
|
|
|
|
NET_DBG("Starting reachable timer nbr %p data %p time %d ms",
|
|
nbr, net_ipv6_nbr_data(nbr), time);
|
|
|
|
k_delayed_work_submit(&net_ipv6_nbr_data(nbr)->reachable, time);
|
|
}
|
|
#endif /* CONFIG_NET_IPV6_ND */
|
|
|
|
#if defined(CONFIG_NET_IPV6_NBR_CACHE)
|
|
static inline bool handle_na_neighbor(struct net_pkt *pkt,
|
|
struct net_icmpv6_na_hdr *na_hdr,
|
|
u16_t tllao_offset)
|
|
{
|
|
bool lladdr_changed = false;
|
|
struct net_nbr *nbr;
|
|
struct net_linkaddr_storage lladdr = { 0 };
|
|
struct net_linkaddr_storage *cached_lladdr;
|
|
struct net_pkt *pending;
|
|
struct net_buf *frag;
|
|
u16_t pos;
|
|
|
|
nbr = nbr_lookup(&net_neighbor.table, net_pkt_iface(pkt),
|
|
&na_hdr->tgt);
|
|
|
|
NET_DBG("Neighbor lookup %p iface %p addr %s", nbr,
|
|
net_pkt_iface(pkt),
|
|
net_sprint_ipv6_addr(&na_hdr->tgt));
|
|
|
|
if (!nbr) {
|
|
nbr_print();
|
|
|
|
NET_DBG("No such neighbor found, msg discarded");
|
|
return false;
|
|
}
|
|
|
|
if (tllao_offset) {
|
|
lladdr.len = net_if_get_link_addr(net_pkt_iface(pkt))->len;
|
|
|
|
frag = net_frag_read(pkt->frags, tllao_offset,
|
|
&pos, lladdr.len, lladdr.addr);
|
|
if (!frag && pos == 0xffff) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (nbr->idx == NET_NBR_LLADDR_UNKNOWN) {
|
|
struct net_linkaddr nbr_lladdr;
|
|
|
|
if (!tllao_offset) {
|
|
NET_DBG("No target link layer address.");
|
|
return false;
|
|
}
|
|
|
|
nbr_lladdr.len = lladdr.len;
|
|
nbr_lladdr.addr = lladdr.addr;
|
|
|
|
if (net_nbr_link(nbr, net_pkt_iface(pkt), &nbr_lladdr)) {
|
|
nbr_free(nbr);
|
|
return false;
|
|
}
|
|
|
|
NET_DBG("[%d] nbr %p state %d IPv6 %s ll %s",
|
|
nbr->idx, nbr, net_ipv6_nbr_data(nbr)->state,
|
|
net_sprint_ipv6_addr(&na_hdr->tgt),
|
|
net_sprint_ll_addr(nbr_lladdr.addr, nbr_lladdr.len));
|
|
}
|
|
|
|
cached_lladdr = net_nbr_get_lladdr(nbr->idx);
|
|
if (!cached_lladdr) {
|
|
NET_DBG("No lladdr but index defined");
|
|
return false;
|
|
}
|
|
|
|
if (tllao_offset) {
|
|
lladdr_changed = memcmp(lladdr.addr,
|
|
cached_lladdr->addr,
|
|
cached_lladdr->len);
|
|
}
|
|
|
|
/* Update the cached address if we do not yet known it */
|
|
if (net_ipv6_nbr_data(nbr)->state == NET_IPV6_NBR_STATE_INCOMPLETE) {
|
|
if (!tllao_offset) {
|
|
return false;
|
|
}
|
|
|
|
if (lladdr_changed) {
|
|
dbg_update_neighbor_lladdr_raw(lladdr.addr,
|
|
cached_lladdr,
|
|
&na_hdr->tgt);
|
|
|
|
net_linkaddr_set(cached_lladdr, lladdr.addr,
|
|
cached_lladdr->len);
|
|
}
|
|
|
|
if (net_is_solicited(pkt)) {
|
|
ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_REACHABLE);
|
|
net_ipv6_nbr_data(nbr)->ns_count = 0;
|
|
|
|
/* We might have active timer from PROBE */
|
|
k_delayed_work_cancel(
|
|
&net_ipv6_nbr_data(nbr)->reachable);
|
|
|
|
net_ipv6_nbr_set_reachable_timer(net_pkt_iface(pkt),
|
|
nbr);
|
|
} else {
|
|
ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_STALE);
|
|
}
|
|
|
|
net_ipv6_nbr_data(nbr)->is_router = net_is_router(pkt);
|
|
|
|
goto send_pending;
|
|
}
|
|
|
|
/* We do not update the address if override bit is not set
|
|
* and we have a valid address in the cache.
|
|
*/
|
|
if (!net_is_override(pkt) && lladdr_changed) {
|
|
if (net_ipv6_nbr_data(nbr)->state ==
|
|
NET_IPV6_NBR_STATE_REACHABLE) {
|
|
ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_STALE);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (net_is_override(pkt) ||
|
|
(!net_is_override(pkt) && tllao_offset && !lladdr_changed)) {
|
|
|
|
if (lladdr_changed) {
|
|
dbg_update_neighbor_lladdr_raw(
|
|
lladdr.addr, cached_lladdr, &na_hdr->tgt);
|
|
|
|
net_linkaddr_set(cached_lladdr, lladdr.addr,
|
|
cached_lladdr->len);
|
|
}
|
|
|
|
if (net_is_solicited(pkt)) {
|
|
ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_REACHABLE);
|
|
|
|
/* We might have active timer from PROBE */
|
|
k_delayed_work_cancel(
|
|
&net_ipv6_nbr_data(nbr)->reachable);
|
|
|
|
net_ipv6_nbr_set_reachable_timer(net_pkt_iface(pkt),
|
|
nbr);
|
|
} else {
|
|
if (lladdr_changed) {
|
|
ipv6_nbr_set_state(nbr,
|
|
NET_IPV6_NBR_STATE_STALE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (net_ipv6_nbr_data(nbr)->is_router && !net_is_router(pkt)) {
|
|
/* Update the routing if the peer is no longer
|
|
* a router.
|
|
*/
|
|
/* FIXME */
|
|
}
|
|
|
|
net_ipv6_nbr_data(nbr)->is_router = net_is_router(pkt);
|
|
|
|
send_pending:
|
|
/* Next send any pending messages to the peer. */
|
|
pending = net_ipv6_nbr_data(nbr)->pending;
|
|
|
|
if (pending) {
|
|
NET_DBG("Sending pending %p to %s lladdr %s", pending,
|
|
net_sprint_ipv6_addr(&NET_IPV6_HDR(pending)->dst),
|
|
net_sprint_ll_addr(cached_lladdr->addr,
|
|
cached_lladdr->len));
|
|
|
|
if (net_send_data(pending) < 0) {
|
|
nbr_clear_ns_pending(net_ipv6_nbr_data(nbr));
|
|
} else {
|
|
net_ipv6_nbr_data(nbr)->pending = NULL;
|
|
}
|
|
|
|
net_pkt_unref(pending);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static enum net_verdict handle_na_input(struct net_pkt *pkt)
|
|
{
|
|
u16_t total_len = net_pkt_get_len(pkt);
|
|
u16_t tllao_offset = 0;
|
|
u8_t prev_opt_len = 0;
|
|
struct net_icmpv6_nd_opt_hdr nd_opt_hdr;
|
|
struct net_icmpv6_na_hdr na_hdr;
|
|
struct net_if_addr *ifaddr;
|
|
size_t left_len;
|
|
int ret;
|
|
|
|
ret = net_icmpv6_get_na_hdr(pkt, &na_hdr);
|
|
if (ret < 0) {
|
|
NET_ERR("NULL NA header - dropping");
|
|
goto drop;
|
|
}
|
|
|
|
dbg_addr_recv_tgt("Neighbor Advertisement",
|
|
&NET_IPV6_HDR(pkt)->src,
|
|
&NET_IPV6_HDR(pkt)->dst,
|
|
&na_hdr.tgt);
|
|
|
|
net_stats_update_ipv6_nd_recv(net_pkt_iface(pkt));
|
|
|
|
if ((total_len < (sizeof(struct net_ipv6_hdr) +
|
|
sizeof(struct net_icmp_hdr) +
|
|
sizeof(struct net_icmpv6_na_hdr) +
|
|
sizeof(struct net_icmpv6_nd_opt_hdr))) ||
|
|
(NET_IPV6_HDR(pkt)->hop_limit != NET_IPV6_ND_HOP_LIMIT) ||
|
|
net_is_ipv6_addr_mcast(&na_hdr.tgt) ||
|
|
(net_is_solicited(pkt) &&
|
|
net_is_ipv6_addr_mcast(&NET_IPV6_HDR(pkt)->dst))) {
|
|
struct net_icmp_hdr icmp_hdr;
|
|
|
|
ret = net_icmpv6_get_hdr(pkt, &icmp_hdr);
|
|
if (ret < 0 || icmp_hdr.code != 0) {
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
net_pkt_set_ipv6_ext_opt_len(pkt, sizeof(struct net_icmpv6_na_hdr));
|
|
|
|
left_len = net_pkt_get_len(pkt) - (sizeof(struct net_ipv6_hdr) +
|
|
sizeof(struct net_icmp_hdr));
|
|
|
|
ret = net_icmpv6_get_nd_opt_hdr(pkt, &nd_opt_hdr);
|
|
|
|
while (!ret && net_pkt_ipv6_ext_opt_len(pkt) < left_len) {
|
|
if (!nd_opt_hdr.len) {
|
|
break;
|
|
}
|
|
|
|
switch (nd_opt_hdr.type) {
|
|
case NET_ICMPV6_ND_OPT_TLLAO:
|
|
tllao_offset = net_pkt_ip_hdr_len(pkt) +
|
|
net_pkt_ipv6_ext_len(pkt) +
|
|
sizeof(struct net_icmp_hdr) +
|
|
net_pkt_ipv6_ext_opt_len(pkt) + 1 + 1;
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("Unknown ND option 0x%x", nd_opt_hdr.type);
|
|
break;
|
|
}
|
|
|
|
prev_opt_len = net_pkt_ipv6_ext_opt_len(pkt);
|
|
|
|
net_pkt_set_ipv6_ext_opt_len(pkt,
|
|
net_pkt_ipv6_ext_opt_len(pkt) +
|
|
(nd_opt_hdr.len << 3));
|
|
|
|
if (prev_opt_len >= net_pkt_ipv6_ext_opt_len(pkt)) {
|
|
NET_ERR("Corrupted NA message");
|
|
goto drop;
|
|
}
|
|
|
|
ret = net_icmpv6_get_nd_opt_hdr(pkt, &nd_opt_hdr);
|
|
}
|
|
|
|
ifaddr = net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt),
|
|
&na_hdr.tgt);
|
|
if (ifaddr) {
|
|
NET_DBG("Interface %p already has address %s",
|
|
net_pkt_iface(pkt),
|
|
net_sprint_ipv6_addr(&na_hdr.tgt));
|
|
|
|
#if defined(CONFIG_NET_IPV6_DAD)
|
|
if (ifaddr->addr_state == NET_ADDR_TENTATIVE) {
|
|
dad_failed(net_pkt_iface(pkt), &na_hdr.tgt);
|
|
}
|
|
#endif /* CONFIG_NET_IPV6_DAD */
|
|
|
|
goto drop;
|
|
}
|
|
|
|
if (!handle_na_neighbor(pkt, &na_hdr, tllao_offset)) {
|
|
goto drop;
|
|
}
|
|
|
|
net_stats_update_ipv6_nd_sent(net_pkt_iface(pkt));
|
|
|
|
net_pkt_unref(pkt);
|
|
|
|
return NET_OK;
|
|
|
|
drop:
|
|
net_stats_update_ipv6_nd_drop(net_pkt_iface(pkt));
|
|
|
|
return NET_DROP;
|
|
}
|
|
|
|
int net_ipv6_send_ns(struct net_if *iface,
|
|
struct net_pkt *pending,
|
|
struct in6_addr *src,
|
|
struct in6_addr *dst,
|
|
struct in6_addr *tgt,
|
|
bool is_my_address)
|
|
{
|
|
struct net_icmpv6_ns_hdr ns_hdr;
|
|
struct net_pkt *pkt;
|
|
struct net_buf *frag;
|
|
struct net_nbr *nbr;
|
|
u8_t llao_len;
|
|
int ret;
|
|
|
|
pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, dst),
|
|
ND_NET_BUF_TIMEOUT);
|
|
if (!pkt) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
frag = net_pkt_get_frag(pkt, ND_NET_BUF_TIMEOUT);
|
|
if (!frag) {
|
|
net_pkt_unref(pkt);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
net_pkt_frag_add(pkt, frag);
|
|
|
|
net_pkt_set_iface(pkt, iface);
|
|
net_pkt_set_family(pkt, AF_INET6);
|
|
net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr));
|
|
net_pkt_set_ipv6_ext_len(pkt, 0);
|
|
|
|
net_pkt_ll_clear(pkt);
|
|
|
|
llao_len = get_llao_len(net_pkt_iface(pkt));
|
|
|
|
setup_headers(pkt, sizeof(struct net_icmpv6_ns_hdr) + llao_len,
|
|
NET_ICMPV6_NS);
|
|
|
|
net_buf_add(frag, sizeof(struct net_icmpv6_ns_hdr));
|
|
|
|
if (!dst) {
|
|
net_ipv6_addr_create_solicited_node(tgt,
|
|
&NET_IPV6_HDR(pkt)->dst);
|
|
} else {
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->dst, dst);
|
|
}
|
|
|
|
net_ipaddr_copy(&ns_hdr.tgt, tgt);
|
|
ret = net_icmpv6_set_ns_hdr(pkt, &ns_hdr);
|
|
if (ret < 0) {
|
|
net_pkt_unref(pkt);
|
|
return ret;
|
|
}
|
|
|
|
if (is_my_address) {
|
|
u16_t len = ntohs(NET_IPV6_HDR(pkt)->len);
|
|
/* DAD */
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src,
|
|
net_ipv6_unspecified_address());
|
|
NET_IPV6_HDR(pkt)->len = htons(len - llao_len);
|
|
} else {
|
|
if (src) {
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, src);
|
|
} else {
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src,
|
|
net_if_ipv6_select_src_addr(
|
|
net_pkt_iface(pkt),
|
|
&NET_IPV6_HDR(pkt)->dst));
|
|
}
|
|
|
|
if (net_is_ipv6_addr_unspecified(&NET_IPV6_HDR(pkt)->src)) {
|
|
NET_DBG("No source address for NS");
|
|
if (pending) {
|
|
net_pkt_unref(pending);
|
|
}
|
|
|
|
goto drop;
|
|
}
|
|
|
|
net_buf_add(frag, llao_len);
|
|
|
|
set_llao(net_if_get_link_addr(net_pkt_iface(pkt)),
|
|
(u8_t *)net_pkt_icmp_data(pkt) +
|
|
sizeof(struct net_icmp_hdr) +
|
|
sizeof(struct net_icmpv6_ns_hdr),
|
|
llao_len, NET_ICMPV6_ND_OPT_SLLAO);
|
|
}
|
|
|
|
ret = net_icmpv6_set_chksum(pkt);
|
|
if (ret < 0) {
|
|
net_pkt_unref(pkt);
|
|
return ret;
|
|
}
|
|
|
|
nbr = nbr_lookup(&net_neighbor.table, net_pkt_iface(pkt), &ns_hdr.tgt);
|
|
if (!nbr) {
|
|
nbr_print();
|
|
|
|
nbr = nbr_new(net_pkt_iface(pkt), &ns_hdr.tgt, false,
|
|
NET_IPV6_NBR_STATE_INCOMPLETE);
|
|
if (!nbr) {
|
|
NET_DBG("Could not create new neighbor %s",
|
|
net_sprint_ipv6_addr(&ns_hdr.tgt));
|
|
if (pending) {
|
|
net_pkt_unref(pending);
|
|
}
|
|
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
if (pending) {
|
|
if (!net_ipv6_nbr_data(nbr)->pending) {
|
|
net_ipv6_nbr_data(nbr)->pending = net_pkt_ref(pending);
|
|
} else {
|
|
NET_DBG("Packet %p already pending for "
|
|
"operation. Discarding pending %p and pkt %p",
|
|
net_ipv6_nbr_data(nbr)->pending, pending, pkt);
|
|
net_pkt_unref(pending);
|
|
goto drop;
|
|
}
|
|
|
|
NET_DBG("Setting timeout %d for NS", NS_REPLY_TIMEOUT);
|
|
|
|
k_delayed_work_submit(&net_ipv6_nbr_data(nbr)->send_ns,
|
|
NS_REPLY_TIMEOUT);
|
|
}
|
|
|
|
dbg_addr_sent_tgt("Neighbor Solicitation",
|
|
&NET_IPV6_HDR(pkt)->src,
|
|
&NET_IPV6_HDR(pkt)->dst,
|
|
&ns_hdr.tgt);
|
|
|
|
if (net_send_data(pkt) < 0) {
|
|
NET_DBG("Cannot send NS %p (pending %p)", pkt, pending);
|
|
|
|
if (pending) {
|
|
nbr_clear_ns_pending(net_ipv6_nbr_data(nbr));
|
|
}
|
|
|
|
goto drop;
|
|
}
|
|
|
|
net_stats_update_ipv6_nd_sent(net_pkt_iface(pkt));
|
|
|
|
return 0;
|
|
|
|
drop:
|
|
net_stats_update_ipv6_nd_drop(net_pkt_iface(pkt));
|
|
net_pkt_unref(pkt);
|
|
|
|
return -EINVAL;
|
|
}
|
|
#endif /* CONFIG_NET_IPV6_NBR_CACHE */
|
|
|
|
#if defined(CONFIG_NET_IPV6_ND)
|
|
int net_ipv6_send_rs(struct net_if *iface)
|
|
{
|
|
struct net_pkt *pkt;
|
|
struct net_buf *frag;
|
|
bool unspec_src;
|
|
u8_t llao_len = 0;
|
|
int ret;
|
|
|
|
pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, NULL),
|
|
ND_NET_BUF_TIMEOUT);
|
|
if (!pkt) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
frag = net_pkt_get_frag(pkt, ND_NET_BUF_TIMEOUT);
|
|
if (!frag) {
|
|
net_pkt_unref(pkt);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
net_pkt_frag_add(pkt, frag);
|
|
|
|
net_pkt_set_iface(pkt, iface);
|
|
net_pkt_set_family(pkt, AF_INET6);
|
|
net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr));
|
|
|
|
net_pkt_ll_clear(pkt);
|
|
|
|
net_ipv6_addr_create_ll_allnodes_mcast(&NET_IPV6_HDR(pkt)->dst);
|
|
|
|
net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src,
|
|
net_if_ipv6_select_src_addr(iface,
|
|
&NET_IPV6_HDR(pkt)->dst));
|
|
|
|
unspec_src = net_is_ipv6_addr_unspecified(&NET_IPV6_HDR(pkt)->src);
|
|
if (!unspec_src) {
|
|
llao_len = get_llao_len(net_pkt_iface(pkt));
|
|
}
|
|
|
|
setup_headers(pkt, sizeof(struct net_icmpv6_rs_hdr) + llao_len,
|
|
NET_ICMPV6_RS);
|
|
|
|
net_buf_add(frag, sizeof(struct net_icmpv6_rs_hdr));
|
|
|
|
if (!unspec_src) {
|
|
net_buf_add(frag, llao_len);
|
|
|
|
set_llao(net_if_get_link_addr(net_pkt_iface(pkt)),
|
|
(u8_t *)net_pkt_icmp_data(pkt) +
|
|
sizeof(struct net_icmp_hdr) +
|
|
sizeof(struct net_icmpv6_rs_hdr),
|
|
llao_len, NET_ICMPV6_ND_OPT_SLLAO);
|
|
}
|
|
|
|
ret = net_icmpv6_set_chksum(pkt);
|
|
if (ret < 0) {
|
|
net_pkt_unref(pkt);
|
|
return ret;
|
|
}
|
|
|
|
dbg_addr_sent("Router Solicitation",
|
|
&NET_IPV6_HDR(pkt)->src,
|
|
&NET_IPV6_HDR(pkt)->dst);
|
|
|
|
if (net_send_data(pkt) < 0) {
|
|
goto drop;
|
|
}
|
|
|
|
net_stats_update_ipv6_nd_sent(net_pkt_iface(pkt));
|
|
|
|
return 0;
|
|
|
|
drop:
|
|
net_stats_update_ipv6_nd_drop(net_pkt_iface(pkt));
|
|
net_pkt_unref(pkt);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
int net_ipv6_start_rs(struct net_if *iface)
|
|
{
|
|
return net_ipv6_send_rs(iface);
|
|
}
|
|
|
|
static inline struct net_buf *handle_ra_neighbor(struct net_pkt *pkt,
|
|
struct net_buf *frag,
|
|
u8_t len,
|
|
u16_t offset, u16_t *pos,
|
|
struct net_nbr **nbr)
|
|
|
|
{
|
|
struct net_linkaddr lladdr;
|
|
struct net_linkaddr_storage llstorage;
|
|
u8_t padding;
|
|
|
|
if (!nbr) {
|
|
return NULL;
|
|
}
|
|
|
|
llstorage.len = NET_LINK_ADDR_MAX_LENGTH;
|
|
lladdr.len = NET_LINK_ADDR_MAX_LENGTH;
|
|
lladdr.addr = llstorage.addr;
|
|
if (net_pkt_ll_src(pkt)->len < lladdr.len) {
|
|
lladdr.len = net_pkt_ll_src(pkt)->len;
|
|
}
|
|
|
|
frag = net_frag_read(frag, offset, pos, lladdr.len, lladdr.addr);
|
|
if (!frag && offset) {
|
|
return NULL;
|
|
}
|
|
|
|
padding = len * 8 - 2 - lladdr.len;
|
|
if (padding) {
|
|
frag = net_frag_read(frag, *pos, pos, padding, NULL);
|
|
if (!frag && *pos) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
*nbr = nbr_add(pkt, &lladdr, true, NET_IPV6_NBR_STATE_STALE);
|
|
|
|
return frag;
|
|
}
|
|
|
|
static inline void handle_prefix_onlink(struct net_pkt *pkt,
|
|
struct net_icmpv6_nd_opt_prefix_info *prefix_info)
|
|
{
|
|
struct net_if_ipv6_prefix *prefix;
|
|
|
|
prefix = net_if_ipv6_prefix_lookup(net_pkt_iface(pkt),
|
|
&prefix_info->prefix,
|
|
prefix_info->prefix_len);
|
|
if (!prefix) {
|
|
if (!prefix_info->valid_lifetime) {
|
|
return;
|
|
}
|
|
|
|
prefix = net_if_ipv6_prefix_add(net_pkt_iface(pkt),
|
|
&prefix_info->prefix,
|
|
prefix_info->prefix_len,
|
|
prefix_info->valid_lifetime);
|
|
if (prefix) {
|
|
NET_DBG("Interface %p add prefix %s/%d lifetime %u",
|
|
net_pkt_iface(pkt),
|
|
net_sprint_ipv6_addr(&prefix_info->prefix),
|
|
prefix_info->prefix_len,
|
|
prefix_info->valid_lifetime);
|
|
} else {
|
|
NET_ERR("Prefix %s/%d could not be added to iface %p",
|
|
net_sprint_ipv6_addr(&prefix_info->prefix),
|
|
prefix_info->prefix_len,
|
|
net_pkt_iface(pkt));
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (prefix_info->valid_lifetime) {
|
|
case 0:
|
|
NET_DBG("Interface %p delete prefix %s/%d",
|
|
net_pkt_iface(pkt),
|
|
net_sprint_ipv6_addr(&prefix_info->prefix),
|
|
prefix_info->prefix_len);
|
|
|
|
net_if_ipv6_prefix_rm(net_pkt_iface(pkt),
|
|
&prefix->prefix,
|
|
prefix->len);
|
|
break;
|
|
|
|
case NET_IPV6_ND_INFINITE_LIFETIME:
|
|
NET_DBG("Interface %p prefix %s/%d infinite",
|
|
net_pkt_iface(pkt),
|
|
net_sprint_ipv6_addr(&prefix->prefix),
|
|
prefix->len);
|
|
|
|
net_if_ipv6_prefix_set_lf(prefix, true);
|
|
break;
|
|
|
|
default:
|
|
NET_DBG("Interface %p update prefix %s/%u lifetime %u",
|
|
net_pkt_iface(pkt),
|
|
net_sprint_ipv6_addr(&prefix_info->prefix),
|
|
prefix_info->prefix_len,
|
|
prefix_info->valid_lifetime);
|
|
|
|
net_if_ipv6_prefix_set_lf(prefix, false);
|
|
net_if_ipv6_prefix_set_timer(prefix,
|
|
prefix_info->valid_lifetime);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define TWO_HOURS (2 * 60 * 60)
|
|
|
|
static inline u32_t remaining_lifetime(struct net_if_addr *ifaddr)
|
|
{
|
|
s32_t remaining;
|
|
|
|
if (ifaddr->lifetime_timer_timeout == 0) {
|
|
return 0;
|
|
}
|
|
|
|
remaining = k_uptime_get() - ifaddr->lifetime_timer_start;
|
|
|
|
return abs(remaining) / K_MSEC(1000);
|
|
}
|
|
|
|
static inline void handle_prefix_autonomous(struct net_pkt *pkt,
|
|
struct net_icmpv6_nd_opt_prefix_info *prefix_info)
|
|
{
|
|
struct in6_addr addr = { };
|
|
struct net_if_addr *ifaddr;
|
|
|
|
/* Create IPv6 address using the given prefix and iid. We first
|
|
* setup link local address, and then copy prefix over first 8
|
|
* bytes of that address.
|
|
*/
|
|
net_ipv6_addr_create_iid(&addr,
|
|
net_if_get_link_addr(net_pkt_iface(pkt)));
|
|
memcpy(&addr, &prefix_info->prefix, sizeof(struct in6_addr) / 2);
|
|
|
|
ifaddr = net_if_ipv6_addr_lookup(&addr, NULL);
|
|
if (ifaddr && ifaddr->addr_type == NET_ADDR_AUTOCONF) {
|
|
if (prefix_info->valid_lifetime ==
|
|
NET_IPV6_ND_INFINITE_LIFETIME) {
|
|
net_if_addr_set_lf(ifaddr, true);
|
|
return;
|
|
}
|
|
|
|
/* RFC 4862 ch 5.5.3 */
|
|
if ((prefix_info->valid_lifetime > TWO_HOURS) ||
|
|
(prefix_info->valid_lifetime >
|
|
remaining_lifetime(ifaddr))) {
|
|
NET_DBG("Timer updating for address %s "
|
|
"long lifetime %u secs",
|
|
net_sprint_ipv6_addr(&addr),
|
|
prefix_info->valid_lifetime);
|
|
|
|
net_if_ipv6_addr_update_lifetime(ifaddr,
|
|
prefix_info->valid_lifetime);
|
|
} else {
|
|
NET_DBG("Timer updating for address %s "
|
|
"lifetime %u secs",
|
|
net_sprint_ipv6_addr(&addr), TWO_HOURS);
|
|
|
|
net_if_ipv6_addr_update_lifetime(ifaddr, TWO_HOURS);
|
|
}
|
|
|
|
net_if_addr_set_lf(ifaddr, false);
|
|
} else {
|
|
if (prefix_info->valid_lifetime ==
|
|
NET_IPV6_ND_INFINITE_LIFETIME) {
|
|
net_if_ipv6_addr_add(net_pkt_iface(pkt),
|
|
&addr, NET_ADDR_AUTOCONF, 0);
|
|
} else {
|
|
net_if_ipv6_addr_add(net_pkt_iface(pkt),
|
|
&addr, NET_ADDR_AUTOCONF,
|
|
prefix_info->valid_lifetime);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline struct net_buf *handle_ra_prefix(struct net_pkt *pkt,
|
|
struct net_buf *frag,
|
|
u8_t len,
|
|
u16_t offset, u16_t *pos)
|
|
{
|
|
struct net_icmpv6_nd_opt_prefix_info prefix_info;
|
|
|
|
prefix_info.type = NET_ICMPV6_ND_OPT_PREFIX_INFO;
|
|
prefix_info.len = len * 8 - 2;
|
|
|
|
frag = net_frag_read(frag, offset, pos, 1, &prefix_info.prefix_len);
|
|
frag = net_frag_read(frag, *pos, pos, 1, &prefix_info.flags);
|
|
frag = net_frag_read_be32(frag, *pos, pos, &prefix_info.valid_lifetime);
|
|
frag = net_frag_read_be32(frag, *pos, pos,
|
|
&prefix_info.preferred_lifetime);
|
|
/* Skip reserved bytes */
|
|
frag = net_frag_skip(frag, *pos, pos, 4);
|
|
frag = net_frag_read(frag, *pos, pos, sizeof(struct in6_addr),
|
|
prefix_info.prefix.s6_addr);
|
|
if (!frag && *pos) {
|
|
return NULL;
|
|
}
|
|
|
|
if (prefix_info.valid_lifetime >= prefix_info.preferred_lifetime &&
|
|
!net_is_ipv6_ll_addr(&prefix_info.prefix)) {
|
|
|
|
if (prefix_info.flags & NET_ICMPV6_RA_FLAG_ONLINK) {
|
|
handle_prefix_onlink(pkt, &prefix_info);
|
|
}
|
|
|
|
if ((prefix_info.flags & NET_ICMPV6_RA_FLAG_AUTONOMOUS) &&
|
|
prefix_info.valid_lifetime &&
|
|
(prefix_info.prefix_len == NET_IPV6_DEFAULT_PREFIX_LEN)) {
|
|
handle_prefix_autonomous(pkt, &prefix_info);
|
|
}
|
|
}
|
|
|
|
return frag;
|
|
}
|
|
|
|
#if defined(CONFIG_NET_6LO_CONTEXT)
|
|
/* 6lowpan Context Option RFC 6775, 4.2 */
|
|
static inline struct net_buf *handle_ra_6co(struct net_pkt *pkt,
|
|
struct net_buf *frag,
|
|
u8_t len,
|
|
u16_t offset, u16_t *pos)
|
|
{
|
|
struct net_icmpv6_nd_opt_6co context;
|
|
|
|
context.type = NET_ICMPV6_ND_OPT_6CO;
|
|
context.len = len * 8 - 2;
|
|
|
|
frag = net_frag_read_u8(frag, offset, pos, &context.context_len);
|
|
|
|
/* RFC 6775, 4.2
|
|
* Context Length: 8-bit unsigned integer. The number of leading
|
|
* bits in the Context Prefix field that are valid. The value ranges
|
|
* from 0 to 128. If it is more than 64, then the Length MUST be 3.
|
|
*/
|
|
if (context.context_len > 64 && len != 3) {
|
|
return NULL;
|
|
}
|
|
|
|
if (context.context_len <= 64 && len != 2) {
|
|
return NULL;
|
|
}
|
|
|
|
context.context_len = context.context_len / 8;
|
|
frag = net_frag_read_u8(frag, *pos, pos, &context.flag);
|
|
|
|
/* Skip reserved bytes */
|
|
frag = net_frag_skip(frag, *pos, pos, 2);
|
|
frag = net_frag_read_be16(frag, *pos, pos, &context.lifetime);
|
|
|
|
/* RFC 6775, 4.2 (Length field). Length can be 2 or 3 depending
|
|
* on the length of context prefix field.
|
|
*/
|
|
if (len == 3) {
|
|
frag = net_frag_read(frag, *pos, pos, sizeof(struct in6_addr),
|
|
context.prefix.s6_addr);
|
|
} else if (len == 2) {
|
|
/* If length is 2 means only 64 bits of context prefix
|
|
* is available, rest set to zeros.
|
|
*/
|
|
frag = net_frag_read(frag, *pos, pos, 8,
|
|
context.prefix.s6_addr);
|
|
}
|
|
|
|
if (!frag && *pos) {
|
|
return NULL;
|
|
}
|
|
|
|
/* context_len: The number of leading bits in the Context Prefix
|
|
* field that are valid. So set remaining data to zero.
|
|
*/
|
|
if (context.context_len != sizeof(struct in6_addr)) {
|
|
memset(context.prefix.s6_addr + context.context_len, 0,
|
|
sizeof(struct in6_addr) - context.context_len);
|
|
}
|
|
|
|
net_6lo_set_context(net_pkt_iface(pkt), &context);
|
|
|
|
return frag;
|
|
}
|
|
#endif
|
|
|
|
static enum net_verdict handle_ra_input(struct net_pkt *pkt)
|
|
{
|
|
u16_t total_len = net_pkt_get_len(pkt);
|
|
struct net_nbr *nbr = NULL;
|
|
struct net_icmpv6_ra_hdr ra_hdr;
|
|
struct net_if_router *router;
|
|
struct net_buf *frag;
|
|
u16_t router_lifetime;
|
|
u32_t reachable_time;
|
|
u32_t retrans_timer;
|
|
u8_t hop_limit;
|
|
u16_t offset;
|
|
u8_t length;
|
|
u8_t type;
|
|
u32_t mtu;
|
|
int ret;
|
|
|
|
dbg_addr_recv("Router Advertisement",
|
|
&NET_IPV6_HDR(pkt)->src,
|
|
&NET_IPV6_HDR(pkt)->dst);
|
|
|
|
net_stats_update_ipv6_nd_recv(net_pkt_iface(pkt));
|
|
|
|
if ((total_len < (sizeof(struct net_ipv6_hdr) +
|
|
sizeof(struct net_icmp_hdr) +
|
|
sizeof(struct net_icmpv6_ra_hdr) +
|
|
sizeof(struct net_icmpv6_nd_opt_hdr))) ||
|
|
(NET_IPV6_HDR(pkt)->hop_limit != NET_IPV6_ND_HOP_LIMIT) ||
|
|
!net_is_ipv6_ll_addr(&NET_IPV6_HDR(pkt)->src)) {
|
|
struct net_icmp_hdr icmp_hdr;
|
|
|
|
ret = net_icmpv6_get_hdr(pkt, &icmp_hdr);
|
|
if (ret < 0 || icmp_hdr.code != 0) {
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
frag = pkt->frags;
|
|
offset = sizeof(struct net_ipv6_hdr) + net_pkt_ipv6_ext_len(pkt) +
|
|
sizeof(struct net_icmp_hdr);
|
|
|
|
frag = net_frag_read_u8(frag, offset, &offset, &hop_limit);
|
|
frag = net_frag_skip(frag, offset, &offset, 1); /* flags */
|
|
if (!frag) {
|
|
goto drop;
|
|
}
|
|
|
|
if (hop_limit) {
|
|
net_ipv6_set_hop_limit(net_pkt_iface(pkt), hop_limit);
|
|
NET_DBG("New hop limit %d",
|
|
net_if_ipv6_get_hop_limit(net_pkt_iface(pkt)));
|
|
}
|
|
|
|
frag = net_frag_read_be16(frag, offset, &offset, &router_lifetime);
|
|
frag = net_frag_read_be32(frag, offset, &offset, &reachable_time);
|
|
frag = net_frag_read_be32(frag, offset, &offset, &retrans_timer);
|
|
if (!frag) {
|
|
goto drop;
|
|
}
|
|
|
|
ret = net_icmpv6_get_ra_hdr(pkt, &ra_hdr);
|
|
if (ret < 0) {
|
|
NET_ERR("could not get ra_hdr");
|
|
goto drop;
|
|
}
|
|
|
|
if (reachable_time && reachable_time <= MAX_REACHABLE_TIME &&
|
|
(net_if_ipv6_get_reachable_time(net_pkt_iface(pkt)) !=
|
|
ra_hdr.reachable_time)) {
|
|
net_if_ipv6_set_base_reachable_time(net_pkt_iface(pkt),
|
|
reachable_time);
|
|
|
|
net_if_ipv6_set_reachable_time(
|
|
net_pkt_iface(pkt)->config.ip.ipv6);
|
|
}
|
|
|
|
if (retrans_timer) {
|
|
net_if_ipv6_set_retrans_timer(net_pkt_iface(pkt),
|
|
retrans_timer);
|
|
}
|
|
|
|
while (frag) {
|
|
frag = net_frag_read(frag, offset, &offset, 1, &type);
|
|
frag = net_frag_read(frag, offset, &offset, 1, &length);
|
|
if (!frag) {
|
|
goto drop;
|
|
}
|
|
|
|
switch (type) {
|
|
case NET_ICMPV6_ND_OPT_SLLAO:
|
|
frag = handle_ra_neighbor(pkt, frag, length, offset,
|
|
&offset, &nbr);
|
|
if (!frag && offset) {
|
|
goto drop;
|
|
}
|
|
|
|
break;
|
|
case NET_ICMPV6_ND_OPT_MTU:
|
|
/* MTU has reserved 2 bytes, so skip it. */
|
|
frag = net_frag_skip(frag, offset, &offset, 2);
|
|
frag = net_frag_read_be32(frag, offset, &offset, &mtu);
|
|
if (!frag && offset) {
|
|
goto drop;
|
|
}
|
|
|
|
if (mtu < MIN_IPV6_MTU || mtu > MAX_IPV6_MTU) {
|
|
NET_ERR("Unsupported MTU %u, min is %u, "
|
|
"max is %u",
|
|
mtu, MIN_IPV6_MTU, MAX_IPV6_MTU);
|
|
goto drop;
|
|
}
|
|
|
|
net_if_set_mtu(net_pkt_iface(pkt), mtu);
|
|
|
|
break;
|
|
case NET_ICMPV6_ND_OPT_PREFIX_INFO:
|
|
frag = handle_ra_prefix(pkt, frag, length, offset,
|
|
&offset);
|
|
if (!frag && offset) {
|
|
goto drop;
|
|
}
|
|
|
|
break;
|
|
#if defined(CONFIG_NET_6LO_CONTEXT)
|
|
case NET_ICMPV6_ND_OPT_6CO:
|
|
/* RFC 6775, 4.2 (Length)*/
|
|
if (!(length == 2 || length == 3)) {
|
|
NET_ERR("Invalid 6CO length %d", length);
|
|
goto drop;
|
|
}
|
|
|
|
frag = handle_ra_6co(pkt, frag, length, offset,
|
|
&offset);
|
|
if (!frag && offset) {
|
|
goto drop;
|
|
}
|
|
|
|
break;
|
|
#endif
|
|
case NET_ICMPV6_ND_OPT_ROUTE:
|
|
NET_DBG("Route option (0x%x) skipped", type);
|
|
goto skip;
|
|
|
|
#if defined(CONFIG_NET_IPV6_RA_RDNSS)
|
|
case NET_ICMPV6_ND_OPT_RDNSS:
|
|
NET_DBG("RDNSS option (0x%x) skipped", type);
|
|
goto skip;
|
|
#endif
|
|
|
|
case NET_ICMPV6_ND_OPT_DNSSL:
|
|
NET_DBG("DNSSL option (0x%x) skipped", type);
|
|
goto skip;
|
|
|
|
default:
|
|
NET_DBG("Unknown ND option 0x%x", type);
|
|
skip:
|
|
frag = net_frag_skip(frag, offset, &offset,
|
|
length * 8 - 2);
|
|
if (!frag && offset) {
|
|
goto drop;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
router = net_if_ipv6_router_lookup(net_pkt_iface(pkt),
|
|
&NET_IPV6_HDR(pkt)->src);
|
|
if (router) {
|
|
if (!router_lifetime) {
|
|
/* TODO: Start rs_timer on iface if no routers
|
|
* at all available on iface.
|
|
*/
|
|
net_if_ipv6_router_rm(router);
|
|
} else {
|
|
if (nbr) {
|
|
net_ipv6_nbr_data(nbr)->is_router = true;
|
|
}
|
|
|
|
net_if_ipv6_router_update_lifetime(router,
|
|
router_lifetime);
|
|
}
|
|
} else {
|
|
net_if_ipv6_router_add(net_pkt_iface(pkt),
|
|
&NET_IPV6_HDR(pkt)->src,
|
|
router_lifetime);
|
|
}
|
|
|
|
if (nbr && net_ipv6_nbr_data(nbr)->pending) {
|
|
NET_DBG("Sending pending pkt %p to %s",
|
|
net_ipv6_nbr_data(nbr)->pending,
|
|
net_sprint_ipv6_addr(&NET_IPV6_HDR(
|
|
net_ipv6_nbr_data(nbr)->pending)->dst));
|
|
|
|
if (net_send_data(net_ipv6_nbr_data(nbr)->pending) < 0) {
|
|
net_pkt_unref(net_ipv6_nbr_data(nbr)->pending);
|
|
}
|
|
|
|
nbr_clear_ns_pending(net_ipv6_nbr_data(nbr));
|
|
}
|
|
|
|
/* Cancel the RS timer on iface */
|
|
k_delayed_work_cancel(&net_pkt_iface(pkt)->config.ip.ipv6->rs_timer);
|
|
|
|
net_pkt_unref(pkt);
|
|
|
|
return NET_OK;
|
|
|
|
drop:
|
|
net_stats_update_ipv6_nd_drop(net_pkt_iface(pkt));
|
|
|
|
return NET_DROP;
|
|
}
|
|
#endif /* CONFIG_NET_IPV6_ND */
|
|
|
|
#define append(pkt, type, value) \
|
|
do { \
|
|
if (!net_pkt_append_##type##_timeout(pkt, value, \
|
|
NET_BUF_TIMEOUT)) { \
|
|
ret = -ENOMEM; \
|
|
goto drop; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define append_all(pkt, size, value) \
|
|
do { \
|
|
if (!net_pkt_append_all(pkt, size, value, \
|
|
NET_BUF_TIMEOUT)) { \
|
|
ret = -ENOMEM; \
|
|
goto drop; \
|
|
} \
|
|
} while (0)
|
|
|
|
#if defined(CONFIG_NET_IPV6_MLD)
|
|
#define MLDv2_LEN (2 + 1 + 1 + 2 + sizeof(struct in6_addr) * 2)
|
|
|
|
static struct net_pkt *create_mldv2(struct net_pkt *pkt,
|
|
const struct in6_addr *addr,
|
|
u16_t record_type,
|
|
u8_t num_sources)
|
|
{
|
|
int ret;
|
|
|
|
append(pkt, u8, record_type);
|
|
append(pkt, u8, 0); /* aux data len */
|
|
append(pkt, be16, num_sources); /* number of addresses */
|
|
|
|
append_all(pkt, sizeof(struct in6_addr), addr->s6_addr);
|
|
|
|
if (num_sources > 0) {
|
|
/* All source addresses, RFC 3810 ch 3 */
|
|
append_all(pkt, sizeof(struct in6_addr),
|
|
net_ipv6_unspecified_address()->s6_addr);
|
|
}
|
|
|
|
return pkt;
|
|
|
|
drop:
|
|
return NULL;
|
|
}
|
|
|
|
static int send_mldv2_raw(struct net_if *iface, struct net_buf *frags)
|
|
{
|
|
struct net_pkt *pkt;
|
|
struct in6_addr dst;
|
|
u16_t pos;
|
|
int ret;
|
|
|
|
/* Sent to all MLDv2-capable routers */
|
|
net_ipv6_addr_create(&dst, 0xff02, 0, 0, 0, 0, 0, 0, 0x0016);
|
|
|
|
pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, &dst),
|
|
NET_BUF_TIMEOUT);
|
|
if (!pkt) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (!net_ipv6_create(pkt,
|
|
net_if_ipv6_select_src_addr(iface, &dst),
|
|
&dst,
|
|
iface,
|
|
NET_IPV6_NEXTHDR_HBHO)) {
|
|
ret = -ENOMEM;
|
|
goto drop;
|
|
}
|
|
|
|
NET_IPV6_HDR(pkt)->hop_limit = 1; /* RFC 3810 ch 7.4 */
|
|
|
|
net_pkt_set_ipv6_hdr_prev(pkt, pkt->frags->len);
|
|
|
|
/* Add hop-by-hop option and router alert option, RFC 3810 ch 5. */
|
|
append(pkt, u8, IPPROTO_ICMPV6);
|
|
append(pkt, u8, 0); /* length (0 means 8 bytes) */
|
|
|
|
/* IPv6 router alert option is described in RFC 2711. */
|
|
append(pkt, be16, 0x0502); /* RFC 2711 ch 2.1 */
|
|
append(pkt, be16, 0); /* pkt contains MLD msg */
|
|
append(pkt, u8, 0); /* padding */
|
|
append(pkt, u8, 0); /* padding */
|
|
|
|
/* ICMPv6 header */
|
|
append(pkt, u8, NET_ICMPV6_MLDv2); /* type */
|
|
append(pkt, u8, 0); /* code */
|
|
append(pkt, be16, 0); /* chksum */
|
|
append(pkt, be16, 0); /* reserved field */
|
|
|
|
#define ROUTER_ALERT_LEN 8
|
|
|
|
pkt->frags->len = NET_IPV6ICMPH_LEN + ROUTER_ALERT_LEN;
|
|
net_pkt_set_iface(pkt, iface);
|
|
|
|
/* Insert the actual multicast record(s) here */
|
|
net_pkt_frag_add(pkt, frags);
|
|
|
|
ret = net_ipv6_finalize(pkt, NET_IPV6_NEXTHDR_HBHO);
|
|
if (ret < 0) {
|
|
goto drop;
|
|
}
|
|
|
|
net_pkt_set_ipv6_ext_len(pkt, ROUTER_ALERT_LEN);
|
|
|
|
if (!net_pkt_write_be16_timeout(pkt, pkt->frags,
|
|
NET_IPV6H_LEN + ROUTER_ALERT_LEN + 2,
|
|
&pos,
|
|
ntohs(~net_calc_chksum_icmpv6(pkt)),
|
|
NET_BUF_TIMEOUT)) {
|
|
ret = -ENOMEM;
|
|
goto drop;
|
|
}
|
|
|
|
ret = net_send_data(pkt);
|
|
if (ret < 0) {
|
|
goto drop;
|
|
}
|
|
|
|
net_stats_update_icmp_sent(net_pkt_iface(pkt));
|
|
net_stats_update_ipv6_mld_sent(net_pkt_iface(pkt));
|
|
|
|
return 0;
|
|
|
|
drop:
|
|
net_stats_update_icmp_drop(net_pkt_iface(pkt));
|
|
net_stats_update_ipv6_mld_drop(net_pkt_iface(pkt));
|
|
|
|
net_pkt_unref(pkt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int send_mldv2(struct net_if *iface, const struct in6_addr *addr,
|
|
u8_t mode)
|
|
{
|
|
struct net_pkt *pkt;
|
|
int ret;
|
|
|
|
pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, NULL),
|
|
NET_BUF_TIMEOUT);
|
|
if (!pkt) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
append(pkt, be16, 1); /* number of records */
|
|
|
|
if (!create_mldv2(pkt, addr, mode, 1)) {
|
|
ret = -ENOMEM;
|
|
goto drop;
|
|
}
|
|
|
|
ret = send_mldv2_raw(iface, pkt->frags);
|
|
|
|
pkt->frags = NULL;
|
|
|
|
drop:
|
|
net_pkt_unref(pkt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int net_ipv6_mld_join(struct net_if *iface, const struct in6_addr *addr)
|
|
{
|
|
struct net_if_mcast_addr *maddr;
|
|
int ret;
|
|
|
|
maddr = net_if_ipv6_maddr_lookup(addr, &iface);
|
|
if (maddr && net_if_ipv6_maddr_is_joined(maddr)) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
if (!maddr) {
|
|
maddr = net_if_ipv6_maddr_add(iface, addr);
|
|
if (!maddr) {
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
ret = send_mldv2(iface, addr, NET_IPV6_MLDv2_MODE_IS_EXCLUDE);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
net_if_ipv6_maddr_join(maddr);
|
|
|
|
net_if_mcast_monitor(iface, addr, true);
|
|
|
|
net_mgmt_event_notify(NET_EVENT_IPV6_MCAST_JOIN, iface);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int net_ipv6_mld_leave(struct net_if *iface, const struct in6_addr *addr)
|
|
{
|
|
int ret;
|
|
|
|
if (!net_if_ipv6_maddr_rm(iface, addr)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = send_mldv2(iface, addr, NET_IPV6_MLDv2_MODE_IS_INCLUDE);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
net_if_mcast_monitor(iface, addr, false);
|
|
|
|
net_mgmt_event_notify(NET_EVENT_IPV6_MCAST_LEAVE, iface);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void send_mld_report(struct net_if *iface)
|
|
{
|
|
struct net_if_ipv6 *ipv6 = iface->config.ip.ipv6;
|
|
struct net_pkt *pkt;
|
|
int i, ret, count = 0;
|
|
|
|
NET_ASSERT(ipv6);
|
|
|
|
pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, NULL),
|
|
NET_BUF_TIMEOUT);
|
|
if (!pkt) {
|
|
return;
|
|
}
|
|
|
|
append(pkt, u8, 0); /* This will be the record count */
|
|
|
|
for (i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) {
|
|
if (!ipv6->mcast[i].is_used || !ipv6->mcast[i].is_joined) {
|
|
continue;
|
|
}
|
|
|
|
if (!create_mldv2(pkt, &ipv6->mcast[i].address.in6_addr,
|
|
NET_IPV6_MLDv2_MODE_IS_EXCLUDE, 0)) {
|
|
goto drop;
|
|
}
|
|
|
|
count++;
|
|
}
|
|
|
|
if (count > 0) {
|
|
u16_t pos;
|
|
|
|
/* Write back the record count */
|
|
if (!net_pkt_write_u8_timeout(pkt, pkt->frags, 0, &pos,
|
|
count, NET_BUF_TIMEOUT)) {
|
|
goto drop;
|
|
}
|
|
|
|
send_mldv2_raw(iface, pkt->frags);
|
|
|
|
pkt->frags = NULL;
|
|
}
|
|
|
|
drop:
|
|
net_pkt_unref(pkt);
|
|
}
|
|
|
|
static enum net_verdict handle_mld_query(struct net_pkt *pkt)
|
|
{
|
|
u16_t total_len = net_pkt_get_len(pkt);
|
|
struct in6_addr mcast;
|
|
u16_t max_rsp_code, num_src, pkt_len;
|
|
u16_t offset, pos;
|
|
struct net_buf *frag;
|
|
int ret;
|
|
|
|
dbg_addr_recv("Multicast Listener Query",
|
|
&NET_IPV6_HDR(pkt)->src,
|
|
&NET_IPV6_HDR(pkt)->dst);
|
|
|
|
net_stats_update_ipv6_mld_recv(net_pkt_iface(pkt));
|
|
|
|
/* offset tells now where the ICMPv6 header is starting */
|
|
frag = net_frag_get_pos(pkt,
|
|
net_pkt_ip_hdr_len(pkt) +
|
|
net_pkt_ipv6_ext_len(pkt) +
|
|
sizeof(struct net_icmp_hdr),
|
|
&offset);
|
|
|
|
frag = net_frag_read_be16(frag, offset, &pos, &max_rsp_code);
|
|
frag = net_frag_skip(frag, pos, &pos, 2); /* two reserved bytes */
|
|
frag = net_frag_read(frag, pos, &pos, sizeof(mcast), mcast.s6_addr);
|
|
frag = net_frag_skip(frag, pos, &pos, 2); /* skip S, QRV & QQIC */
|
|
frag = net_frag_read_be16(pkt->frags, pos, &pos, &num_src);
|
|
if (!frag && pos == 0xffff) {
|
|
goto drop;
|
|
}
|
|
|
|
pkt_len = sizeof(struct net_ipv6_hdr) + net_pkt_ipv6_ext_len(pkt) +
|
|
sizeof(struct net_icmp_hdr) + (2 + 2 + 16 + 2 + 2) +
|
|
sizeof(struct in6_addr) * num_src;
|
|
|
|
if ((total_len < pkt_len || pkt_len > NET_IPV6_MTU ||
|
|
(NET_IPV6_HDR(pkt)->hop_limit != 1))) {
|
|
struct net_icmp_hdr icmp_hdr;
|
|
|
|
ret = net_icmpv6_get_hdr(pkt, &icmp_hdr);
|
|
if (ret < 0 || icmp_hdr.code != 0) {
|
|
NET_DBG("Preliminary check failed %u/%u, code %u, "
|
|
"hop %u", total_len, pkt_len,
|
|
icmp_hdr.code, NET_IPV6_HDR(pkt)->hop_limit);
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
/* Currently we only support a unspecified address query. */
|
|
if (!net_ipv6_addr_cmp(&mcast, net_ipv6_unspecified_address())) {
|
|
NET_DBG("Only supporting unspecified address query (%s)",
|
|
net_sprint_ipv6_addr(&mcast));
|
|
goto drop;
|
|
}
|
|
|
|
send_mld_report(net_pkt_iface(pkt));
|
|
|
|
drop:
|
|
net_stats_update_ipv6_mld_drop(net_pkt_iface(pkt));
|
|
|
|
return NET_DROP;
|
|
}
|
|
|
|
static struct net_icmpv6_handler mld_query_input_handler = {
|
|
.type = NET_ICMPV6_MLD_QUERY,
|
|
.code = 0,
|
|
.handler = handle_mld_query,
|
|
};
|
|
#endif /* CONFIG_NET_IPV6_MLD */
|
|
|
|
#if defined(CONFIG_NET_IPV6_NBR_CACHE)
|
|
static struct net_icmpv6_handler ns_input_handler = {
|
|
.type = NET_ICMPV6_NS,
|
|
.code = 0,
|
|
.handler = handle_ns_input,
|
|
};
|
|
|
|
static struct net_icmpv6_handler na_input_handler = {
|
|
.type = NET_ICMPV6_NA,
|
|
.code = 0,
|
|
.handler = handle_na_input,
|
|
};
|
|
#endif /* CONFIG_NET_IPV6_NBR_CACHE */
|
|
|
|
#if defined(CONFIG_NET_IPV6_ND)
|
|
static struct net_icmpv6_handler ra_input_handler = {
|
|
.type = NET_ICMPV6_RA,
|
|
.code = 0,
|
|
.handler = handle_ra_input,
|
|
};
|
|
#endif /* CONFIG_NET_IPV6_ND */
|
|
|
|
#if defined(CONFIG_NET_IPV6_FRAGMENT)
|
|
#if defined(CONFIG_NET_IPV6_FRAGMENT_TIMEOUT)
|
|
#define IPV6_REASSEMBLY_TIMEOUT K_SECONDS(CONFIG_NET_IPV6_FRAGMENT_TIMEOUT)
|
|
#else
|
|
#define IPV6_REASSEMBLY_TIMEOUT K_SECONDS(5)
|
|
#endif /* CONFIG_NET_IPV6_FRAGMENT_TIMEOUT */
|
|
|
|
#define FRAG_BUF_WAIT K_MSEC(10) /* how long to max wait for a buffer */
|
|
|
|
static void reassembly_timeout(struct k_work *work);
|
|
static bool reassembly_init_done;
|
|
|
|
static struct net_ipv6_reassembly
|
|
reassembly[CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT];
|
|
|
|
static struct net_ipv6_reassembly *reassembly_get(u32_t id,
|
|
struct in6_addr *src,
|
|
struct in6_addr *dst)
|
|
{
|
|
int i, avail = -1;
|
|
|
|
for (i = 0; i < CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT; i++) {
|
|
|
|
if (k_delayed_work_remaining_get(&reassembly[i].timer) &&
|
|
reassembly[i].id == id &&
|
|
net_ipv6_addr_cmp(src, &reassembly[i].src) &&
|
|
net_ipv6_addr_cmp(dst, &reassembly[i].dst)) {
|
|
return &reassembly[i];
|
|
}
|
|
|
|
if (k_delayed_work_remaining_get(&reassembly[i].timer)) {
|
|
continue;
|
|
}
|
|
|
|
if (avail < 0) {
|
|
avail = i;
|
|
}
|
|
}
|
|
|
|
if (avail < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
k_delayed_work_submit(&reassembly[avail].timer,
|
|
IPV6_REASSEMBLY_TIMEOUT);
|
|
|
|
net_ipaddr_copy(&reassembly[avail].src, src);
|
|
net_ipaddr_copy(&reassembly[avail].dst, dst);
|
|
|
|
reassembly[avail].id = id;
|
|
|
|
return &reassembly[avail];
|
|
}
|
|
|
|
static bool reassembly_cancel(u32_t id,
|
|
struct in6_addr *src,
|
|
struct in6_addr *dst)
|
|
{
|
|
int i, j;
|
|
|
|
NET_DBG("Cancel 0x%x", id);
|
|
|
|
for (i = 0; i < CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT; i++) {
|
|
s32_t remaining;
|
|
|
|
if (reassembly[i].id != id ||
|
|
!net_ipv6_addr_cmp(src, &reassembly[i].src) ||
|
|
!net_ipv6_addr_cmp(dst, &reassembly[i].dst)) {
|
|
continue;
|
|
}
|
|
|
|
remaining = k_delayed_work_remaining_get(&reassembly[i].timer);
|
|
if (remaining) {
|
|
k_delayed_work_cancel(&reassembly[i].timer);
|
|
}
|
|
|
|
NET_DBG("IPv6 reassembly id 0x%x remaining %d ms",
|
|
reassembly[i].id, remaining);
|
|
|
|
reassembly[i].id = 0;
|
|
|
|
for (j = 0; j < NET_IPV6_FRAGMENTS_MAX_PKT; j++) {
|
|
if (!reassembly[i].pkt[j]) {
|
|
continue;
|
|
}
|
|
|
|
NET_DBG("[%d] IPv6 reassembly pkt %p %zd bytes data",
|
|
j, reassembly[i].pkt[j],
|
|
net_pkt_get_len(reassembly[i].pkt[j]));
|
|
|
|
net_pkt_unref(reassembly[i].pkt[j]);
|
|
reassembly[i].pkt[j] = NULL;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void reassembly_info(char *str, struct net_ipv6_reassembly *reass)
|
|
{
|
|
int i, len;
|
|
|
|
for (i = 0, len = 0; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) {
|
|
if (!reass->pkt[i]) {
|
|
continue;
|
|
}
|
|
|
|
len += net_pkt_get_len(reass->pkt[i]);
|
|
}
|
|
|
|
NET_DBG("%s id 0x%x src %s dst %s remain %d ms len %d", str, reass->id,
|
|
net_sprint_ipv6_addr(&reass->src),
|
|
net_sprint_ipv6_addr(&reass->dst),
|
|
k_delayed_work_remaining_get(&reass->timer), len);
|
|
}
|
|
|
|
static void reassembly_timeout(struct k_work *work)
|
|
{
|
|
struct net_ipv6_reassembly *reass =
|
|
CONTAINER_OF(work, struct net_ipv6_reassembly, timer);
|
|
|
|
reassembly_info("Reassembly cancelled", reass);
|
|
|
|
reassembly_cancel(reass->id, &reass->src, &reass->dst);
|
|
}
|
|
|
|
static void reassemble_packet(struct net_ipv6_reassembly *reass)
|
|
{
|
|
struct net_pkt *pkt;
|
|
struct net_buf *last;
|
|
struct net_buf *frag;
|
|
u8_t next_hdr;
|
|
int i, len, ret;
|
|
u16_t pos;
|
|
|
|
k_delayed_work_cancel(&reass->timer);
|
|
|
|
NET_ASSERT(reass->pkt[0]);
|
|
|
|
last = net_buf_frag_last(reass->pkt[0]->frags);
|
|
|
|
/* We start from 2nd packet which is then appended to
|
|
* the first one.
|
|
*/
|
|
for (i = 1; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) {
|
|
int removed_len;
|
|
int ret;
|
|
|
|
pkt = reass->pkt[i];
|
|
|
|
/* Get rid of IPv6 and fragment header which are at
|
|
* the beginning of the fragment.
|
|
*/
|
|
removed_len = net_pkt_ipv6_fragment_start(pkt) +
|
|
sizeof(struct net_ipv6_frag_hdr);
|
|
|
|
NET_DBG("Removing %d bytes from start of pkt %p",
|
|
removed_len, pkt->frags);
|
|
|
|
ret = net_pkt_pull(pkt, 0, removed_len);
|
|
if (ret) {
|
|
NET_ERR("Failed to pull headers");
|
|
NET_ASSERT(ret != 0);
|
|
}
|
|
|
|
/* Attach the data to previous pkt */
|
|
last->frags = pkt->frags;
|
|
last = net_buf_frag_last(pkt->frags);
|
|
|
|
pkt->frags = NULL;
|
|
reass->pkt[i] = NULL;
|
|
|
|
net_pkt_unref(pkt);
|
|
}
|
|
|
|
pkt = reass->pkt[0];
|
|
reass->pkt[0] = NULL;
|
|
|
|
/* Next we need to strip away the fragment header from the first packet
|
|
* and set the various pointers and values in packet.
|
|
*/
|
|
|
|
frag = net_frag_read_u8(pkt->frags, net_pkt_ipv6_fragment_start(pkt),
|
|
&pos, &next_hdr);
|
|
if (!frag && pos == 0xFFFF) {
|
|
NET_ERR("Failed to read next header");
|
|
NET_ASSERT(frag);
|
|
}
|
|
|
|
ret = net_pkt_pull(pkt, net_pkt_ipv6_fragment_start(pkt),
|
|
sizeof(struct net_ipv6_frag_hdr));
|
|
if (ret) {
|
|
NET_ERR("Failed to pull fragmentation header");
|
|
NET_ASSERT(ret);
|
|
}
|
|
|
|
/* This one updates the previous header's nexthdr value */
|
|
if (!net_pkt_write_u8_timeout(pkt, pkt->frags,
|
|
net_pkt_ipv6_hdr_prev(pkt),
|
|
&pos, next_hdr, NET_BUF_TIMEOUT)) {
|
|
net_pkt_unref(pkt);
|
|
return;
|
|
}
|
|
|
|
if (!net_pkt_compact(pkt)) {
|
|
NET_ERR("Cannot compact reassembly packet %p", pkt);
|
|
net_pkt_unref(pkt);
|
|
return;
|
|
}
|
|
|
|
/* Fix the total length of the IPv6 packet. */
|
|
len = net_pkt_ipv6_ext_len(pkt);
|
|
if (len > 0) {
|
|
NET_DBG("Old pkt %p IPv6 ext len is %d bytes", pkt, len);
|
|
net_pkt_set_ipv6_ext_len(pkt,
|
|
len - sizeof(struct net_ipv6_frag_hdr));
|
|
}
|
|
|
|
len = net_pkt_get_len(pkt) - sizeof(struct net_ipv6_hdr);
|
|
|
|
NET_IPV6_HDR(pkt)->len = htons(len);
|
|
|
|
NET_DBG("New pkt %p IPv6 len is %d bytes", pkt, len);
|
|
|
|
/* We need to use the queue when feeding the packet back into the
|
|
* IP stack as we might run out of stack if we call processing_data()
|
|
* directly. As the packet does not contain link layer header, we
|
|
* MUST NOT pass it to L2 so there will be a special check for that
|
|
* in process_data() when handling the packet.
|
|
*/
|
|
ret = net_recv_data(net_pkt_iface(pkt), pkt);
|
|
if (ret < 0) {
|
|
net_pkt_unref(pkt);
|
|
}
|
|
}
|
|
|
|
void net_ipv6_frag_foreach(net_ipv6_frag_cb_t cb, void *user_data)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; reassembly_init_done &&
|
|
i < CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT; i++) {
|
|
if (!k_delayed_work_remaining_get(&reassembly[i].timer)) {
|
|
continue;
|
|
}
|
|
|
|
cb(&reassembly[i], user_data);
|
|
}
|
|
}
|
|
|
|
/* Verify that we have all the fragments received and in correct order.
|
|
*/
|
|
static bool fragment_verify(struct net_ipv6_reassembly *reass)
|
|
{
|
|
u16_t offset;
|
|
int i, prev_len;
|
|
|
|
prev_len = net_pkt_get_len(reass->pkt[0]);
|
|
offset = net_pkt_ipv6_fragment_offset(reass->pkt[0]);
|
|
|
|
NET_DBG("pkt %p offset %u", reass->pkt[0], offset);
|
|
|
|
if (offset != 0) {
|
|
return false;
|
|
}
|
|
|
|
for (i = 1; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) {
|
|
offset = net_pkt_ipv6_fragment_offset(reass->pkt[i]);
|
|
|
|
NET_DBG("pkt %p offset %u prev_len %d", reass->pkt[i],
|
|
offset, prev_len);
|
|
|
|
if (prev_len < offset) {
|
|
/* Something wrong with the offset value */
|
|
return false;
|
|
}
|
|
|
|
prev_len = net_pkt_get_len(reass->pkt[i]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int shift_packets(struct net_ipv6_reassembly *reass, int pos)
|
|
{
|
|
int i;
|
|
|
|
for (i = pos + 1; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) {
|
|
if (!reass->pkt[i]) {
|
|
NET_DBG("Moving [%d] %p (offset 0x%x) to [%d]",
|
|
pos, reass->pkt[pos],
|
|
net_pkt_ipv6_fragment_offset(reass->pkt[pos]),
|
|
i);
|
|
|
|
/* Do we have enough space in packet array to make
|
|
* the move?
|
|
*/
|
|
if (((i - pos) + 1) >
|
|
(NET_IPV6_FRAGMENTS_MAX_PKT - i)) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
memmove(&reass->pkt[i], &reass->pkt[pos],
|
|
sizeof(void *) * (i - pos));
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static enum net_verdict handle_fragment_hdr(struct net_pkt *pkt,
|
|
struct net_buf *frag,
|
|
int total_len,
|
|
u16_t buf_offset,
|
|
u16_t *loc,
|
|
u8_t nexthdr)
|
|
{
|
|
struct net_ipv6_reassembly *reass = NULL;
|
|
u32_t id;
|
|
u16_t offset;
|
|
u16_t flag;
|
|
u8_t more;
|
|
bool found;
|
|
int i;
|
|
|
|
if (!reassembly_init_done) {
|
|
/* Static initializing does not work here because of the array
|
|
* so we must do it at runtime.
|
|
*/
|
|
for (i = 0; i < CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT; i++) {
|
|
k_delayed_work_init(&reassembly[i].timer,
|
|
reassembly_timeout);
|
|
}
|
|
|
|
reassembly_init_done = true;
|
|
}
|
|
|
|
/* Each fragment has a fragment header. */
|
|
frag = net_frag_skip(frag, buf_offset, loc, 1); /* reserved */
|
|
frag = net_frag_read_be16(frag, *loc, loc, &flag);
|
|
frag = net_frag_read_be32(frag, *loc, loc, &id);
|
|
if (!frag && *loc == 0xffff) {
|
|
goto drop;
|
|
}
|
|
|
|
reass = reassembly_get(id, &NET_IPV6_HDR(pkt)->src,
|
|
&NET_IPV6_HDR(pkt)->dst);
|
|
if (!reass) {
|
|
NET_DBG("Cannot get reassembly slot, dropping pkt %p", pkt);
|
|
goto drop;
|
|
}
|
|
|
|
offset = flag & 0xfff8;
|
|
more = flag & 0x01;
|
|
|
|
net_pkt_set_ipv6_fragment_offset(pkt, offset);
|
|
|
|
if (!reass->pkt[0]) {
|
|
NET_DBG("Storing pkt %p to slot %d offset 0x%x", pkt, 0,
|
|
offset);
|
|
reass->pkt[0] = pkt;
|
|
|
|
reassembly_info("Reassembly 1st pkt", reass);
|
|
|
|
/* Wait for more fragments to receive. */
|
|
goto accept;
|
|
}
|
|
|
|
/* The fragments might come in wrong order so place them
|
|
* in reassembly chain in correct order.
|
|
*/
|
|
for (i = 0, found = false; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) {
|
|
if (!reass->pkt[i]) {
|
|
NET_DBG("Storing pkt %p to slot %d offset 0x%x", pkt,
|
|
i, offset);
|
|
reass->pkt[i] = pkt;
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
if (net_pkt_ipv6_fragment_offset(reass->pkt[i]) < offset) {
|
|
continue;
|
|
}
|
|
|
|
/* Make room for this fragment, if there is no room, then
|
|
* discard the whole reassembly.
|
|
*/
|
|
if (shift_packets(reass, i)) {
|
|
break;
|
|
}
|
|
|
|
NET_DBG("Storing %p (offset 0x%x) to [%d]", pkt, offset, i);
|
|
reass->pkt[i] = pkt;
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
if (!found) {
|
|
/* We could not add this fragment into our saved fragment
|
|
* list. We must discard the whole packet at this point.
|
|
*/
|
|
NET_DBG("No slots available for 0x%x", reass->id);
|
|
net_pkt_unref(pkt);
|
|
goto drop;
|
|
}
|
|
|
|
if (more) {
|
|
if (net_pkt_get_len(pkt) % 8) {
|
|
/* Fragment length is not multiple of 8, discard
|
|
* the packet and send parameter problem error.
|
|
*/
|
|
net_icmpv6_send_error(pkt, NET_ICMPV6_PARAM_PROBLEM,
|
|
NET_ICMPV6_PARAM_PROB_OPTION, 0);
|
|
goto drop;
|
|
}
|
|
|
|
reassembly_info("Reassembly nth pkt", reass);
|
|
|
|
NET_DBG("More fragments to be received");
|
|
goto accept;
|
|
}
|
|
|
|
reassembly_info("Reassembly last pkt", reass);
|
|
|
|
if (!fragment_verify(reass)) {
|
|
NET_DBG("Reassembled IPv6 verify failed, dropping id %u",
|
|
reass->id);
|
|
|
|
/* Let the caller release the already inserted pkt */
|
|
if (i < NET_IPV6_FRAGMENTS_MAX_PKT) {
|
|
reass->pkt[i] = NULL;
|
|
}
|
|
|
|
net_pkt_unref(pkt);
|
|
goto drop;
|
|
}
|
|
|
|
/* The last fragment received, reassemble the packet */
|
|
reassemble_packet(reass);
|
|
|
|
accept:
|
|
return NET_OK;
|
|
|
|
drop:
|
|
if (reass) {
|
|
if (reassembly_cancel(reass->id, &reass->src, &reass->dst)) {
|
|
return NET_OK;
|
|
}
|
|
}
|
|
|
|
return NET_DROP;
|
|
}
|
|
|
|
#define BUF_ALLOC_TIMEOUT K_MSEC(100)
|
|
|
|
static int send_ipv6_fragment(struct net_if *iface,
|
|
struct net_pkt *pkt,
|
|
struct net_buf **rest,
|
|
u16_t ipv6_hdrs_len,
|
|
u16_t fit_len,
|
|
u16_t frag_offset,
|
|
u8_t next_hdr,
|
|
u16_t next_hdr_idx,
|
|
u8_t last_hdr,
|
|
u16_t last_hdr_idx,
|
|
u16_t frag_count)
|
|
{
|
|
struct net_pkt *ipv6 = NULL;
|
|
bool final;
|
|
struct net_ipv6_frag_hdr hdr;
|
|
struct net_buf *frag;
|
|
struct net_buf *temp;
|
|
u16_t pos;
|
|
bool res;
|
|
int ret;
|
|
|
|
ipv6 = net_pkt_clone(pkt, BUF_ALLOC_TIMEOUT);
|
|
if (!ipv6) {
|
|
NET_DBG("Cannot clone %p", ipv6);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* And we need to update the last header in the IPv6 packet to point to
|
|
* fragment header.
|
|
*/
|
|
temp = net_pkt_write_u8_timeout(ipv6, ipv6->frags, next_hdr_idx, &pos,
|
|
NET_IPV6_NEXTHDR_FRAG,
|
|
BUF_ALLOC_TIMEOUT);
|
|
if (!temp) {
|
|
if (pos == 0xffff) {
|
|
ret = -EINVAL;
|
|
} else {
|
|
ret = -ENOMEM;
|
|
}
|
|
|
|
goto fail;
|
|
}
|
|
|
|
/* Update the extension length metadata so that upper layer checksum
|
|
* will be calculated properly by net_ipv6_finalize().
|
|
*/
|
|
net_pkt_set_ipv6_ext_len(ipv6,
|
|
net_pkt_ipv6_ext_len(pkt) +
|
|
sizeof(struct net_ipv6_frag_hdr));
|
|
|
|
frag = *rest;
|
|
if (fit_len < net_buf_frags_len(*rest)) {
|
|
ret = net_pkt_split(pkt, frag, fit_len, rest, FRAG_BUF_WAIT);
|
|
if (ret < 0) {
|
|
net_buf_unref(frag);
|
|
goto fail;
|
|
}
|
|
} else {
|
|
*rest = NULL;
|
|
}
|
|
|
|
final = false;
|
|
/* *rest == NULL means no more data to send */
|
|
if (!*rest) {
|
|
final = true;
|
|
}
|
|
|
|
/* Append the Fragmentation Header */
|
|
hdr.nexthdr = next_hdr;
|
|
hdr.reserved = 0;
|
|
hdr.id = net_pkt_ipv6_fragment_id(pkt);
|
|
hdr.offset = htons(((frag_offset / 8) << 3) | !final);
|
|
|
|
res = net_pkt_append_all(ipv6, sizeof(struct net_ipv6_frag_hdr),
|
|
(u8_t *)&hdr, FRAG_BUF_WAIT);
|
|
if (!res) {
|
|
net_buf_unref(frag);
|
|
ret = EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
/* Attach the first part of split payload to end of the packet. And
|
|
* "rest" of the packet will be sent in next iteration.
|
|
*/
|
|
temp = ipv6->frags;
|
|
while (1) {
|
|
if (!temp->frags) {
|
|
temp->frags = frag;
|
|
break;
|
|
}
|
|
|
|
temp = temp->frags;
|
|
}
|
|
|
|
res = net_pkt_compact(ipv6);
|
|
if (!res) {
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
/* Note that we must not calculate possible UDP/TCP/ICMPv6 checksum
|
|
* as that is already calculated in the non-fragmented packet.
|
|
*/
|
|
ret = net_ipv6_finalize(ipv6, NET_IPV6_NEXTHDR_FRAG);
|
|
if (ret < 0) {
|
|
NET_DBG("Cannot create IPv6 packet (%d)", ret);
|
|
goto fail;
|
|
}
|
|
|
|
/* If everything has been ok so far, we can send the packet.
|
|
* Note that we cannot send this re-constructed packet directly
|
|
* as the link layer headers will not be properly set (because
|
|
* we recreated the packet). So pass this packet back to TX
|
|
* so that the pkt is going back to L2 for setup.
|
|
*/
|
|
ret = net_send_data(ipv6);
|
|
if (ret < 0) {
|
|
NET_DBG("Cannot send fragment (%d)", ret);
|
|
goto fail;
|
|
}
|
|
|
|
/* Let this packet to be sent and hopefully it will release
|
|
* the memory that can be utilized for next sent IPv6 fragment.
|
|
*/
|
|
k_yield();
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
if (ipv6) {
|
|
net_pkt_unref(ipv6);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int net_ipv6_send_fragmented_pkt(struct net_if *iface, struct net_pkt *pkt,
|
|
u16_t pkt_len)
|
|
{
|
|
struct net_pkt *clone;
|
|
struct net_buf *rest;
|
|
struct net_buf *temp;
|
|
u16_t next_hdr_idx;
|
|
u16_t last_hdr_idx;
|
|
u16_t ipv6_hdrs_len;
|
|
u16_t frag_offset;
|
|
u16_t frag_count;
|
|
u16_t pos;
|
|
u8_t next_hdr;
|
|
u8_t last_hdr;
|
|
int fit_len;
|
|
int ret = -EINVAL;
|
|
|
|
/* We cannot touch original pkt because it might be used for
|
|
* some other purposes, like TCP resend etc. So we need to copy
|
|
* the large pkt here and do the fragmenting with the clone.
|
|
*/
|
|
clone = net_pkt_clone(pkt, BUF_ALLOC_TIMEOUT);
|
|
if (!clone) {
|
|
NET_DBG("Cannot clone %p", pkt);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pkt = clone;
|
|
net_pkt_set_ipv6_fragment_id(pkt, sys_rand32_get());
|
|
|
|
ret = net_ipv6_find_last_ext_hdr(pkt, &next_hdr_idx, &last_hdr_idx);
|
|
if (ret < 0) {
|
|
goto fail;
|
|
}
|
|
|
|
temp = net_frag_read_u8(pkt->frags, next_hdr_idx, &pos, &next_hdr);
|
|
if (!temp && pos == 0xffff) {
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
temp = net_frag_read_u8(pkt->frags, last_hdr_idx, &pos, &last_hdr);
|
|
if (!temp && pos == 0xffff) {
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
ipv6_hdrs_len = net_pkt_ip_hdr_len(pkt) + net_pkt_ipv6_ext_len(pkt);
|
|
|
|
ret = net_pkt_split(pkt, pkt->frags, ipv6_hdrs_len, &rest,
|
|
FRAG_BUF_WAIT);
|
|
if (ret < 0 || ipv6_hdrs_len != net_pkt_get_len(pkt)) {
|
|
NET_DBG("Cannot split packet (%d)", ret);
|
|
goto fail;
|
|
}
|
|
|
|
frag_count = 0;
|
|
frag_offset = 0;
|
|
|
|
/* The Maximum payload can fit into each packet after IPv6 header,
|
|
* Extenstion headers and Fragmentation header.
|
|
*/
|
|
fit_len = NET_IPV6_MTU - NET_IPV6_FRAGH_LEN - ipv6_hdrs_len;
|
|
if (fit_len <= 0) {
|
|
/* Must be invalid extension headers length */
|
|
NET_DBG("No room for IPv6 payload MTU %d hdrs_len %d",
|
|
NET_IPV6_MTU, NET_IPV6_FRAGH_LEN + ipv6_hdrs_len);
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
while (rest) {
|
|
ret = send_ipv6_fragment(iface, pkt, &rest, ipv6_hdrs_len,
|
|
fit_len, frag_offset, next_hdr,
|
|
next_hdr_idx, last_hdr, last_hdr_idx,
|
|
frag_count);
|
|
if (ret < 0) {
|
|
goto fail;
|
|
}
|
|
|
|
frag_count++;
|
|
frag_offset += fit_len;
|
|
}
|
|
|
|
net_pkt_unref(pkt);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
net_pkt_unref(pkt);
|
|
|
|
if (rest) {
|
|
net_buf_unref(rest);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_NET_IPV6_FRAGMENT */
|
|
|
|
static inline enum net_verdict process_icmpv6_pkt(struct net_pkt *pkt,
|
|
struct net_ipv6_hdr *ipv6)
|
|
{
|
|
struct net_icmp_hdr icmp_hdr;
|
|
int ret;
|
|
|
|
ret = net_icmpv6_get_hdr(pkt, &icmp_hdr);
|
|
if (ret < 0) {
|
|
NET_DBG("NULL ICMPv6 header - dropping");
|
|
return NET_DROP;
|
|
}
|
|
|
|
NET_DBG("ICMPv6 %s received type %d code %d",
|
|
net_icmpv6_type2str(icmp_hdr.type), icmp_hdr.type,
|
|
icmp_hdr.code);
|
|
|
|
return net_icmpv6_input(pkt, icmp_hdr.type, icmp_hdr.code);
|
|
}
|
|
|
|
static inline struct net_pkt *check_unknown_option(struct net_pkt *pkt,
|
|
u8_t opt_type,
|
|
u16_t length)
|
|
{
|
|
/* RFC 2460 chapter 4.2 tells how to handle the unknown
|
|
* options by the two highest order bits of the option:
|
|
*
|
|
* 00: Skip over this option and continue processing the header.
|
|
* 01: Discard the packet.
|
|
* 10: Discard the packet and, regardless of whether or not the
|
|
* packet's Destination Address was a multicast address,
|
|
* send an ICMP Parameter Problem, Code 2, message to the packet's
|
|
* Source Address, pointing to the unrecognized Option Type.
|
|
* 11: Discard the packet and, only if the packet's Destination
|
|
* Address was not a multicast address, send an ICMP Parameter
|
|
* Problem, Code 2, message to the packet's Source Address,
|
|
* pointing to the unrecognized Option Type.
|
|
*/
|
|
NET_DBG("Unknown option %d (0x%02x) MSB %d", opt_type, opt_type,
|
|
opt_type >> 6);
|
|
|
|
switch (opt_type & 0xc0) {
|
|
case 0x00:
|
|
break;
|
|
case 0x40:
|
|
return NULL;
|
|
case 0xc0:
|
|
if (net_is_ipv6_addr_mcast(&NET_IPV6_HDR(pkt)->dst)) {
|
|
return NULL;
|
|
}
|
|
/* passthrough */
|
|
case 0x80:
|
|
net_icmpv6_send_error(pkt, NET_ICMPV6_PARAM_PROBLEM,
|
|
NET_ICMPV6_PARAM_PROB_OPTION,
|
|
(u32_t)length);
|
|
return NULL;
|
|
}
|
|
|
|
return pkt;
|
|
}
|
|
|
|
static inline struct net_buf *handle_ext_hdr_options(struct net_pkt *pkt,
|
|
struct net_buf *frag,
|
|
int total_len,
|
|
u16_t len,
|
|
u16_t offset,
|
|
u16_t *pos,
|
|
enum net_verdict *verdict)
|
|
{
|
|
u8_t opt_type, opt_len;
|
|
u16_t length = 0, loc;
|
|
#if defined(CONFIG_NET_RPL)
|
|
bool result;
|
|
#endif
|
|
|
|
if (len > total_len) {
|
|
NET_DBG("Corrupted packet, extension header %d too long "
|
|
"(max %d bytes)", len, total_len);
|
|
*verdict = NET_DROP;
|
|
return NULL;
|
|
}
|
|
|
|
length += 2;
|
|
|
|
/* Each extension option has type and length */
|
|
frag = net_frag_read_u8(frag, offset, &loc, &opt_type);
|
|
if (!frag && loc == 0xffff) {
|
|
goto drop;
|
|
}
|
|
|
|
if (opt_type != NET_IPV6_EXT_HDR_OPT_PAD1) {
|
|
frag = net_frag_read_u8(frag, loc, &loc, &opt_len);
|
|
if (!frag && loc == 0xffff) {
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
while (frag && (length < len)) {
|
|
switch (opt_type) {
|
|
case NET_IPV6_EXT_HDR_OPT_PAD1:
|
|
length++;
|
|
break;
|
|
case NET_IPV6_EXT_HDR_OPT_PADN:
|
|
NET_DBG("PADN option");
|
|
length += opt_len + 2;
|
|
loc += opt_len + 2;
|
|
break;
|
|
#if defined(CONFIG_NET_RPL)
|
|
case NET_IPV6_EXT_HDR_OPT_RPL:
|
|
NET_DBG("Processing RPL option");
|
|
frag = net_rpl_verify_header(pkt, frag, loc, &loc,
|
|
&result);
|
|
if (!result) {
|
|
NET_DBG("RPL option error, packet dropped");
|
|
goto drop;
|
|
}
|
|
|
|
if (!frag && *pos == 0xffff) {
|
|
goto drop;
|
|
}
|
|
|
|
*verdict = NET_CONTINUE;
|
|
return frag;
|
|
#endif
|
|
default:
|
|
if (!check_unknown_option(pkt, opt_type, length)) {
|
|
goto drop;
|
|
}
|
|
|
|
length += opt_len + 2;
|
|
|
|
/* No need to +2 here as loc already contains option
|
|
* header len.
|
|
*/
|
|
loc += opt_len;
|
|
|
|
break;
|
|
}
|
|
|
|
if (length >= len) {
|
|
break;
|
|
}
|
|
|
|
frag = net_frag_read_u8(frag, loc, &loc, &opt_type);
|
|
if (!frag && loc == 0xffff) {
|
|
goto drop;
|
|
}
|
|
|
|
if (opt_type != NET_IPV6_EXT_HDR_OPT_PAD1) {
|
|
frag = net_frag_read_u8(frag, loc, &loc, &opt_len);
|
|
if (!frag && loc == 0xffff) {
|
|
goto drop;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (length != len) {
|
|
goto drop;
|
|
}
|
|
|
|
*pos = loc;
|
|
|
|
*verdict = NET_CONTINUE;
|
|
return frag;
|
|
|
|
drop:
|
|
*verdict = NET_DROP;
|
|
return NULL;
|
|
}
|
|
|
|
static inline bool is_upper_layer_protocol_header(u8_t proto)
|
|
{
|
|
return (proto == IPPROTO_ICMPV6 || proto == IPPROTO_UDP ||
|
|
proto == IPPROTO_TCP);
|
|
}
|
|
|
|
#if defined(CONFIG_NET_ROUTE)
|
|
static struct net_route_entry *add_route(struct net_if *iface,
|
|
struct in6_addr *addr,
|
|
u8_t prefix_len)
|
|
{
|
|
struct net_route_entry *route;
|
|
|
|
route = net_route_lookup(iface, addr);
|
|
if (route) {
|
|
return route;
|
|
}
|
|
|
|
route = net_route_add(iface, addr, prefix_len, addr);
|
|
|
|
NET_DBG("%s route to %s/%d iface %p", route ? "Add" : "Cannot add",
|
|
net_sprint_ipv6_addr(addr), prefix_len, iface);
|
|
|
|
return route;
|
|
}
|
|
#endif /* CONFIG_NET_ROUTE */
|
|
|
|
static void no_route_info(struct net_pkt *pkt,
|
|
struct in6_addr *src,
|
|
struct in6_addr *dst)
|
|
{
|
|
NET_DBG("Will not route pkt %p ll src %s to dst %s between interfaces",
|
|
pkt, net_sprint_ipv6_addr(src), net_sprint_ipv6_addr(dst));
|
|
}
|
|
|
|
#if defined(CONFIG_NET_ROUTE)
|
|
static enum net_verdict route_ipv6_packet(struct net_pkt *pkt,
|
|
struct net_ipv6_hdr *hdr)
|
|
{
|
|
struct net_route_entry *route;
|
|
struct in6_addr *nexthop;
|
|
bool found;
|
|
|
|
/* Check if the packet can be routed */
|
|
if (IS_ENABLED(CONFIG_NET_ROUTING)) {
|
|
found = net_route_get_info(NULL, &hdr->dst, &route,
|
|
&nexthop);
|
|
} else {
|
|
found = net_route_get_info(net_pkt_iface(pkt),
|
|
&hdr->dst, &route, &nexthop);
|
|
}
|
|
|
|
if (found) {
|
|
int ret;
|
|
|
|
if (IS_ENABLED(CONFIG_NET_ROUTING) &&
|
|
(net_is_ipv6_ll_addr(&hdr->src) ||
|
|
net_is_ipv6_ll_addr(&hdr->dst))) {
|
|
/* RFC 4291 ch 2.5.6 */
|
|
no_route_info(pkt, &hdr->src, &hdr->dst);
|
|
goto drop;
|
|
}
|
|
|
|
/* Used when detecting if the original link
|
|
* layer address length is changed or not.
|
|
*/
|
|
net_pkt_set_orig_iface(pkt, net_pkt_iface(pkt));
|
|
|
|
if (route) {
|
|
net_pkt_set_iface(pkt, route->iface);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_NET_ROUTING) &&
|
|
net_pkt_orig_iface(pkt) != net_pkt_iface(pkt)) {
|
|
/* If the route interface to destination is
|
|
* different than the original route, then add
|
|
* route to original source.
|
|
*/
|
|
NET_DBG("Route pkt %p from %p to %p",
|
|
pkt, net_pkt_orig_iface(pkt),
|
|
net_pkt_iface(pkt));
|
|
|
|
add_route(net_pkt_orig_iface(pkt),
|
|
&NET_IPV6_HDR(pkt)->src, 128);
|
|
}
|
|
|
|
ret = net_route_packet(pkt, nexthop);
|
|
if (ret < 0) {
|
|
NET_DBG("Cannot re-route pkt %p via %s "
|
|
"at iface %p (%d)",
|
|
pkt, net_sprint_ipv6_addr(nexthop),
|
|
net_pkt_iface(pkt), ret);
|
|
} else {
|
|
return NET_OK;
|
|
}
|
|
} else {
|
|
NET_DBG("No route to %s pkt %p dropped",
|
|
net_sprint_ipv6_addr(&hdr->dst), pkt);
|
|
}
|
|
|
|
drop:
|
|
return NET_DROP;
|
|
}
|
|
#endif /* CONFIG_NET_ROUTE */
|
|
|
|
enum net_verdict net_ipv6_process_pkt(struct net_pkt *pkt)
|
|
{
|
|
struct net_ipv6_hdr *hdr = NET_IPV6_HDR(pkt);
|
|
int real_len = net_pkt_get_len(pkt);
|
|
int pkt_len = ntohs(hdr->len) + sizeof(*hdr);
|
|
struct net_buf *frag;
|
|
u8_t start_of_ext, prev_hdr;
|
|
u8_t next, next_hdr;
|
|
u8_t first_option;
|
|
u16_t offset;
|
|
u16_t length;
|
|
u16_t total_len = 0;
|
|
u8_t ext_bitmap;
|
|
|
|
if (real_len != pkt_len) {
|
|
NET_DBG("IPv6 packet size %d pkt len %d", pkt_len, real_len);
|
|
net_stats_update_ipv6_drop(net_pkt_iface(pkt));
|
|
goto drop;
|
|
}
|
|
|
|
NET_DBG("IPv6 packet len %d received from %s to %s", real_len,
|
|
net_sprint_ipv6_addr(&hdr->src),
|
|
net_sprint_ipv6_addr(&hdr->dst));
|
|
|
|
if (net_is_ipv6_addr_mcast(&hdr->src)) {
|
|
NET_DBG("Dropping src multicast packet");
|
|
net_stats_update_ipv6_drop(net_pkt_iface(pkt));
|
|
goto drop;
|
|
}
|
|
|
|
if (!net_is_my_ipv6_addr(&hdr->dst) &&
|
|
!net_is_my_ipv6_maddr(&hdr->dst) &&
|
|
!net_is_ipv6_addr_mcast(&hdr->dst) &&
|
|
!net_is_ipv6_addr_loopback(&hdr->dst)) {
|
|
#if defined(CONFIG_NET_ROUTE)
|
|
enum net_verdict verdict;
|
|
|
|
verdict = route_ipv6_packet(pkt, hdr);
|
|
if (verdict == NET_OK) {
|
|
return NET_OK;
|
|
}
|
|
#else /* CONFIG_NET_ROUTE */
|
|
NET_DBG("IPv6 packet in pkt %p not for me", pkt);
|
|
#endif /* CONFIG_NET_ROUTE */
|
|
|
|
net_stats_update_ipv6_drop(net_pkt_iface(pkt));
|
|
goto drop;
|
|
}
|
|
|
|
/* If we receive a packet with ll source address fe80: and destination
|
|
* address is one of ours, and if the packet would cross interface
|
|
* boundary, then drop the packet. RFC 4291 ch 2.5.6
|
|
*/
|
|
if (IS_ENABLED(CONFIG_NET_ROUTING) &&
|
|
net_is_ipv6_ll_addr(&hdr->src) &&
|
|
!net_is_ipv6_addr_mcast(&hdr->dst) &&
|
|
!net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt),
|
|
&hdr->dst)) {
|
|
no_route_info(pkt, &hdr->src, &hdr->dst);
|
|
|
|
net_stats_update_ipv6_drop(net_pkt_iface(pkt));
|
|
goto drop;
|
|
}
|
|
|
|
/* Check extension headers */
|
|
net_pkt_set_next_hdr(pkt, &hdr->nexthdr);
|
|
net_pkt_set_ipv6_ext_len(pkt, 0);
|
|
net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr));
|
|
net_pkt_set_ipv6_hop_limit(pkt, NET_IPV6_HDR(pkt)->hop_limit);
|
|
|
|
/* Fast path for main upper layer protocols. The handling of extension
|
|
* headers can be slow so do this checking here. There cannot
|
|
* be any extension headers after the upper layer protocol header.
|
|
*/
|
|
next = *(net_pkt_next_hdr(pkt));
|
|
if (is_upper_layer_protocol_header(next)) {
|
|
goto upper_proto;
|
|
}
|
|
|
|
/* Go through the extensions */
|
|
frag = pkt->frags;
|
|
next = hdr->nexthdr;
|
|
first_option = next;
|
|
length = 0;
|
|
ext_bitmap = 0;
|
|
start_of_ext = 0;
|
|
offset = sizeof(struct net_ipv6_hdr);
|
|
prev_hdr = &NET_IPV6_HDR(pkt)->nexthdr - &NET_IPV6_HDR(pkt)->vtc;
|
|
|
|
while (frag) {
|
|
enum net_verdict verdict;
|
|
|
|
if (is_upper_layer_protocol_header(next)) {
|
|
NET_DBG("IPv6 next header %d", next);
|
|
goto upper_proto;
|
|
}
|
|
|
|
if (!start_of_ext) {
|
|
start_of_ext = offset;
|
|
}
|
|
|
|
frag = net_frag_read_u8(frag, offset, &offset, &next_hdr);
|
|
if (!frag) {
|
|
goto drop;
|
|
}
|
|
|
|
verdict = NET_OK;
|
|
|
|
NET_DBG("IPv6 next header %d", next);
|
|
|
|
switch (next) {
|
|
case NET_IPV6_NEXTHDR_NONE:
|
|
/* There is nothing after this header (see RFC 2460,
|
|
* ch 4.7), so we can drop the packet now.
|
|
* This is not an error case so do not update drop
|
|
* statistics.
|
|
*/
|
|
goto drop;
|
|
|
|
case NET_IPV6_NEXTHDR_HBHO:
|
|
frag = net_frag_read_u8(frag, offset, &offset,
|
|
(u8_t *)&length);
|
|
if (!frag) {
|
|
goto drop;
|
|
}
|
|
|
|
length = length * 8 + 8;
|
|
total_len += length;
|
|
|
|
/* HBH option needs to be the first one */
|
|
if (first_option != NET_IPV6_NEXTHDR_HBHO) {
|
|
goto bad_hdr;
|
|
}
|
|
|
|
/* Hop by hop option */
|
|
if (ext_bitmap & NET_IPV6_EXT_HDR_BITMAP_HBHO) {
|
|
goto bad_hdr;
|
|
}
|
|
|
|
ext_bitmap |= NET_IPV6_EXT_HDR_BITMAP_HBHO;
|
|
|
|
frag = handle_ext_hdr_options(pkt, frag, real_len,
|
|
length, offset, &offset,
|
|
&verdict);
|
|
break;
|
|
|
|
#if defined(CONFIG_NET_IPV6_FRAGMENT)
|
|
case NET_IPV6_NEXTHDR_FRAG:
|
|
net_pkt_set_ipv6_hdr_prev(pkt, prev_hdr);
|
|
|
|
net_pkt_set_ipv6_fragment_start(pkt,
|
|
sizeof(struct
|
|
net_ipv6_hdr) +
|
|
total_len);
|
|
|
|
total_len += 8;
|
|
return handle_fragment_hdr(pkt, frag, real_len, offset,
|
|
&offset, next_hdr);
|
|
#endif
|
|
default:
|
|
goto bad_hdr;
|
|
}
|
|
|
|
if (verdict == NET_DROP) {
|
|
goto drop;
|
|
}
|
|
|
|
prev_hdr = start_of_ext;
|
|
next = next_hdr;
|
|
}
|
|
|
|
upper_proto:
|
|
|
|
net_pkt_set_ipv6_ext_len(pkt, total_len);
|
|
|
|
switch (next) {
|
|
case IPPROTO_ICMPV6:
|
|
return process_icmpv6_pkt(pkt, hdr);
|
|
case IPPROTO_UDP:
|
|
#if defined(CONFIG_NET_UDP)
|
|
return net_conn_input(IPPROTO_UDP, pkt);
|
|
#else
|
|
return NET_DROP;
|
|
#endif
|
|
case IPPROTO_TCP:
|
|
#if defined(CONFIG_NET_TCP)
|
|
return net_conn_input(IPPROTO_TCP, pkt);
|
|
#else
|
|
return NET_DROP;
|
|
#endif
|
|
}
|
|
|
|
drop:
|
|
return NET_DROP;
|
|
|
|
bad_hdr:
|
|
/* Send error message about parameter problem (RFC 2460)
|
|
*/
|
|
net_icmpv6_send_error(pkt, NET_ICMPV6_PARAM_PROBLEM,
|
|
NET_ICMPV6_PARAM_PROB_NEXTHEADER,
|
|
offset - 1);
|
|
|
|
NET_DBG("Unknown next header type");
|
|
net_stats_update_ip_errors_protoerr(net_pkt_iface(pkt));
|
|
|
|
return NET_DROP;
|
|
}
|
|
|
|
void net_ipv6_init(void)
|
|
{
|
|
#if defined(CONFIG_NET_IPV6_NBR_CACHE)
|
|
net_icmpv6_register_handler(&ns_input_handler);
|
|
net_icmpv6_register_handler(&na_input_handler);
|
|
#endif
|
|
#if defined(CONFIG_NET_IPV6_ND)
|
|
net_icmpv6_register_handler(&ra_input_handler);
|
|
#endif
|
|
#if defined(CONFIG_NET_IPV6_MLD)
|
|
net_icmpv6_register_handler(&mld_query_input_handler);
|
|
#endif
|
|
}
|