1010 lines
28 KiB
C
1010 lines
28 KiB
C
|
|
/*********************************************************************
|
||
|
|
PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved.
|
||
|
|
See LICENSE and COPYING for usage.
|
||
|
|
|
||
|
|
.
|
||
|
|
|
||
|
|
Authors: Daniele Lacamera
|
||
|
|
*********************************************************************/
|
||
|
|
|
||
|
|
#include "pico_config.h"
|
||
|
|
#include "pico_tree.h"
|
||
|
|
#include "pico_icmp6.h"
|
||
|
|
#include "pico_ipv6.h"
|
||
|
|
#include "pico_stack.h"
|
||
|
|
#include "pico_device.h"
|
||
|
|
#include "pico_eth.h"
|
||
|
|
#include "pico_addressing.h"
|
||
|
|
#include "pico_ipv6_nd.h"
|
||
|
|
|
||
|
|
#ifdef PICO_SUPPORT_IPV6
|
||
|
|
|
||
|
|
|
||
|
|
#define nd_dbg(...) do {} while(0)
|
||
|
|
|
||
|
|
static struct pico_frame *frames_queued_v6[PICO_ND_MAX_FRAMES_QUEUED] = { 0 };
|
||
|
|
|
||
|
|
|
||
|
|
enum pico_ipv6_neighbor_state {
|
||
|
|
PICO_ND_STATE_INCOMPLETE = 0,
|
||
|
|
PICO_ND_STATE_REACHABLE,
|
||
|
|
PICO_ND_STATE_STALE,
|
||
|
|
PICO_ND_STATE_DELAY,
|
||
|
|
PICO_ND_STATE_PROBE
|
||
|
|
};
|
||
|
|
|
||
|
|
struct pico_ipv6_neighbor {
|
||
|
|
enum pico_ipv6_neighbor_state state;
|
||
|
|
struct pico_ip6 address;
|
||
|
|
struct pico_eth mac;
|
||
|
|
struct pico_device *dev;
|
||
|
|
uint16_t is_router;
|
||
|
|
uint16_t failure_count;
|
||
|
|
pico_time expire;
|
||
|
|
};
|
||
|
|
|
||
|
|
static int pico_ipv6_neighbor_compare(void *ka, void *kb)
|
||
|
|
{
|
||
|
|
struct pico_ipv6_neighbor *a = ka, *b = kb;
|
||
|
|
return pico_ipv6_compare(&a->address, &b->address);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
PICO_TREE_DECLARE(NCache, pico_ipv6_neighbor_compare);
|
||
|
|
|
||
|
|
static struct pico_ipv6_neighbor *pico_nd_find_neighbor(struct pico_ip6 *dst)
|
||
|
|
{
|
||
|
|
struct pico_ipv6_neighbor test = {
|
||
|
|
0
|
||
|
|
};
|
||
|
|
|
||
|
|
test.address = *dst;
|
||
|
|
return pico_tree_findKey(&NCache, &test);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pico_ipv6_nd_queued_trigger(void)
|
||
|
|
{
|
||
|
|
int i;
|
||
|
|
struct pico_frame *f;
|
||
|
|
for (i = 0; i < PICO_ND_MAX_FRAMES_QUEUED; i++)
|
||
|
|
{
|
||
|
|
f = frames_queued_v6[i];
|
||
|
|
if (f) {
|
||
|
|
(void)pico_ethernet_send(f);
|
||
|
|
if(frames_queued_v6[i])
|
||
|
|
pico_frame_discard(frames_queued_v6[i]);
|
||
|
|
frames_queued_v6[i] = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void ipv6_duplicate_detected(struct pico_ipv6_link *l)
|
||
|
|
{
|
||
|
|
struct pico_device *dev;
|
||
|
|
int is_ll = pico_ipv6_is_linklocal(l->address.addr);
|
||
|
|
dev = l->dev;
|
||
|
|
dbg("IPV6: Duplicate address detected. Removing link.\n");
|
||
|
|
pico_ipv6_link_del(l->dev, l->address);
|
||
|
|
if (is_ll)
|
||
|
|
pico_device_ipv6_random_ll(dev);
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct pico_ipv6_neighbor *pico_nd_add(struct pico_ip6 *addr, struct pico_device *dev)
|
||
|
|
{
|
||
|
|
struct pico_ipv6_neighbor *n = PICO_ZALLOC(sizeof(struct pico_ipv6_neighbor));
|
||
|
|
char address[120];
|
||
|
|
if (!n)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
pico_ipv6_to_string(address, addr->addr);
|
||
|
|
nd_dbg("Adding address %s to cache...\n", address);
|
||
|
|
memcpy(&n->address, addr, sizeof(struct pico_ip6));
|
||
|
|
n->dev = dev;
|
||
|
|
pico_tree_insert(&NCache, n);
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pico_ipv6_nd_unreachable(struct pico_ip6 *a)
|
||
|
|
{
|
||
|
|
int i;
|
||
|
|
struct pico_frame *f;
|
||
|
|
struct pico_ipv6_hdr *hdr;
|
||
|
|
struct pico_ip6 dst;
|
||
|
|
for (i = 0; i < PICO_ND_MAX_FRAMES_QUEUED; i++)
|
||
|
|
{
|
||
|
|
f = frames_queued_v6[i];
|
||
|
|
if (f) {
|
||
|
|
hdr = (struct pico_ipv6_hdr *) f->net_hdr;
|
||
|
|
dst = pico_ipv6_route_get_gateway(&hdr->dst);
|
||
|
|
if (pico_ipv6_is_unspecified(dst.addr))
|
||
|
|
dst = hdr->dst;
|
||
|
|
|
||
|
|
if (memcmp(dst.addr, a->addr, PICO_SIZE_IP6) == 0) {
|
||
|
|
if (!pico_source_is_local(f)) {
|
||
|
|
pico_notify_dest_unreachable(f);
|
||
|
|
}
|
||
|
|
|
||
|
|
pico_frame_discard(f);
|
||
|
|
frames_queued_v6[i] = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pico_nd_new_expire_time(struct pico_ipv6_neighbor *n)
|
||
|
|
{
|
||
|
|
if (n->state == PICO_ND_STATE_REACHABLE)
|
||
|
|
n->expire = PICO_TIME_MS() + PICO_ND_REACHABLE_TIME;
|
||
|
|
else if ((n->state == PICO_ND_STATE_DELAY) || (n->state == PICO_ND_STATE_STALE))
|
||
|
|
n->expire = PICO_TIME_MS() + PICO_ND_DELAY_FIRST_PROBE_TIME;
|
||
|
|
else {
|
||
|
|
n->expire = n->dev->hostvars.retranstime + PICO_TIME_MS();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pico_nd_discover(struct pico_ipv6_neighbor *n)
|
||
|
|
{
|
||
|
|
char IPADDR[64];
|
||
|
|
if (n->expire != (pico_time)0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
pico_ipv6_to_string(IPADDR, n->address.addr);
|
||
|
|
/* dbg("Sending NS for %s\n", IPADDR); */
|
||
|
|
if (++n->failure_count > PICO_ND_MAX_SOLICIT)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (n->state == PICO_ND_STATE_INCOMPLETE) {
|
||
|
|
pico_icmp6_neighbor_solicitation(n->dev, &n->address, PICO_ICMP6_ND_SOLICITED);
|
||
|
|
} else {
|
||
|
|
pico_icmp6_neighbor_solicitation(n->dev, &n->address, PICO_ICMP6_ND_UNICAST);
|
||
|
|
}
|
||
|
|
|
||
|
|
pico_nd_new_expire_time(n);
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct pico_eth *pico_nd_get_neighbor(struct pico_ip6 *addr, struct pico_ipv6_neighbor *n, struct pico_device *dev)
|
||
|
|
{
|
||
|
|
/* dbg("Finding neighbor %02x:...:%02x, state = %d\n", addr->addr[0], addr->addr[15], n?n->state:-1); */
|
||
|
|
|
||
|
|
if (!n) {
|
||
|
|
n = pico_nd_add(addr, dev);
|
||
|
|
pico_nd_discover(n);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (n->state == PICO_ND_STATE_INCOMPLETE) {
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (n->state == PICO_ND_STATE_STALE) {
|
||
|
|
n->state = PICO_ND_STATE_DELAY;
|
||
|
|
pico_nd_new_expire_time(n);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (n->state != PICO_ND_STATE_REACHABLE)
|
||
|
|
pico_nd_discover(n);
|
||
|
|
|
||
|
|
return &n->mac;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct pico_eth *pico_nd_get(struct pico_ip6 *address, struct pico_device *dev)
|
||
|
|
{
|
||
|
|
struct pico_ip6 gateway = {{0}}, addr = {{0}};
|
||
|
|
|
||
|
|
/* should we use gateway, or is dst local (gateway == 0)? */
|
||
|
|
gateway = pico_ipv6_route_get_gateway(address);
|
||
|
|
if (memcmp(gateway.addr, PICO_IP6_ANY, PICO_SIZE_IP6) == 0)
|
||
|
|
addr = *address;
|
||
|
|
else
|
||
|
|
addr = gateway;
|
||
|
|
|
||
|
|
return pico_nd_get_neighbor(&addr, pico_nd_find_neighbor(&addr), dev);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int neigh_options(struct pico_frame *f, struct pico_icmp6_opt_lladdr *opt, uint8_t expected_opt)
|
||
|
|
{
|
||
|
|
/* RFC 4861 $7.1.2 + $7.2.5.
|
||
|
|
* * The contents of any defined options that are not specified to be used
|
||
|
|
* * with Neighbor Advertisement messages MUST be ignored and the packet
|
||
|
|
* * processed as normal. The only defined option that may appear is the
|
||
|
|
* * Target Link-Layer Address option.
|
||
|
|
* */
|
||
|
|
int optlen = 0;
|
||
|
|
uint8_t *option = NULL;
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
int len;
|
||
|
|
uint8_t type;
|
||
|
|
int found = 0;
|
||
|
|
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
optlen = f->transport_len - PICO_ICMP6HDR_NEIGH_ADV_SIZE;
|
||
|
|
if (optlen)
|
||
|
|
option = ((uint8_t *)&icmp6_hdr->msg.info.neigh_adv) + sizeof(struct neigh_adv_s);
|
||
|
|
|
||
|
|
while (optlen > 0) {
|
||
|
|
type = ((struct pico_icmp6_opt_lladdr *)option)->type;
|
||
|
|
len = ((struct pico_icmp6_opt_lladdr *)option)->len;
|
||
|
|
optlen -= len << 3; /* len in units of 8 octets */
|
||
|
|
if (len <= 0)
|
||
|
|
return -1; /* malformed option. */
|
||
|
|
|
||
|
|
if (type == expected_opt) {
|
||
|
|
if (found > 0)
|
||
|
|
return -1; /* malformed option: option is there twice. */
|
||
|
|
|
||
|
|
memcpy(opt, (struct pico_icmp6_opt_lladdr *)option, (size_t)(len << 3));
|
||
|
|
found++;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (optlen > 0) {
|
||
|
|
option += len << 3;
|
||
|
|
} else { /* parsing options: terminated. */
|
||
|
|
return found;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return found;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pico_ipv6_neighbor_update(struct pico_ipv6_neighbor *n, struct pico_icmp6_opt_lladdr *opt)
|
||
|
|
{
|
||
|
|
memcpy(n->mac.addr, opt->addr.mac.addr, PICO_SIZE_ETH);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int pico_ipv6_neighbor_compare_stored(struct pico_ipv6_neighbor *n, struct pico_icmp6_opt_lladdr *opt)
|
||
|
|
{
|
||
|
|
return memcmp(n->mac.addr, opt->addr.mac.addr, PICO_SIZE_ETH);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void neigh_adv_reconfirm_router_option(struct pico_ipv6_neighbor *n, unsigned int isRouter)
|
||
|
|
{
|
||
|
|
if (!isRouter && n->is_router) {
|
||
|
|
pico_ipv6_router_down(&n->address);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (isRouter)
|
||
|
|
n->is_router = 1;
|
||
|
|
else
|
||
|
|
n->is_router = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static int neigh_adv_reconfirm_no_tlla(struct pico_ipv6_neighbor *n, struct pico_icmp6_hdr *hdr)
|
||
|
|
{
|
||
|
|
if (IS_SOLICITED(hdr)) {
|
||
|
|
n->state = PICO_ND_STATE_REACHABLE;
|
||
|
|
n->failure_count = 0;
|
||
|
|
pico_ipv6_nd_queued_trigger();
|
||
|
|
pico_nd_new_expire_time(n);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static int neigh_adv_reconfirm(struct pico_ipv6_neighbor *n, struct pico_icmp6_opt_lladdr *opt, struct pico_icmp6_hdr *hdr)
|
||
|
|
{
|
||
|
|
|
||
|
|
if (IS_SOLICITED(hdr) && !IS_OVERRIDE(hdr) && (pico_ipv6_neighbor_compare_stored(n, opt) == 0)) {
|
||
|
|
n->state = PICO_ND_STATE_REACHABLE;
|
||
|
|
n->failure_count = 0;
|
||
|
|
pico_ipv6_nd_queued_trigger();
|
||
|
|
pico_nd_new_expire_time(n);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((n->state == PICO_ND_STATE_REACHABLE) && IS_SOLICITED(hdr) && !IS_OVERRIDE(hdr)) {
|
||
|
|
n->state = PICO_ND_STATE_STALE;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (IS_SOLICITED(hdr) && IS_OVERRIDE(hdr)) {
|
||
|
|
pico_ipv6_neighbor_update(n, opt);
|
||
|
|
n->state = PICO_ND_STATE_REACHABLE;
|
||
|
|
n->failure_count = 0;
|
||
|
|
pico_ipv6_nd_queued_trigger();
|
||
|
|
pico_nd_new_expire_time(n);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!IS_SOLICITED(hdr) && IS_OVERRIDE(hdr) && (pico_ipv6_neighbor_compare_stored(n, opt) != 0)) {
|
||
|
|
pico_ipv6_neighbor_update(n, opt);
|
||
|
|
n->state = PICO_ND_STATE_STALE;
|
||
|
|
pico_ipv6_nd_queued_trigger();
|
||
|
|
pico_nd_new_expire_time(n);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((n->state == PICO_ND_STATE_REACHABLE) && (!IS_SOLICITED(hdr)) && (!IS_OVERRIDE(hdr)) &&
|
||
|
|
(pico_ipv6_neighbor_compare_stored(n, opt) != 0)) {
|
||
|
|
|
||
|
|
/* I. If the Override flag is clear and the supplied link-layer address
|
||
|
|
* differs from that in the cache, then one of two actions takes
|
||
|
|
* place:
|
||
|
|
* a. If the state of the entry is REACHABLE, set it to STALE, but
|
||
|
|
* do not update the entry in any other way.
|
||
|
|
* b. Otherwise, the received advertisement should be ignored and
|
||
|
|
* MUST NOT update the cache.
|
||
|
|
*/
|
||
|
|
n->state = PICO_ND_STATE_STALE;
|
||
|
|
pico_nd_new_expire_time(n);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void neigh_adv_process_incomplete(struct pico_ipv6_neighbor *n, struct pico_frame *f, struct pico_icmp6_opt_lladdr *opt)
|
||
|
|
{
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
if (!n || !f)
|
||
|
|
return;
|
||
|
|
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
|
||
|
|
if (!icmp6_hdr)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (IS_SOLICITED(icmp6_hdr)) {
|
||
|
|
n->state = PICO_ND_STATE_REACHABLE;
|
||
|
|
n->failure_count = 0;
|
||
|
|
pico_nd_new_expire_time(n);
|
||
|
|
} else {
|
||
|
|
n->state = PICO_ND_STATE_STALE;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (opt)
|
||
|
|
pico_ipv6_neighbor_update(n, opt);
|
||
|
|
|
||
|
|
pico_ipv6_nd_queued_trigger();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static int neigh_adv_process(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
struct pico_ipv6_neighbor *n = NULL;
|
||
|
|
struct pico_icmp6_opt_lladdr opt = {
|
||
|
|
0
|
||
|
|
};
|
||
|
|
int optres = neigh_options(f, &opt, PICO_ND_OPT_LLADDR_TGT);
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
|
||
|
|
if (optres < 0) { /* Malformed packet: option field cannot be processed. */
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
n = pico_nd_find_neighbor(&icmp6_hdr->msg.info.neigh_adv.target);
|
||
|
|
if (!n) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((optres == 0) || IS_OVERRIDE(icmp6_hdr) || (pico_ipv6_neighbor_compare_stored(n, &opt) == 0)) {
|
||
|
|
neigh_adv_reconfirm_router_option(n, IS_ROUTER(icmp6_hdr));
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((optres > 0) && (n->state == PICO_ND_STATE_INCOMPLETE)) {
|
||
|
|
neigh_adv_process_incomplete(n, f, &opt);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (optres > 0)
|
||
|
|
return neigh_adv_reconfirm(n, &opt, icmp6_hdr);
|
||
|
|
else
|
||
|
|
return neigh_adv_reconfirm_no_tlla(n, icmp6_hdr);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
static struct pico_ipv6_neighbor *pico_ipv6_neighbor_from_sol_new(struct pico_ip6 *ip, struct pico_icmp6_opt_lladdr *opt, struct pico_device *dev)
|
||
|
|
{
|
||
|
|
struct pico_ipv6_neighbor *n = NULL;
|
||
|
|
n = pico_nd_add(ip, dev);
|
||
|
|
if (!n)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
memcpy(n->mac.addr, opt->addr.mac.addr, PICO_SIZE_ETH);
|
||
|
|
n->state = PICO_ND_STATE_STALE;
|
||
|
|
pico_ipv6_nd_queued_trigger();
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pico_ipv6_neighbor_from_unsolicited(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
struct pico_ipv6_neighbor *n = NULL;
|
||
|
|
struct pico_icmp6_opt_lladdr opt = {
|
||
|
|
0
|
||
|
|
};
|
||
|
|
struct pico_ipv6_hdr *ip = (struct pico_ipv6_hdr *)f->net_hdr;
|
||
|
|
int valid_lladdr = neigh_options(f, &opt, PICO_ND_OPT_LLADDR_SRC);
|
||
|
|
|
||
|
|
if (!pico_ipv6_is_unspecified(ip->src.addr) && (valid_lladdr > 0)) {
|
||
|
|
n = pico_nd_find_neighbor(&ip->src);
|
||
|
|
if (!n) {
|
||
|
|
n = pico_ipv6_neighbor_from_sol_new(&ip->src, &opt, f->dev);
|
||
|
|
} else if (memcmp(opt.addr.mac.addr, n->mac.addr, PICO_SIZE_ETH)) {
|
||
|
|
pico_ipv6_neighbor_update(n, &opt);
|
||
|
|
n->state = PICO_ND_STATE_STALE;
|
||
|
|
pico_ipv6_nd_queued_trigger();
|
||
|
|
pico_nd_new_expire_time(n);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!n)
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int neigh_sol_detect_dad(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
struct pico_ipv6_hdr *ipv6_hdr = NULL;
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
struct pico_ipv6_link *link = NULL;
|
||
|
|
ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
link = pico_ipv6_link_istentative(&icmp6_hdr->msg.info.neigh_adv.target);
|
||
|
|
if (link) {
|
||
|
|
if (pico_ipv6_is_unicast(&ipv6_hdr->src))
|
||
|
|
{
|
||
|
|
/* RFC4862 5.4.3 : sender is performing address resolution,
|
||
|
|
* our address is not yet valid, discard silently.
|
||
|
|
*/
|
||
|
|
dbg("DAD:Sender performing AR\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
else if (pico_ipv6_is_unspecified(ipv6_hdr->src.addr) &&
|
||
|
|
!pico_ipv6_is_allhosts_multicast(ipv6_hdr->dst.addr))
|
||
|
|
{
|
||
|
|
/* RFC4862 5.4.3 : sender is performing DaD */
|
||
|
|
dbg("DAD:Sender performing DaD\n");
|
||
|
|
ipv6_duplicate_detected(link);
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return -1; /* Current link is not tentative */
|
||
|
|
}
|
||
|
|
|
||
|
|
static int neigh_sol_process(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
struct pico_ipv6_link *link = NULL;
|
||
|
|
int valid_lladdr;
|
||
|
|
struct pico_icmp6_opt_lladdr opt = {
|
||
|
|
0
|
||
|
|
};
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
|
||
|
|
valid_lladdr = neigh_options(f, &opt, PICO_ND_OPT_LLADDR_SRC);
|
||
|
|
pico_ipv6_neighbor_from_unsolicited(f);
|
||
|
|
|
||
|
|
if ((valid_lladdr == 0) && (neigh_sol_detect_dad(f) == 0))
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
if (valid_lladdr < 0)
|
||
|
|
return -1; /* Malformed packet. */
|
||
|
|
|
||
|
|
link = pico_ipv6_link_get(&icmp6_hdr->msg.info.neigh_adv.target);
|
||
|
|
if (!link) { /* Not for us. */
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
pico_icmp6_neighbor_advertisement(f, &icmp6_hdr->msg.info.neigh_adv.target);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int icmp6_initial_checks(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
/* Common "step 0" validation */
|
||
|
|
struct pico_ipv6_hdr *ipv6_hdr = NULL;
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
|
||
|
|
ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
|
||
|
|
/* RFC4861 - 7.1.2 :
|
||
|
|
* - The IP Hop Limit field has a value of 255, i.e., the packet
|
||
|
|
* could not possibly have been forwarded by a router.
|
||
|
|
* - ICMP Checksum is valid.
|
||
|
|
* - ICMP Code is 0.
|
||
|
|
*/
|
||
|
|
if (ipv6_hdr->hop != 255 || pico_icmp6_checksum(f) != 0 || icmp6_hdr->code != 0)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int neigh_adv_option_len_validity_check(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
/* Step 4 validation */
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
uint8_t *opt;
|
||
|
|
int optlen = f->transport_len - PICO_ICMP6HDR_NEIGH_ADV_SIZE;
|
||
|
|
/* RFC4861 - 7.1.2 :
|
||
|
|
* - All included options have a length that is greater than zero.
|
||
|
|
*/
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
opt = ((uint8_t *)&icmp6_hdr->msg.info.neigh_adv) + sizeof(struct neigh_adv_s);
|
||
|
|
|
||
|
|
while(optlen > 0) {
|
||
|
|
int opt_size = (opt[1] << 3);
|
||
|
|
if (opt_size == 0)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
opt = opt + opt_size;
|
||
|
|
optlen -= opt_size;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int neigh_adv_mcast_validity_check(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
/* Step 3 validation */
|
||
|
|
struct pico_ipv6_hdr *ipv6_hdr = NULL;
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
/* RFC4861 - 7.1.2 :
|
||
|
|
* - If the IP Destination Address is a multicast address the
|
||
|
|
* Solicited flag is zero.
|
||
|
|
*/
|
||
|
|
ipv6_hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
if (pico_ipv6_is_multicast(ipv6_hdr->dst.addr) && IS_SOLICITED(icmp6_hdr))
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
return neigh_adv_option_len_validity_check(f);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int neigh_adv_validity_checks(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
/* Step 2 validation */
|
||
|
|
/* RFC4861 - 7.1.2:
|
||
|
|
* - ICMP length (derived from the IP length) is 24 or more octets.
|
||
|
|
*/
|
||
|
|
if (f->transport_len < PICO_ICMP6HDR_NEIGH_ADV_SIZE)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
return neigh_adv_mcast_validity_check(f);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static int neigh_sol_mcast_validity_check(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
if (pico_ipv6_is_solnode_multicast(icmp6_hdr->msg.info.neigh_sol.target.addr, f->dev) == 0)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int neigh_sol_unicast_validity_check(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
struct pico_ipv6_link *link;
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
|
||
|
|
link = pico_ipv6_link_by_dev(f->dev);
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
while(link) {
|
||
|
|
if (pico_ipv6_compare(&link->address, &icmp6_hdr->msg.info.neigh_sol.target) == 0)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
link = pico_ipv6_link_by_dev_next(f->dev, link);
|
||
|
|
}
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
static int neigh_sol_validate_unspec(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
/* RFC4861, 7.1.1:
|
||
|
|
*
|
||
|
|
* - If the IP source address is the unspecified address, the IP
|
||
|
|
* destination address is a solicited-node multicast address.
|
||
|
|
*
|
||
|
|
* - If the IP source address is the unspecified address, there is no
|
||
|
|
* source link-layer address option in the message.
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
|
||
|
|
struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)(f->net_hdr);
|
||
|
|
struct pico_icmp6_opt_lladdr opt = {
|
||
|
|
0
|
||
|
|
};
|
||
|
|
int valid_lladdr = neigh_options(f, &opt, PICO_ND_OPT_LLADDR_SRC);
|
||
|
|
if (pico_ipv6_is_solnode_multicast(hdr->dst.addr, f->dev) == 0) {
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (valid_lladdr) {
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int neigh_sol_validity_checks(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
/* Step 2 validation */
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
struct pico_ipv6_hdr *hdr = (struct pico_ipv6_hdr *)(f->net_hdr);
|
||
|
|
if (f->transport_len < PICO_ICMP6HDR_NEIGH_ADV_SIZE)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
if ((pico_ipv6_is_unspecified(hdr->src.addr)) && (neigh_sol_validate_unspec(f) < 0))
|
||
|
|
{
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
if (pico_ipv6_is_multicast(icmp6_hdr->msg.info.neigh_adv.target.addr)) {
|
||
|
|
return neigh_sol_mcast_validity_check(f);
|
||
|
|
}
|
||
|
|
|
||
|
|
return neigh_sol_unicast_validity_check(f);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int router_adv_validity_checks(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
/* Step 2 validation */
|
||
|
|
if (f->transport_len < PICO_ICMP6HDR_ROUTER_ADV_SIZE)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int neigh_adv_checks(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
/* Step 1 validation */
|
||
|
|
if (icmp6_initial_checks(f) < 0)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
return neigh_adv_validity_checks(f);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static int pico_nd_router_sol_recv(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
pico_ipv6_neighbor_from_unsolicited(f);
|
||
|
|
/* Host only: router solicitation is discarded. */
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int radv_process(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
uint8_t *nxtopt, *opt_start;
|
||
|
|
struct pico_ipv6_link *link;
|
||
|
|
struct pico_ipv6_hdr *hdr;
|
||
|
|
struct pico_ip6 zero = {
|
||
|
|
.addr = {0}
|
||
|
|
};
|
||
|
|
int optlen;
|
||
|
|
|
||
|
|
hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
optlen = f->transport_len - PICO_ICMP6HDR_ROUTER_ADV_SIZE;
|
||
|
|
opt_start = ((uint8_t *)&icmp6_hdr->msg.info.router_adv) + sizeof(struct router_adv_s);
|
||
|
|
nxtopt = opt_start;
|
||
|
|
|
||
|
|
while (optlen > 0) {
|
||
|
|
uint8_t *type = (uint8_t *)nxtopt;
|
||
|
|
switch (*type) {
|
||
|
|
case PICO_ND_OPT_PREFIX:
|
||
|
|
{
|
||
|
|
pico_time now = PICO_TIME_MS();
|
||
|
|
struct pico_icmp6_opt_prefix *prefix =
|
||
|
|
(struct pico_icmp6_opt_prefix *) nxtopt;
|
||
|
|
/* RFC4862 5.5.3 */
|
||
|
|
/* a) If the Autonomous flag is not set, silently ignore the Prefix
|
||
|
|
* Information option.
|
||
|
|
*/
|
||
|
|
if (prefix->aac == 0)
|
||
|
|
goto ignore_opt_prefix;
|
||
|
|
|
||
|
|
/* b) If the prefix is the link-local prefix, silently ignore the
|
||
|
|
* Prefix Information option
|
||
|
|
*/
|
||
|
|
if (pico_ipv6_is_linklocal(prefix->prefix.addr))
|
||
|
|
goto ignore_opt_prefix;
|
||
|
|
|
||
|
|
/* c) If the preferred lifetime is greater than the valid lifetime,
|
||
|
|
* silently ignore the Prefix Information option
|
||
|
|
*/
|
||
|
|
if (long_be(prefix->pref_lifetime) > long_be(prefix->val_lifetime))
|
||
|
|
goto ignore_opt_prefix;
|
||
|
|
|
||
|
|
if (prefix->val_lifetime == 0)
|
||
|
|
goto ignore_opt_prefix;
|
||
|
|
|
||
|
|
|
||
|
|
if (prefix->prefix_len != 64) {
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
link = pico_ipv6_prefix_configured(&prefix->prefix);
|
||
|
|
if (link) {
|
||
|
|
pico_ipv6_lifetime_set(link, now + (pico_time)(1000 * (long_be(prefix->val_lifetime))));
|
||
|
|
goto ignore_opt_prefix;
|
||
|
|
}
|
||
|
|
|
||
|
|
link = pico_ipv6_link_add_local(f->dev, &prefix->prefix);
|
||
|
|
if (link) {
|
||
|
|
pico_ipv6_lifetime_set(link, now + (pico_time)(1000 * (long_be(prefix->val_lifetime))));
|
||
|
|
pico_ipv6_route_add(zero, zero, hdr->src, 10, link);
|
||
|
|
}
|
||
|
|
|
||
|
|
ignore_opt_prefix:
|
||
|
|
optlen -= (prefix->len << 3);
|
||
|
|
nxtopt += (prefix->len << 3);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case PICO_ND_OPT_LLADDR_SRC:
|
||
|
|
{
|
||
|
|
struct pico_icmp6_opt_lladdr *lladdr_src =
|
||
|
|
(struct pico_icmp6_opt_lladdr *) nxtopt;
|
||
|
|
optlen -= (lladdr_src->len << 3);
|
||
|
|
nxtopt += (lladdr_src->len << 3);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case PICO_ND_OPT_MTU:
|
||
|
|
{
|
||
|
|
struct pico_icmp6_opt_mtu *mtu =
|
||
|
|
(struct pico_icmp6_opt_mtu *) nxtopt;
|
||
|
|
/* Skip this */
|
||
|
|
optlen -= (mtu->len << 3);
|
||
|
|
nxtopt += (mtu->len << 3);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case PICO_ND_OPT_REDIRECT:
|
||
|
|
{
|
||
|
|
struct pico_icmp6_opt_redirect *redirect =
|
||
|
|
(struct pico_icmp6_opt_redirect *) nxtopt;
|
||
|
|
/* Skip this */
|
||
|
|
optlen -= (redirect->len << 3);
|
||
|
|
nxtopt += (redirect->len << 3);
|
||
|
|
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case PICO_ND_OPT_RDNSS:
|
||
|
|
{
|
||
|
|
struct pico_icmp6_opt_rdnss *rdnss =
|
||
|
|
(struct pico_icmp6_opt_rdnss *) nxtopt;
|
||
|
|
/* Skip this */
|
||
|
|
optlen -= (rdnss->len << 3);
|
||
|
|
nxtopt += (rdnss->len << 3);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
pico_icmp6_parameter_problem(f, PICO_ICMP6_PARAMPROB_IPV6OPT,
|
||
|
|
(uint32_t)sizeof(struct pico_ipv6_hdr) + (uint32_t)PICO_ICMP6HDR_ROUTER_ADV_SIZE + (uint32_t)(nxtopt - opt_start));
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (icmp6_hdr->msg.info.router_adv.retrans_time != 0u) {
|
||
|
|
f->dev->hostvars.retranstime = long_be(icmp6_hdr->msg.info.router_adv.retrans_time);
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static int pico_nd_router_adv_recv(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
if (icmp6_initial_checks(f) < 0)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
if (router_adv_validity_checks(f) < 0)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
pico_ipv6_neighbor_from_unsolicited(f);
|
||
|
|
return radv_process(f);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int pico_nd_neigh_sol_recv(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
if (icmp6_initial_checks(f) < 0)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
if (neigh_sol_validity_checks(f) < 0)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
return neigh_sol_process(f);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int pico_nd_neigh_adv_recv(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
struct pico_icmp6_hdr *icmp6_hdr = NULL;
|
||
|
|
struct pico_ipv6_link *link = NULL;
|
||
|
|
|
||
|
|
icmp6_hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
if (neigh_adv_checks(f) < 0) {
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
link = pico_ipv6_link_istentative(&icmp6_hdr->msg.info.neigh_adv.target);
|
||
|
|
if (link)
|
||
|
|
ipv6_duplicate_detected(link);
|
||
|
|
|
||
|
|
return neigh_adv_process(f);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int pico_nd_redirect_recv(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
pico_ipv6_neighbor_from_unsolicited(f);
|
||
|
|
/* TODO */
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pico_ipv6_nd_timer_elapsed(pico_time now, struct pico_ipv6_neighbor *n)
|
||
|
|
{
|
||
|
|
(void)now;
|
||
|
|
switch(n->state) {
|
||
|
|
case PICO_ND_STATE_INCOMPLETE:
|
||
|
|
/* intentional fall through */
|
||
|
|
case PICO_ND_STATE_PROBE:
|
||
|
|
if (n->failure_count > PICO_ND_MAX_SOLICIT) {
|
||
|
|
pico_ipv6_nd_unreachable(&n->address);
|
||
|
|
pico_tree_delete(&NCache, n);
|
||
|
|
PICO_FREE(n);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
n->expire = 0ull;
|
||
|
|
pico_nd_discover(n);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case PICO_ND_STATE_REACHABLE:
|
||
|
|
n->state = PICO_ND_STATE_STALE;
|
||
|
|
/* dbg("IPv6_ND: neighbor expired!\n"); */
|
||
|
|
return;
|
||
|
|
|
||
|
|
case PICO_ND_STATE_STALE:
|
||
|
|
break;
|
||
|
|
|
||
|
|
case PICO_ND_STATE_DELAY:
|
||
|
|
n->expire = 0ull;
|
||
|
|
n->state = PICO_ND_STATE_PROBE;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
dbg("IPv6_ND: neighbor in wrong state!\n");
|
||
|
|
}
|
||
|
|
pico_nd_new_expire_time(n);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pico_ipv6_nd_timer_callback(pico_time now, void *arg)
|
||
|
|
{
|
||
|
|
struct pico_tree_node *index = NULL, *_tmp = NULL;
|
||
|
|
struct pico_ipv6_neighbor *n;
|
||
|
|
|
||
|
|
(void)arg;
|
||
|
|
pico_tree_foreach_safe(index, &NCache, _tmp)
|
||
|
|
{
|
||
|
|
n = index->keyValue;
|
||
|
|
if ( now > n->expire) {
|
||
|
|
pico_ipv6_nd_timer_elapsed(now, n);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
pico_timer_add(200, pico_ipv6_nd_timer_callback, NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
#define PICO_IPV6_ND_MIN_RADV_INTERVAL (5000)
|
||
|
|
#define PICO_IPV6_ND_MAX_RADV_INTERVAL (15000)
|
||
|
|
|
||
|
|
static void pico_ipv6_nd_ra_timer_callback(pico_time now, void *arg)
|
||
|
|
{
|
||
|
|
struct pico_tree_node *devindex = NULL;
|
||
|
|
struct pico_tree_node *rindex = NULL;
|
||
|
|
struct pico_device *dev;
|
||
|
|
struct pico_ipv6_route *rt;
|
||
|
|
struct pico_ip6 nm64 = { {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0 } };
|
||
|
|
pico_time next_timer_expire = 0u;
|
||
|
|
|
||
|
|
(void)arg;
|
||
|
|
(void)now;
|
||
|
|
pico_tree_foreach(rindex, &IPV6Routes)
|
||
|
|
{
|
||
|
|
rt = rindex->keyValue;
|
||
|
|
if (pico_ipv6_compare(&nm64, &rt->netmask) == 0) {
|
||
|
|
pico_tree_foreach(devindex, &Device_tree) {
|
||
|
|
dev = devindex->keyValue;
|
||
|
|
if ((!pico_ipv6_is_linklocal(rt->dest.addr)) && dev->hostvars.routing && (rt->link) && (dev != rt->link->dev)) {
|
||
|
|
pico_icmp6_router_advertisement(dev, &rt->dest);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
next_timer_expire = PICO_IPV6_ND_MIN_RADV_INTERVAL + (pico_rand() % (PICO_IPV6_ND_MAX_RADV_INTERVAL - PICO_IPV6_ND_MIN_RADV_INTERVAL));
|
||
|
|
pico_timer_add(next_timer_expire, pico_ipv6_nd_ra_timer_callback, NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Public API */
|
||
|
|
|
||
|
|
struct pico_eth *pico_ipv6_get_neighbor(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
struct pico_ipv6_hdr *hdr = NULL;
|
||
|
|
struct pico_ipv6_link *l = NULL;
|
||
|
|
if (!f)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
hdr = (struct pico_ipv6_hdr *)f->net_hdr;
|
||
|
|
/* If we are still probing for Duplicate Address, abort now. */
|
||
|
|
if (pico_ipv6_link_istentative(&hdr->src))
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
/* address belongs to ourselves? */
|
||
|
|
l = pico_ipv6_link_get(&hdr->dst);
|
||
|
|
if (l)
|
||
|
|
return &l->dev->eth->mac;
|
||
|
|
|
||
|
|
return pico_nd_get(&hdr->dst, f->dev);
|
||
|
|
}
|
||
|
|
|
||
|
|
void pico_ipv6_nd_postpone(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
int i;
|
||
|
|
static int last_enq = -1;
|
||
|
|
struct pico_frame *cp = pico_frame_copy(f);
|
||
|
|
for (i = 0; i < PICO_ND_MAX_FRAMES_QUEUED; i++)
|
||
|
|
{
|
||
|
|
if (!frames_queued_v6[i]) {
|
||
|
|
frames_queued_v6[i] = cp;
|
||
|
|
last_enq = i;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/* Overwrite the oldest frame in the buffer */
|
||
|
|
if (++last_enq >= PICO_ND_MAX_FRAMES_QUEUED) {
|
||
|
|
last_enq = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (frames_queued_v6[last_enq])
|
||
|
|
pico_frame_discard(frames_queued_v6[last_enq]);
|
||
|
|
|
||
|
|
frames_queued_v6[last_enq] = cp;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int pico_ipv6_nd_recv(struct pico_frame *f)
|
||
|
|
{
|
||
|
|
|
||
|
|
struct pico_icmp6_hdr *hdr = (struct pico_icmp6_hdr *)f->transport_hdr;
|
||
|
|
int ret = -1;
|
||
|
|
switch(hdr->type) {
|
||
|
|
case PICO_ICMP6_ROUTER_SOL:
|
||
|
|
nd_dbg("ICMP6: received ROUTER SOL\n");
|
||
|
|
ret = pico_nd_router_sol_recv(f);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case PICO_ICMP6_ROUTER_ADV:
|
||
|
|
ret = pico_nd_router_adv_recv(f);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case PICO_ICMP6_NEIGH_SOL:
|
||
|
|
nd_dbg("ICMP6: received NEIGH SOL\n");
|
||
|
|
ret = pico_nd_neigh_sol_recv(f);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case PICO_ICMP6_NEIGH_ADV:
|
||
|
|
nd_dbg("ICMP6: received NEIGH ADV\n");
|
||
|
|
ret = pico_nd_neigh_adv_recv(f);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case PICO_ICMP6_REDIRECT:
|
||
|
|
ret = pico_nd_redirect_recv(f);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
pico_frame_discard(f);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
void pico_ipv6_nd_init(void)
|
||
|
|
{
|
||
|
|
pico_timer_add(200, pico_ipv6_nd_timer_callback, NULL);
|
||
|
|
pico_timer_add(200, pico_ipv6_nd_ra_timer_callback, NULL);
|
||
|
|
pico_timer_add(1000, pico_ipv6_check_lifetime_expired, NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif
|