Make DS6 neighbors use neighbor-table, and move all DS6 neighbor management from uip-ds6 to a new uip-ds6-neighbor module.
This commit is contained in:
parent
5a1d8d8006
commit
ec609b49eb
|
@ -18,6 +18,7 @@ uaodv-rt.c \
|
|||
uaodv.c \
|
||||
uip-debug.c \
|
||||
uip-ds6-route.c \
|
||||
uip-ds6-neighbor.c \
|
||||
uip-ds6.c \
|
||||
uip-fw-drv.c \
|
||||
uip-fw.c \
|
||||
|
|
|
@ -142,21 +142,6 @@ rpl_remove_routes_by_nexthop(uip_ipaddr_t *nexthop, rpl_dag_t *dag)
|
|||
}
|
||||
}
|
||||
ANNOTATE("#L %u 0\n", nexthop->u8[sizeof(uip_ipaddr_t) - 1]);
|
||||
|
||||
#if 0
|
||||
uip_ds6_route_t *locroute;
|
||||
|
||||
for(locroute = uip_ds6_routing_table;
|
||||
locroute < uip_ds6_routing_table + UIP_DS6_ROUTE_NB;
|
||||
locroute++) {
|
||||
if(locroute->isused
|
||||
&& uip_ipaddr_cmp(&locroute->nexthop, nexthop)
|
||||
&& locroute->state.dag == dag) {
|
||||
locroute->isused = 0;
|
||||
}
|
||||
}
|
||||
ANNOTATE("#L %u 0\n",nexthop->u8[sizeof(uip_ipaddr_t) - 1]);
|
||||
#endif /* 0 */
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_ds6_route_t *
|
||||
|
@ -242,18 +227,16 @@ rpl_ipv6_neighbor_callback(uip_ds6_nbr_t *nbr)
|
|||
rpl_instance_t *instance;
|
||||
rpl_instance_t *end;
|
||||
|
||||
if(!nbr->isused) {
|
||||
PRINTF("RPL: Removing neighbor ");
|
||||
PRINT6ADDR(&nbr->ipaddr);
|
||||
PRINTF("\n");
|
||||
for(instance = &instance_table[0], end = instance + RPL_MAX_INSTANCES; instance < end; ++instance) {
|
||||
if(instance->used == 1 ) {
|
||||
p = rpl_find_parent_any_dag(instance, &nbr->ipaddr);
|
||||
if(p != NULL) {
|
||||
p->rank = INFINITE_RANK;
|
||||
/* Trigger DAG rank recalculation. */
|
||||
p->updated = 1;
|
||||
}
|
||||
PRINTF("RPL: Removing neighbor ");
|
||||
PRINT6ADDR(&nbr->ipaddr);
|
||||
PRINTF("\n");
|
||||
for(instance = &instance_table[0], end = instance + RPL_MAX_INSTANCES; instance < end; ++instance) {
|
||||
if(instance->used == 1 ) {
|
||||
p = rpl_find_parent_any_dag(instance, &nbr->ipaddr);
|
||||
if(p != NULL) {
|
||||
p->rank = INFINITE_RANK;
|
||||
/* Trigger DAG rank recalculation. */
|
||||
p->updated = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -567,6 +567,7 @@ tcpip_ipv6_output(void)
|
|||
uip_ds6_route_t* locrt;
|
||||
locrt = uip_ds6_route_lookup(&UIP_IP_BUF->destipaddr);
|
||||
if(locrt == NULL) {
|
||||
PRINTF("tcpip_ipv6_output: no route found, using default route\n");
|
||||
if((nexthop = uip_ds6_defrt_choose()) == NULL) {
|
||||
#ifdef UIP_FALLBACK_INTERFACE
|
||||
PRINTF("FALLBACK: removing ext hdrs & setting proto %d %d\n",
|
||||
|
@ -588,11 +589,14 @@ tcpip_ipv6_output(void)
|
|||
} else {
|
||||
nexthop = &locrt->nexthop;
|
||||
}
|
||||
#if TCPIP_CONF_ANNOTATE_TRANSMISSIONS
|
||||
if(nexthop != NULL) {
|
||||
PRINTF("tcpip_ipv6_output: next hop ");
|
||||
PRINT6ADDR(nexthop);
|
||||
PRINTF("\n");
|
||||
#if TCPIP_CONF_ANNOTATE_TRANSMISSIONS
|
||||
printf("#L %u 1; red\n", nexthop->u8[sizeof(uip_ipaddr_t) - 1]);
|
||||
}
|
||||
#endif /* TCPIP_CONF_ANNOTATE_TRANSMISSIONS */
|
||||
}
|
||||
}
|
||||
/* End of next hop determination */
|
||||
#if UIP_CONF_IPV6_RPL
|
||||
|
@ -655,7 +659,7 @@ tcpip_ipv6_output(void)
|
|||
}
|
||||
#endif /* UIP_ND6_SEND_NA */
|
||||
|
||||
tcpip_output(&nbr->lladdr);
|
||||
tcpip_output(uip_ds6_nbr_get_ll(nbr));
|
||||
|
||||
#if UIP_CONF_IPV6_QUEUE_PKT
|
||||
/*
|
||||
|
@ -668,7 +672,7 @@ tcpip_ipv6_output(void)
|
|||
uip_len = uip_packetqueue_buflen(&nbr->packethandle);
|
||||
memcpy(UIP_IP_BUF, uip_packetqueue_buf(&nbr->packethandle), uip_len);
|
||||
uip_packetqueue_free(&nbr->packethandle);
|
||||
tcpip_output(&nbr->lladdr);
|
||||
tcpip_output(uip_ds6_nbr_get_ll(nbr));
|
||||
}
|
||||
#endif /*UIP_CONF_IPV6_QUEUE_PKT*/
|
||||
|
||||
|
|
248
core/net/uip-ds6-neighbor.c
Normal file
248
core/net/uip-ds6-neighbor.c
Normal file
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* \addtogroup uip6
|
||||
* @{
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2013, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* IPv6 Neighbor cache (link-layer/IPv6 address mapping)
|
||||
* \author Mathilde Durvy <mdurvy@cisco.com>
|
||||
* \author Julien Abeille <jabeille@cisco.com>
|
||||
* \author Simon Duquennoy <simonduq@sics.se>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include "lib/list.h"
|
||||
#include "net/rime/rimeaddr.h"
|
||||
#include "net/packetbuf.h"
|
||||
#include "net/uip-ds6-neighbor.h"
|
||||
|
||||
#define DEBUG DEBUG_NONE
|
||||
#include "net/uip-debug.h"
|
||||
|
||||
#ifdef UIP_CONF_DS6_NEIGHBOR_STATE_CHANGED
|
||||
#define NEIGHBOR_STATE_CHANGED(n) UIP_CONF_DS6_NEIGHBOR_STATE_CHANGED(n)
|
||||
void NEIGHBOR_STATE_CHANGED(uip_ds6_nbr_t *n);
|
||||
#else
|
||||
#define NEIGHBOR_STATE_CHANGED(n)
|
||||
#endif /* UIP_DS6_CONF_NEIGHBOR_STATE_CHANGED */
|
||||
|
||||
NEIGHBOR_TABLE_GLOBAL(uip_ds6_nbr_t, ds6_neighbors);
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void
|
||||
uip_ds6_neighbors_init(void)
|
||||
{
|
||||
nbr_table_register(ds6_neighbors, (remove_callback_func *)uip_ds6_nbr_rm);
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_ds6_nbr_t *
|
||||
uip_ds6_nbr_add(uip_ipaddr_t *ipaddr, uip_lladdr_t *lladdr,
|
||||
uint8_t isrouter, uint8_t state)
|
||||
{
|
||||
uip_ds6_nbr_t *nbr = nbr_table_add_lladdr(ds6_neighbors, (rimeaddr_t*)lladdr);
|
||||
if(nbr) {
|
||||
uip_ipaddr_copy(&nbr->ipaddr, ipaddr);
|
||||
nbr->isrouter = isrouter;
|
||||
nbr->state = state;
|
||||
#if UIP_CONF_IPV6_QUEUE_PKT
|
||||
uip_packetqueue_new(&nbr->packethandle);
|
||||
#endif /* UIP_CONF_IPV6_QUEUE_PKT */
|
||||
/* timers are set separately, for now we put them in expired state */
|
||||
stimer_set(&nbr->reachable, 0);
|
||||
stimer_set(&nbr->sendns, 0);
|
||||
nbr->nscount = 0;
|
||||
PRINTF("Adding neighbor with ip addr ");
|
||||
PRINT6ADDR(ipaddr);
|
||||
PRINTF(" link addr ");
|
||||
PRINTLLADDR(lladdr);
|
||||
PRINTF(" state %u\n", state);
|
||||
NEIGHBOR_STATE_CHANGED(nbr);
|
||||
return nbr;
|
||||
} else {
|
||||
PRINTF("uip_ds6_nbr_add drop ip addr ");
|
||||
PRINT6ADDR(ipaddr);
|
||||
PRINTF(" link addr (%p) ", lladdr);
|
||||
PRINTLLADDR(lladdr);
|
||||
PRINTF(" state %u\n", state);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void
|
||||
uip_ds6_nbr_rm(uip_ds6_nbr_t *nbr)
|
||||
{
|
||||
if(nbr != NULL) {
|
||||
#if UIP_CONF_IPV6_QUEUE_PKT
|
||||
uip_packetqueue_free(&nbr->packethandle);
|
||||
#endif /* UIP_CONF_IPV6_QUEUE_PKT */
|
||||
NEIGHBOR_STATE_CHANGED(nbr);
|
||||
nbr_table_remove(ds6_neighbors, nbr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_ipaddr_t *
|
||||
uip_ds6_nbr_get_ipaddr(uip_ds6_nbr_t *nbr)
|
||||
{
|
||||
return (nbr != NULL) ? &nbr->ipaddr : NULL;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_lladdr_t *
|
||||
uip_ds6_nbr_get_ll(uip_ds6_nbr_t *nbr)
|
||||
{
|
||||
return (uip_lladdr_t *)nbr_table_get_lladdr(ds6_neighbors, nbr);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_ds6_nbr_t *
|
||||
uip_ds6_nbr_lookup(uip_ipaddr_t *ipaddr)
|
||||
{
|
||||
uip_ds6_nbr_t *nbr = nbr_table_head(ds6_neighbors);
|
||||
while(nbr != NULL) {
|
||||
if(uip_ipaddr_cmp(&nbr->ipaddr, ipaddr)) {
|
||||
return nbr;
|
||||
}
|
||||
nbr = nbr_table_next(ds6_neighbors, nbr);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_ds6_nbr_t *
|
||||
uip_ds6_nbr_ll_lookup(uip_lladdr_t *lladdr)
|
||||
{
|
||||
return nbr_table_get_from_lladdr(ds6_neighbors, (rimeaddr_t*)lladdr);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_ipaddr_t *
|
||||
uip_ds6_nbr_ipaddr_from_lladdr(uip_lladdr_t *lladdr)
|
||||
{
|
||||
uip_ds6_nbr_t *nbr = uip_ds6_nbr_ll_lookup(lladdr);
|
||||
return nbr ? &nbr->ipaddr : NULL;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_lladdr_t *
|
||||
uip_ds6_nbr_lladdr_from_ipaddr(uip_ipaddr_t *ipaddr)
|
||||
{
|
||||
uip_ds6_nbr_t *nbr = uip_ds6_nbr_lookup(ipaddr);
|
||||
return nbr ? uip_ds6_nbr_get_ll(nbr) : NULL;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void
|
||||
uip_ds6_neighbor_periodic()
|
||||
{
|
||||
/* Periodic processing on neighbors */
|
||||
uip_ds6_nbr_t *nbr = nbr_table_head(ds6_neighbors);
|
||||
while(nbr != NULL) {
|
||||
switch(nbr->state) {
|
||||
case NBR_REACHABLE:
|
||||
if(stimer_expired(&nbr->reachable)) {
|
||||
PRINTF("REACHABLE: moving to STALE (");
|
||||
PRINT6ADDR(&nbr->ipaddr);
|
||||
PRINTF(")\n");
|
||||
nbr->state = NBR_STALE;
|
||||
}
|
||||
break;
|
||||
#if UIP_ND6_SEND_NA
|
||||
case NBR_INCOMPLETE:
|
||||
if(nbr->nscount >= UIP_ND6_MAX_MULTICAST_SOLICIT) {
|
||||
uip_ds6_nbr_rm(nbr);
|
||||
} else if(stimer_expired(&nbr->sendns) && (uip_len == 0)) {
|
||||
nbr->nscount++;
|
||||
PRINTF("NBR_INCOMPLETE: NS %u\n", nbr->nscount);
|
||||
uip_nd6_ns_output(NULL, NULL, &nbr->ipaddr);
|
||||
stimer_set(&nbr->sendns, uip_ds6_if.retrans_timer / 1000);
|
||||
}
|
||||
break;
|
||||
case NBR_DELAY:
|
||||
if(stimer_expired(&nbr->reachable)) {
|
||||
nbr->state = NBR_PROBE;
|
||||
nbr->nscount = 0;
|
||||
PRINTF("DELAY: moving to PROBE\n");
|
||||
stimer_set(&nbr->sendns, 0);
|
||||
}
|
||||
break;
|
||||
case NBR_PROBE:
|
||||
if(nbr->nscount >= UIP_ND6_MAX_UNICAST_SOLICIT) {
|
||||
uip_ds6_defrt_t *locdefrt;
|
||||
PRINTF("PROBE END\n");
|
||||
if((locdefrt = uip_ds6_defrt_lookup(&nbr->ipaddr)) != NULL) {
|
||||
if (!locdefrt->isinfinite) {
|
||||
uip_ds6_defrt_rm(locdefrt);
|
||||
}
|
||||
}
|
||||
uip_ds6_nbr_rm(nbr);
|
||||
} else if(stimer_expired(&nbr->sendns) && (uip_len == 0)) {
|
||||
nbr->nscount++;
|
||||
PRINTF("PROBE: NS %u\n", nbr->nscount);
|
||||
uip_nd6_ns_output(NULL, &nbr->ipaddr, &nbr->ipaddr);
|
||||
stimer_set(&nbr->sendns, uip_ds6_if.retrans_timer / 1000);
|
||||
}
|
||||
break;
|
||||
#endif /* UIP_ND6_SEND_NA */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
nbr = nbr_table_next(ds6_neighbors, nbr);
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_ds6_nbr_t *
|
||||
uip_ds6_get_least_lifetime_neighbor(void)
|
||||
{
|
||||
uip_ds6_nbr_t *nbr = nbr_table_head(ds6_neighbors);
|
||||
uip_ds6_nbr_t *nbr_expiring = NULL;
|
||||
while(nbr != NULL) {
|
||||
if(nbr_expiring != NULL) {
|
||||
clock_time_t curr = stimer_remaining(&nbr->reachable);
|
||||
if(curr < stimer_remaining(&nbr->reachable)) {
|
||||
nbr_expiring = nbr;
|
||||
}
|
||||
} else {
|
||||
nbr_expiring = nbr;
|
||||
}
|
||||
nbr = nbr_table_next(ds6_neighbors, nbr);
|
||||
}
|
||||
return nbr_expiring;
|
||||
}
|
97
core/net/uip-ds6-neighbor.h
Normal file
97
core/net/uip-ds6-neighbor.h
Normal file
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* \addtogroup uip6
|
||||
* @{
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2013, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* IPv6 Neighbor cache (link-layer/IPv6 address mapping)
|
||||
* \author Mathilde Durvy <mdurvy@cisco.com>
|
||||
* \author Julien Abeille <jabeille@cisco.com>
|
||||
* \author Simon Duquennoy <simonduq@sics.se>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __UIP_DS6_NEIGHBOR_H__
|
||||
#define __UIP_DS6_NEIGHBOR_H__
|
||||
|
||||
#include "net/uip-ds6.h"
|
||||
#include "net/neighbor-table.h"
|
||||
|
||||
#if UIP_CONF_IPV6_QUEUE_PKT
|
||||
#include "net/uip-packetqueue.h"
|
||||
#endif /*UIP_CONF_QUEUE_PKT */
|
||||
|
||||
NEIGHBOR_TABLE_DECLARE(ds6_neighbors);
|
||||
|
||||
/** \brief An entry in the nbr cache */
|
||||
typedef struct uip_ds6_nbr {
|
||||
uip_ipaddr_t ipaddr;
|
||||
struct stimer reachable;
|
||||
struct stimer sendns;
|
||||
uint8_t nscount;
|
||||
uint8_t isrouter;
|
||||
uint8_t state;
|
||||
#if UIP_CONF_IPV6_QUEUE_PKT
|
||||
struct uip_packetqueue_handle packethandle;
|
||||
#define UIP_DS6_NBR_PACKET_LIFETIME CLOCK_SECOND * 4
|
||||
#endif /*UIP_CONF_QUEUE_PKT */
|
||||
} uip_ds6_nbr_t;
|
||||
|
||||
void uip_ds6_neighbors_init(void);
|
||||
|
||||
/** \brief Neighbor Cache basic routines */
|
||||
uip_ds6_nbr_t *uip_ds6_nbr_add(uip_ipaddr_t *ipaddr, uip_lladdr_t *lladdr,
|
||||
uint8_t isrouter, uint8_t state);
|
||||
void uip_ds6_nbr_rm(uip_ds6_nbr_t *nbr);
|
||||
uip_lladdr_t *uip_ds6_nbr_get_ll(uip_ds6_nbr_t *nbr);
|
||||
uip_ipaddr_t *uip_ds6_nbr_get_ipaddr(uip_ds6_nbr_t *nbr);
|
||||
uip_ds6_nbr_t *uip_ds6_nbr_lookup(uip_ipaddr_t *ipaddr);
|
||||
uip_ds6_nbr_t *uip_ds6_nbr_ll_lookup(uip_lladdr_t *lladdr);
|
||||
uip_ipaddr_t *uip_ds6_nbr_ipaddr_from_lladdr(uip_lladdr_t *lladdr);
|
||||
uip_lladdr_t *uip_ds6_nbr_lladdr_from_ipaddr(uip_ipaddr_t *ipaddr);
|
||||
void uip_ds6_neighbor_periodic();
|
||||
|
||||
/**
|
||||
* \brief
|
||||
* This searches inside the neighbor table for the neighbor that is about to
|
||||
* expire the next.
|
||||
*
|
||||
* \return
|
||||
* A reference to the neighbor about to expire the next or NULL if
|
||||
* table is empty.
|
||||
*/
|
||||
uip_ds6_nbr_t *uip_ds6_get_least_lifetime_neighbor(void);
|
||||
|
||||
#endif /* __UIP_DS6_NEIGHBOR_H__ */
|
|
@ -53,13 +53,6 @@
|
|||
#define DEBUG DEBUG_NONE
|
||||
#include "net/uip-debug.h"
|
||||
|
||||
#ifdef UIP_CONF_DS6_NEIGHBOR_STATE_CHANGED
|
||||
#define NEIGHBOR_STATE_CHANGED(n) UIP_CONF_DS6_NEIGHBOR_STATE_CHANGED(n)
|
||||
void NEIGHBOR_STATE_CHANGED(uip_ds6_nbr_t *n);
|
||||
#else
|
||||
#define NEIGHBOR_STATE_CHANGED(n)
|
||||
#endif /* UIP_DS6_CONF_NEIGHBOR_STATE_CHANGED */
|
||||
|
||||
struct etimer uip_ds6_timer_periodic; /** \brief Timer for maintenance of data structures */
|
||||
|
||||
#if UIP_CONF_ROUTER
|
||||
|
@ -76,10 +69,7 @@ static uint8_t rscount; /** \brief numbe
|
|||
/** \name "DS6" Data structures */
|
||||
/** @{ */
|
||||
uip_ds6_netif_t uip_ds6_if; /** \brief The single interface */
|
||||
uip_ds6_nbr_t uip_ds6_nbr_cache[UIP_DS6_NBR_NB]; /** \brief Neighor cache */
|
||||
//uip_ds6_defrt_t uip_ds6_defrt_list[UIP_DS6_DEFRT_NB]; /** \brief Default rt list */
|
||||
uip_ds6_prefix_t uip_ds6_prefix_list[UIP_DS6_PREFIX_NB]; /** \brief Prefix list */
|
||||
//uip_ds6_route_t uip_ds6_routing_table[UIP_DS6_ROUTE_NB]; /** \brief Routing table */
|
||||
|
||||
/* Used by Cooja to enable extraction of addresses from memory.*/
|
||||
uint8_t uip_ds6_addr_size;
|
||||
|
@ -95,25 +85,21 @@ static uip_ds6_addr_t *locaddr;
|
|||
static uip_ds6_maddr_t *locmaddr;
|
||||
static uip_ds6_aaddr_t *locaaddr;
|
||||
static uip_ds6_prefix_t *locprefix;
|
||||
static uip_ds6_nbr_t *locnbr;
|
||||
static uip_ds6_defrt_t *locdefrt;
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void
|
||||
uip_ds6_init(void)
|
||||
{
|
||||
|
||||
uip_ds6_neighbors_init();
|
||||
uip_ds6_route_init();
|
||||
|
||||
PRINTF("Init of IPv6 data structures\n");
|
||||
PRINTF("%u neighbors\n%u default routers\n%u prefixes\n%u routes\n%u unicast addresses\n%u multicast addresses\n%u anycast addresses\n",
|
||||
UIP_DS6_NBR_NB, UIP_DS6_DEFRT_NB, UIP_DS6_PREFIX_NB, UIP_DS6_ROUTE_NB,
|
||||
NEIGHBOR_TABLE_MAX_NEIGHBORS, UIP_DS6_DEFRT_NB, UIP_DS6_PREFIX_NB, UIP_DS6_ROUTE_NB,
|
||||
UIP_DS6_ADDR_NB, UIP_DS6_MADDR_NB, UIP_DS6_AADDR_NB);
|
||||
memset(uip_ds6_nbr_cache, 0, sizeof(uip_ds6_nbr_cache));
|
||||
// memset(uip_ds6_defrt_list, 0, sizeof(uip_ds6_defrt_list));
|
||||
memset(uip_ds6_prefix_list, 0, sizeof(uip_ds6_prefix_list));
|
||||
memset(&uip_ds6_if, 0, sizeof(uip_ds6_if));
|
||||
// memset(uip_ds6_routing_table, 0, sizeof(uip_ds6_routing_table));
|
||||
uip_ds6_addr_size = sizeof(struct uip_ds6_addr);
|
||||
uip_ds6_netif_addr_list_offset = offsetof(struct uip_ds6_netif, addr_list);
|
||||
|
||||
|
@ -198,61 +184,7 @@ uip_ds6_periodic(void)
|
|||
}
|
||||
#endif /* !UIP_CONF_ROUTER */
|
||||
|
||||
/* Periodic processing on neighbors */
|
||||
for(locnbr = uip_ds6_nbr_cache;
|
||||
locnbr < uip_ds6_nbr_cache + UIP_DS6_NBR_NB;
|
||||
locnbr++) {
|
||||
if(locnbr->isused) {
|
||||
switch(locnbr->state) {
|
||||
case NBR_REACHABLE:
|
||||
if(stimer_expired(&locnbr->reachable)) {
|
||||
PRINTF("REACHABLE: moving to STALE (");
|
||||
PRINT6ADDR(&locnbr->ipaddr);
|
||||
PRINTF(")\n");
|
||||
locnbr->state = NBR_STALE;
|
||||
}
|
||||
break;
|
||||
#if UIP_ND6_SEND_NA
|
||||
case NBR_INCOMPLETE:
|
||||
if(locnbr->nscount >= UIP_ND6_MAX_MULTICAST_SOLICIT) {
|
||||
uip_ds6_nbr_rm(locnbr);
|
||||
} else if(stimer_expired(&locnbr->sendns) && (uip_len == 0)) {
|
||||
locnbr->nscount++;
|
||||
PRINTF("NBR_INCOMPLETE: NS %u\n", locnbr->nscount);
|
||||
uip_nd6_ns_output(NULL, NULL, &locnbr->ipaddr);
|
||||
stimer_set(&locnbr->sendns, uip_ds6_if.retrans_timer / 1000);
|
||||
}
|
||||
break;
|
||||
case NBR_DELAY:
|
||||
if(stimer_expired(&locnbr->reachable)) {
|
||||
locnbr->state = NBR_PROBE;
|
||||
locnbr->nscount = 0;
|
||||
PRINTF("DELAY: moving to PROBE\n");
|
||||
stimer_set(&locnbr->sendns, 0);
|
||||
}
|
||||
break;
|
||||
case NBR_PROBE:
|
||||
if(locnbr->nscount >= UIP_ND6_MAX_UNICAST_SOLICIT) {
|
||||
PRINTF("PROBE END\n");
|
||||
if((locdefrt = uip_ds6_defrt_lookup(&locnbr->ipaddr)) != NULL) {
|
||||
if (!locdefrt->isinfinite) {
|
||||
uip_ds6_defrt_rm(locdefrt);
|
||||
}
|
||||
}
|
||||
uip_ds6_nbr_rm(locnbr);
|
||||
} else if(stimer_expired(&locnbr->sendns) && (uip_len == 0)) {
|
||||
locnbr->nscount++;
|
||||
PRINTF("PROBE: NS %u\n", locnbr->nscount);
|
||||
uip_nd6_ns_output(NULL, &locnbr->ipaddr, &locnbr->ipaddr);
|
||||
stimer_set(&locnbr->sendns, uip_ds6_if.retrans_timer / 1000);
|
||||
}
|
||||
break;
|
||||
#endif /* UIP_ND6_SEND_NA */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
uip_ds6_neighbor_periodic();
|
||||
|
||||
#if UIP_CONF_ROUTER & UIP_ND6_SEND_RA
|
||||
/* Periodic RA sending */
|
||||
|
@ -291,118 +223,6 @@ uip_ds6_list_loop(uip_ds6_element_t *list, uint8_t size,
|
|||
return *out_element != NULL ? FREESPACE : NOSPACE;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_ds6_nbr_t *
|
||||
uip_ds6_nbr_add(uip_ipaddr_t *ipaddr, uip_lladdr_t *lladdr,
|
||||
uint8_t isrouter, uint8_t state)
|
||||
{
|
||||
int r;
|
||||
|
||||
r = uip_ds6_list_loop
|
||||
((uip_ds6_element_t *)uip_ds6_nbr_cache, UIP_DS6_NBR_NB,
|
||||
sizeof(uip_ds6_nbr_t), ipaddr, 128,
|
||||
(uip_ds6_element_t **)&locnbr);
|
||||
|
||||
if(r == FREESPACE) {
|
||||
locnbr->isused = 1;
|
||||
uip_ipaddr_copy(&locnbr->ipaddr, ipaddr);
|
||||
if(lladdr != NULL) {
|
||||
memcpy(&locnbr->lladdr, lladdr, UIP_LLADDR_LEN);
|
||||
} else {
|
||||
memset(&locnbr->lladdr, 0, UIP_LLADDR_LEN);
|
||||
}
|
||||
locnbr->isrouter = isrouter;
|
||||
locnbr->state = state;
|
||||
#if UIP_CONF_IPV6_QUEUE_PKT
|
||||
uip_packetqueue_new(&locnbr->packethandle);
|
||||
#endif /* UIP_CONF_IPV6_QUEUE_PKT */
|
||||
/* timers are set separately, for now we put them in expired state */
|
||||
stimer_set(&locnbr->reachable, 0);
|
||||
stimer_set(&locnbr->sendns, 0);
|
||||
locnbr->nscount = 0;
|
||||
PRINTF("Adding neighbor with ip addr ");
|
||||
PRINT6ADDR(ipaddr);
|
||||
PRINTF("link addr ");
|
||||
PRINTLLADDR((&(locnbr->lladdr)));
|
||||
PRINTF("state %u\n", state);
|
||||
NEIGHBOR_STATE_CHANGED(locnbr);
|
||||
|
||||
locnbr->last_lookup = clock_time();
|
||||
return locnbr;
|
||||
} else if(r == NOSPACE) {
|
||||
/* We did not find any empty slot on the neighbor list, so we need
|
||||
to remove one old entry to make room. */
|
||||
uip_ds6_nbr_t *n, *oldest;
|
||||
clock_time_t oldest_time;
|
||||
|
||||
oldest = NULL;
|
||||
oldest_time = clock_time();
|
||||
|
||||
for(n = uip_ds6_nbr_cache;
|
||||
n < &uip_ds6_nbr_cache[UIP_DS6_NBR_NB];
|
||||
n++) {
|
||||
if(n->isused && !uip_ds6_defrt_lookup(&n->ipaddr)) {
|
||||
if(n->last_lookup < oldest_time) {
|
||||
oldest = n;
|
||||
oldest_time = n->last_lookup;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(oldest != NULL) {
|
||||
uip_ds6_nbr_rm(oldest);
|
||||
return uip_ds6_nbr_add(ipaddr, lladdr, isrouter, state);
|
||||
}
|
||||
}
|
||||
PRINTF("uip_ds6_nbr_add drop\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void
|
||||
uip_ds6_nbr_rm(uip_ds6_nbr_t *nbr)
|
||||
{
|
||||
if(nbr != NULL) {
|
||||
nbr->isused = 0;
|
||||
#if UIP_CONF_IPV6_QUEUE_PKT
|
||||
uip_packetqueue_free(&nbr->packethandle);
|
||||
#endif /* UIP_CONF_IPV6_QUEUE_PKT */
|
||||
NEIGHBOR_STATE_CHANGED(nbr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_ds6_nbr_t *
|
||||
uip_ds6_nbr_lookup(uip_ipaddr_t *ipaddr)
|
||||
{
|
||||
if(uip_ds6_list_loop
|
||||
((uip_ds6_element_t *)uip_ds6_nbr_cache, UIP_DS6_NBR_NB,
|
||||
sizeof(uip_ds6_nbr_t), ipaddr, 128,
|
||||
(uip_ds6_element_t **)&locnbr) == FOUND) {
|
||||
locnbr->last_lookup = clock_time();
|
||||
return locnbr;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_ds6_nbr_t *
|
||||
uip_ds6_nbr_ll_lookup(uip_lladdr_t *lladdr)
|
||||
{
|
||||
uip_ds6_nbr_t *fin;
|
||||
|
||||
for(locnbr = uip_ds6_nbr_cache, fin = locnbr + UIP_DS6_NBR_NB;
|
||||
locnbr < fin;
|
||||
++locnbr) {
|
||||
if(locnbr->isused) {
|
||||
if(!memcmp(lladdr, &locnbr->lladdr, UIP_LLADDR_LEN)) {
|
||||
return locnbr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
#if UIP_CONF_ROUTER
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
@ -878,25 +698,5 @@ uip_ds6_compute_reachable_time(void)
|
|||
UIP_ND6_MIN_RANDOM_FACTOR(uip_ds6_if.base_reachable_time));
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uip_ds6_nbr_t *
|
||||
uip_ds6_get_least_lifetime_neighbor(void)
|
||||
{
|
||||
uip_ds6_nbr_t *nbr_expiring = NULL;
|
||||
uint8_t i;
|
||||
for(i = 0; i < UIP_DS6_NBR_NB; i++) {
|
||||
if(uip_ds6_nbr_cache[i].isused) {
|
||||
if(nbr_expiring != NULL) {
|
||||
clock_time_t curr = stimer_remaining(&uip_ds6_nbr_cache[i].reachable);
|
||||
if(curr < stimer_remaining(&nbr_expiring->reachable)) {
|
||||
nbr_expiring = &uip_ds6_nbr_cache[i];
|
||||
}
|
||||
} else {
|
||||
nbr_expiring = &uip_ds6_nbr_cache[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return nbr_expiring;
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/** @} */
|
||||
#endif /* UIP_CONF_IPV6 */
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
/* The size of uip_ds6_addr_t depends on UIP_ND6_DEF_MAXDADNS. Include uip-nd6.h to define it. */
|
||||
#include "net/uip-nd6.h"
|
||||
#include "net/uip-ds6-route.h"
|
||||
#include "net/uip-ds6-neighbor.h"
|
||||
|
||||
/*--------------------------------------------------*/
|
||||
/** Configuration. For all tables (Neighbor cache, Prefix List, Routing Table,
|
||||
|
@ -56,14 +57,6 @@
|
|||
* - the number of elements assigned by the system (name suffixed by _NBS)
|
||||
* - the total number of elements is the sum (name suffixed by _NB)
|
||||
*/
|
||||
/* Neighbor cache */
|
||||
#define UIP_DS6_NBR_NBS 0
|
||||
#ifndef UIP_CONF_DS6_NBR_NBU
|
||||
#define UIP_DS6_NBR_NBU 4
|
||||
#else
|
||||
#define UIP_DS6_NBR_NBU UIP_CONF_DS6_NBR_NBU
|
||||
#endif
|
||||
#define UIP_DS6_NBR_NB UIP_DS6_NBR_NBS + UIP_DS6_NBR_NBU
|
||||
|
||||
/* Default router list */
|
||||
#define UIP_DS6_DEFRT_NBS 0
|
||||
|
@ -155,22 +148,6 @@
|
|||
#if UIP_CONF_IPV6_QUEUE_PKT
|
||||
#include "net/uip-packetqueue.h"
|
||||
#endif /*UIP_CONF_QUEUE_PKT */
|
||||
/** \brief An entry in the nbr cache */
|
||||
typedef struct uip_ds6_nbr {
|
||||
uint8_t isused;
|
||||
uip_ipaddr_t ipaddr;
|
||||
uip_lladdr_t lladdr;
|
||||
struct stimer reachable;
|
||||
struct stimer sendns;
|
||||
clock_time_t last_lookup;
|
||||
uint8_t nscount;
|
||||
uint8_t isrouter;
|
||||
uint8_t state;
|
||||
#if UIP_CONF_IPV6_QUEUE_PKT
|
||||
struct uip_packetqueue_handle packethandle;
|
||||
#define UIP_DS6_NBR_PACKET_LIFETIME CLOCK_SECOND * 4
|
||||
#endif /*UIP_CONF_QUEUE_PKT */
|
||||
} uip_ds6_nbr_t;
|
||||
|
||||
/** \brief A prefix list entry */
|
||||
#if UIP_CONF_ROUTER
|
||||
|
@ -226,8 +203,6 @@ typedef struct uip_ds6_maddr {
|
|||
#endif /* UIP_CONF_DS6_NEIGHBOR_STATE_CHANGED */
|
||||
#endif /* UIP_CONF_IPV6_RPL */
|
||||
|
||||
|
||||
|
||||
/** \brief Interface structure (contains all the interface variables) */
|
||||
typedef struct uip_ds6_netif {
|
||||
uint32_t link_mtu;
|
||||
|
@ -273,14 +248,6 @@ uint8_t uip_ds6_list_loop(uip_ds6_element_t *list, uint8_t size,
|
|||
uint8_t ipaddrlen,
|
||||
uip_ds6_element_t **out_element);
|
||||
|
||||
/** \name Neighbor Cache basic routines */
|
||||
/** @{ */
|
||||
uip_ds6_nbr_t *uip_ds6_nbr_add(uip_ipaddr_t *ipaddr, uip_lladdr_t *lladdr,
|
||||
uint8_t isrouter, uint8_t state);
|
||||
void uip_ds6_nbr_rm(uip_ds6_nbr_t *nbr);
|
||||
uip_ds6_nbr_t *uip_ds6_nbr_lookup(uip_ipaddr_t *ipaddr);
|
||||
uip_ds6_nbr_t *uip_ds6_nbr_ll_lookup(uip_lladdr_t *lladdr);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
||||
|
@ -371,15 +338,4 @@ uint32_t uip_ds6_compute_reachable_time(void); /** \brief compute random reachab
|
|||
/** @} */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* \brief
|
||||
* This searches inside the neighbor table for the neighbor that is about to
|
||||
* expire the next.
|
||||
*
|
||||
* \return
|
||||
* A reference to the neighbor about to expire the next or NULL if
|
||||
* table is empty.
|
||||
*/
|
||||
uip_ds6_nbr_t *uip_ds6_get_least_lifetime_neighbor(void);
|
||||
|
||||
#endif /* __UIP_DS6_H__ */
|
||||
|
|
|
@ -191,9 +191,10 @@ uip_nd6_ns_input(void)
|
|||
(uip_lladdr_t *)&nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
|
||||
0, NBR_STALE);
|
||||
} else {
|
||||
uip_lladdr_t *lladdr = uip_ds6_nbr_get_ll(nbr);
|
||||
if(memcmp(&nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
|
||||
&nbr->lladdr, UIP_LLADDR_LEN) != 0) {
|
||||
memcpy(&nbr->lladdr, &nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
|
||||
lladdr, UIP_LLADDR_LEN) != 0) {
|
||||
memcpy(lladdr, &nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
|
||||
UIP_LLADDR_LEN);
|
||||
nbr->state = NBR_STALE;
|
||||
} else {
|
||||
|
@ -460,20 +461,22 @@ uip_nd6_na_input(void)
|
|||
PRINTF("NA received is bad\n");
|
||||
goto discard;
|
||||
} else {
|
||||
uip_lladdr_t *lladdr;
|
||||
nbr = uip_ds6_nbr_lookup(&UIP_ND6_NA_BUF->tgtipaddr);
|
||||
lladdr = uip_ds6_nbr_get_ll(nbr);
|
||||
if(nbr == NULL) {
|
||||
goto discard;
|
||||
}
|
||||
if(nd6_opt_llao != 0) {
|
||||
is_llchange =
|
||||
memcmp(&nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET], (void *)(&nbr->lladdr),
|
||||
memcmp(&nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET], (void *)lladdr,
|
||||
UIP_LLADDR_LEN);
|
||||
}
|
||||
if(nbr->state == NBR_INCOMPLETE) {
|
||||
if(nd6_opt_llao == NULL) {
|
||||
goto discard;
|
||||
}
|
||||
memcpy(&nbr->lladdr, &nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
|
||||
memcpy(lladdr, &nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
|
||||
UIP_LLADDR_LEN);
|
||||
if(is_solicited) {
|
||||
nbr->state = NBR_REACHABLE;
|
||||
|
@ -496,7 +499,7 @@ uip_nd6_na_input(void)
|
|||
if(is_override || (!is_override && nd6_opt_llao != 0 && !is_llchange)
|
||||
|| nd6_opt_llao == 0) {
|
||||
if(nd6_opt_llao != 0) {
|
||||
memcpy(&nbr->lladdr, &nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
|
||||
memcpy(lladdr, &nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
|
||||
UIP_LLADDR_LEN);
|
||||
}
|
||||
if(is_solicited) {
|
||||
|
@ -583,7 +586,7 @@ uip_nd6_rs_input(void)
|
|||
#endif /*UIP_CONF_IPV6_CHECKS */
|
||||
switch (UIP_ND6_OPT_HDR_BUF->type) {
|
||||
case UIP_ND6_OPT_SLLAO:
|
||||
nd6_opt_llao = UIP_ND6_OPT_HDR_BUF;
|
||||
nd6_opt_llao = (uint8_t *)UIP_ND6_OPT_HDR_BUF;
|
||||
break;
|
||||
default:
|
||||
PRINTF("ND option not supported in RS\n");
|
||||
|
@ -602,14 +605,18 @@ uip_nd6_rs_input(void)
|
|||
if((nbr = uip_ds6_nbr_lookup(&UIP_IP_BUF->srcipaddr)) == NULL) {
|
||||
/* we need to add the neighbor */
|
||||
uip_ds6_nbr_add(&UIP_IP_BUF->srcipaddr,
|
||||
&nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET], 0, NBR_STALE);
|
||||
(uip_lladdr_t *)&nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET], 0, NBR_STALE);
|
||||
} else {
|
||||
/* If LL address changed, set neighbor state to stale */
|
||||
if(memcmp(&nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
|
||||
&nbr->lladdr, UIP_LLADDR_LEN) != 0) {
|
||||
memcpy(&nbr->lladdr, &nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET],
|
||||
UIP_LLADDR_LEN);
|
||||
nbr->state = NBR_STALE;
|
||||
uip_ds6_nbr_get_ll(nbr), UIP_LLADDR_LEN) != 0) {
|
||||
uip_ds6_nbr_t nbr_data = *nbr;
|
||||
uip_ds6_nbr_rm(nbr);
|
||||
nbr = uip_ds6_nbr_add(&UIP_IP_BUF->srcipaddr,
|
||||
(uip_lladdr_t *)&nd6_opt_llao[UIP_ND6_OPT_DATA_OFFSET], 0, NBR_STALE);
|
||||
nbr->reachable = nbr_data.reachable;
|
||||
nbr->sendns = nbr_data.sendns;
|
||||
nbr->nscount = nbr_data.nscount;
|
||||
}
|
||||
nbr->isrouter = 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue