diff --git a/core/net/ipv6/sicslowpan.c b/core/net/ipv6/sicslowpan.c index ebbd8768e..8c174b228 100644 --- a/core/net/ipv6/sicslowpan.c +++ b/core/net/ipv6/sicslowpan.c @@ -62,6 +62,7 @@ #include "contiki.h" #include "dev/watchdog.h" +#include "net/link-stats.h" #include "net/ip/tcpip.h" #include "net/ip/uip.h" #include "net/ipv6/uip-ds6.h" @@ -1516,6 +1517,9 @@ input(void) uint8_t first_fragment = 0, last_fragment = 0; #endif /*SICSLOWPAN_CONF_FRAG*/ + /* Update link statistics */ + link_stats_input_callback(packetbuf_addr(PACKETBUF_ADDR_SENDER)); + /* init */ uncomp_hdr_len = 0; packetbuf_hdr_len = 0; diff --git a/core/net/ipv6/uip-ds6-nbr.c b/core/net/ipv6/uip-ds6-nbr.c index e5c6167e5..3ccebddd1 100644 --- a/core/net/ipv6/uip-ds6-nbr.c +++ b/core/net/ipv6/uip-ds6-nbr.c @@ -47,6 +47,7 @@ #include #include #include "lib/list.h" +#include "net/link-stats.h" #include "net/linkaddr.h" #include "net/packetbuf.h" #include "net/ipv6/uip-ds6-nbr.h" @@ -74,6 +75,7 @@ NBR_TABLE_GLOBAL(uip_ds6_nbr_t, ds6_neighbors); void uip_ds6_neighbors_init(void) { + link_stats_init(); nbr_table_register(ds6_neighbors, (nbr_table_callback *)uip_ds6_nbr_rm); } /*---------------------------------------------------------------------------*/ @@ -204,6 +206,9 @@ uip_ds6_link_neighbor_callback(int status, int numtx) return; } + /* Update neighbor link statistics */ + link_stats_packet_sent(dest, status, numtx); + /* Call upper-layer callback (e.g. RPL) */ LINK_NEIGHBOR_CALLBACK(dest, status, numtx); #if UIP_DS6_LL_NUD diff --git a/core/net/ipv6/uip-ds6-nbr.h b/core/net/ipv6/uip-ds6-nbr.h index 2f3a51d28..40a17d1e2 100644 --- a/core/net/ipv6/uip-ds6-nbr.h +++ b/core/net/ipv6/uip-ds6-nbr.h @@ -71,7 +71,6 @@ typedef struct uip_ds6_nbr { uip_ipaddr_t ipaddr; uint8_t isrouter; uint8_t state; - uint16_t link_metric; #if UIP_ND6_SEND_NA || UIP_ND6_SEND_RA struct stimer reachable; struct stimer sendns; diff --git a/core/net/link-stats.c b/core/net/link-stats.c new file mode 100644 index 000000000..a2f99e729 --- /dev/null +++ b/core/net/link-stats.c @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2015, SICS Swedish ICT. + * 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. + * + * + * Authors: Simon Duquennoy + */ + +#include "contiki.h" +#include "sys/clock.h" +#include "net/packetbuf.h" +#include "net/nbr-table.h" +#include "net/link-stats.h" +#include + +#define DEBUG 0 +#if DEBUG +#define PRINTF(...) printf(__VA_ARGS__) +#else +#define PRINTF(...) +#endif + +/* Half time for the freshness counter, in minutes */ +#define FRESHNESS_HALF_LIFE 20 +/* Statistics are fresh if the freshness counter is FRESHNESS_TARGET or more */ +#define FRESHNESS_TARGET 4 +/* Maximum value for the freshness counter */ +#define FRESHNESS_MAX 16 +/* Statistics with no update in FRESHNESS_EXPIRATION_TIMEOUT is not fresh */ +#define FRESHNESS_EXPIRATION_TIME (10 * 60 * CLOCK_SECOND) + +/* EWMA (exponential moving average) used to maintain statistics over time */ +#define EWMA_SCALE 100 +#define EWMA_ALPHA 15 +#define EWMA_BOOTSTRAP_ALPHA 30 + +/* ETX fixed point divisor. 128 is the value used by RPL (RFC 6551 and RFC 6719) */ +#define ETX_DIVISOR LINK_STATS_ETX_DIVISOR +/* Number of Tx used to update the ETX EWMA in case of no-ACK */ +#define ETX_NOACK_PENALTY 10 +/* Initial ETX value */ +#define ETX_INIT 2 + +/* Per-neighbor link statistics table */ +NBR_TABLE(struct link_stats, link_stats); + +/* Called every FRESHNESS_HALF_LIFE minutes */ +struct ctimer periodic_timer; + +/* Used to initialize ETX before any transmission occurs. In order to + * infer the initial ETX from the RSSI of previously received packets, use: */ +/* #define LINK_STATS_CONF_INIT_ETX(stats) guess_etx_from_rssi(stats) */ + +#ifdef LINK_STATS_CONF_INIT_ETX +#define LINK_STATS_INIT_ETX(stats) LINK_STATS_CONF_INIT_ETX(stats) +#else /* LINK_STATS_INIT_ETX */ +#define LINK_STATS_INIT_ETX(stats) (ETX_INIT * ETX_DIVISOR) +#endif /* LINK_STATS_INIT_ETX */ + +/*---------------------------------------------------------------------------*/ +/* Returns the neighbor's link stats */ +const struct link_stats * +link_stats_from_lladdr(const linkaddr_t *lladdr) +{ + return nbr_table_get_from_lladdr(link_stats, lladdr); +} +/*---------------------------------------------------------------------------*/ +/* Are the statistics fresh? */ +int +link_stats_is_fresh(const struct link_stats *stats) +{ + return (stats != NULL) + && clock_time() - stats->last_tx_time < FRESHNESS_EXPIRATION_TIME + && stats->freshness >= FRESHNESS_TARGET; +} +/*---------------------------------------------------------------------------*/ +uint16_t +guess_etx_from_rssi(const struct link_stats *stats) +{ + if(stats != NULL) { + if(stats->rssi == 0) { + return ETX_INIT * ETX_DIVISOR; + } else { + /* A rough estimate of PRR from RSSI, as a linear function where: + * RSSI >= -60 results in PRR of 1 + * RSSI <= -90 results in PRR of 0 + * prr = (bounded_rssi - RSSI_LOW) / (RSSI_DIFF) + * etx = ETX_DIVOSOR / ((bounded_rssi - RSSI_LOW) / RSSI_DIFF) + * etx = (RSSI_DIFF * ETX_DIVOSOR) / (bounded_rssi - RSSI_LOW) + * */ +#define ETX_INIT_MAX 3 +#define RSSI_HIGH -60 +#define RSSI_LOW -90 +#define RSSI_DIFF (RSSI_HIGH - RSSI_LOW) + uint16_t etx; + int16_t bounded_rssi = stats->rssi; + bounded_rssi = MIN(bounded_rssi, RSSI_HIGH); + bounded_rssi = MAX(bounded_rssi, RSSI_LOW + 1); + etx = RSSI_DIFF * ETX_DIVISOR / (bounded_rssi - RSSI_LOW); + return MIN(etx, ETX_INIT_MAX * ETX_DIVISOR); + } + } + return 0xffff; +} +/*---------------------------------------------------------------------------*/ +/* Packet sent callback. Updates stats for transmissions to lladdr */ +void +link_stats_packet_sent(const linkaddr_t *lladdr, int status, int numtx) +{ + struct link_stats *stats; + uint16_t packet_etx; + uint8_t ewma_alpha; + + if(status != MAC_TX_OK && status != MAC_TX_NOACK) { + /* Do not penalize the ETX when collisions or transmission errors occur. */ + return; + } + + stats = nbr_table_get_from_lladdr(link_stats, lladdr); + if(stats == NULL) { + /* Add the neighbor */ + stats = nbr_table_add_lladdr(link_stats, lladdr, NBR_TABLE_REASON_LINK_STATS, NULL); + if(stats != NULL) { + stats->etx = LINK_STATS_INIT_ETX(stats); + } else { + return; /* No space left, return */ + } + } + + /* Update last timestamp and freshness */ + stats->last_tx_time = clock_time(); + stats->freshness = MIN(stats->freshness + numtx, FRESHNESS_MAX); + + /* ETX used for this update */ + packet_etx = ((status == MAC_TX_NOACK) ? ETX_NOACK_PENALTY : numtx) * ETX_DIVISOR; + /* ETX alpha used for this update */ + ewma_alpha = link_stats_is_fresh(stats) ? EWMA_ALPHA : EWMA_BOOTSTRAP_ALPHA; + + /* Compute EWMA and update ETX */ + stats->etx = ((uint32_t)stats->etx * (EWMA_SCALE - ewma_alpha) + + (uint32_t)packet_etx * ewma_alpha) / EWMA_SCALE; +} +/*---------------------------------------------------------------------------*/ +/* Packet input callback. Updates statistics for receptions on a given link */ +void +link_stats_input_callback(const linkaddr_t *lladdr) +{ + struct link_stats *stats; + int16_t packet_rssi = packetbuf_attr(PACKETBUF_ATTR_RSSI); + + stats = nbr_table_get_from_lladdr(link_stats, lladdr); + if(stats == NULL) { + /* Add the neighbor */ + stats = nbr_table_add_lladdr(link_stats, lladdr, NBR_TABLE_REASON_LINK_STATS, NULL); + if(stats != NULL) { + /* Initialize */ + stats->rssi = packet_rssi; + stats->etx = LINK_STATS_INIT_ETX(stats); + } + return; + } + + /* Update RSSI EWMA */ + stats->rssi = ((int32_t)stats->rssi * (EWMA_SCALE - EWMA_ALPHA) + + (int32_t)packet_rssi * EWMA_ALPHA) / EWMA_SCALE; +} +/*---------------------------------------------------------------------------*/ +/* Periodic timer called every FRESHNESS_HALF_LIFE minutes */ +static void +periodic(void *ptr) +{ + /* Age (by halving) freshness counter of all neighbors */ + struct link_stats *stats; + ctimer_reset(&periodic_timer); + for(stats = nbr_table_head(link_stats); stats != NULL; stats = nbr_table_next(link_stats, stats)) { + stats->freshness >>= 1; + } +} +/*---------------------------------------------------------------------------*/ +/* Initializes link-stats module */ +void +link_stats_init(void) +{ + nbr_table_register(link_stats, NULL); + ctimer_set(&periodic_timer, 60 * CLOCK_SECOND * FRESHNESS_HALF_LIFE, + periodic, NULL); +} diff --git a/core/net/link-stats.h b/core/net/link-stats.h new file mode 100644 index 000000000..d28a1c1a4 --- /dev/null +++ b/core/net/link-stats.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015, SICS Swedish ICT. + * 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. + * + * + * Authors: Simon Duquennoy + */ + +#ifndef LINK_STATS_H_ +#define LINK_STATS_H_ + +#include "core/net/linkaddr.h" + +/* ETX fixed point divisor. 128 is the value used by RPL (RFC 6551 and RFC 6719) */ +#ifdef LINK_STATS_CONF_ETX_DIVISOR +#define LINK_STATS_ETX_DIVISOR LINK_STATS_CONF_ETX_DIVISOR +#else /* LINK_STATS_CONF_ETX_DIVISOR */ +#define LINK_STATS_ETX_DIVISOR 128 +#endif /* LINK_STATS_CONF_ETX_DIVISOR */ + +/* All statistics of a given link */ +struct link_stats { + uint16_t etx; /* ETX using ETX_DIVISOR as fixed point divisor */ + int16_t rssi; /* RSSI (received signal strength) */ + uint8_t freshness; /* Freshness of the statistics */ + clock_time_t last_tx_time; /* Last Tx timestamp */ +}; + +/* Returns the neighbor's link statistics */ +const struct link_stats *link_stats_from_lladdr(const linkaddr_t *lladdr); +/* Are the statistics fresh? */ +int link_stats_is_fresh(const struct link_stats *stats); + +/* Initializes link-stats module */ +void link_stats_init(void); +/* Packet sent callback. Updates statistics for transmissions on a given link */ +void link_stats_packet_sent(const linkaddr_t *lladdr, int status, int numtx); +/* Packet input callback. Updates statistics for receptions on a given link */ +void link_stats_input_callback(const linkaddr_t *lladdr); + +#endif /* LINK_STATS_H_ */ diff --git a/core/net/nbr-table.h b/core/net/nbr-table.h index c797a44ec..320ef2e5f 100644 --- a/core/net/nbr-table.h +++ b/core/net/nbr-table.h @@ -83,7 +83,8 @@ typedef enum { NBR_TABLE_REASON_ROUTE, NBR_TABLE_REASON_IPV6_ND, NBR_TABLE_REASON_MAC, - NBR_TABLE_REASON_LLSEC + NBR_TABLE_REASON_LLSEC, + NBR_TABLE_REASON_LINK_STATS, } nbr_table_reason_t; /** \name Neighbor tables: register and loop through table elements */ diff --git a/core/net/rpl/rpl-conf.h b/core/net/rpl/rpl-conf.h index 073186071..1da6c5ea8 100644 --- a/core/net/rpl/rpl-conf.h +++ b/core/net/rpl/rpl-conf.h @@ -45,31 +45,52 @@ #define RPL_CONF_STATS 0 #endif /* RPL_CONF_STATS */ -/* - * Select routing metric supported at runtime. This must be a valid - * DAG Metric Container Object Type (see below). Currently, we only - * support RPL_DAG_MC_ETX and RPL_DAG_MC_ENERGY. - * When MRHOF (RFC6719) is used with ETX, no metric container must - * be used; instead the rank carries ETX directly. +/* + * The objective function (OF) used by a RPL root is configurable through + * the RPL_CONF_OF_OCP parameter. This is defined as the objective code + * point (OCP) of the OF, RPL_OCP_OF0 or RPL_OCP_MRHOF. This flag is of + * no relevance to non-root nodes, which run the OF advertised in the + * instance they join. + * Make sure the selected of is inRPL_SUPPORTED_OFS. */ +#ifdef RPL_CONF_OF_OCP +#define RPL_OF_OCP RPL_CONF_OF_OCP +#else /* RPL_CONF_OF_OCP */ +#define RPL_OF_OCP RPL_OCP_MRHOF +#endif /* RPL_CONF_OF_OCP */ + +/* + * The set of objective functions supported at runtime. Nodes are only + * able to join instances that advertise an OF in this set. To include + * both OF0 and MRHOF, use {&rpl_of0, &rpl_mrhof}. + */ +#ifdef RPL_CONF_SUPPORTED_OFS +#define RPL_SUPPORTED_OFS RPL_CONF_SUPPORTED_OFS +#else /* RPL_CONF_SUPPORTED_OFS */ +#define RPL_SUPPORTED_OFS {&rpl_mrhof} +#endif /* RPL_CONF_SUPPORTED_OFS */ + +/* + * Enable/disable RPL Metric Containers (MC). The actual MC in use + * for a given DODAG is decided at runtime, when joining. Note that + * OF0 (RFC6552) operates without MC, and so does MRHOF (RFC6719) when + * used with ETX as a metric (the rank is the metric). We disable MC + * by default, but note it must be enabled to support joining a DODAG + * that requires MC (e.g., MRHOF with a metric other than ETX). + */ +#ifdef RPL_CONF_WITH_MC +#define RPL_WITH_MC RPL_CONF_WITH_MC +#else /* RPL_CONF_WITH_MC */ +#define RPL_WITH_MC 0 +#endif /* RPL_CONF_WITH_MC */ + +/* The MC advertised in DIOs and propagating from the root */ #ifdef RPL_CONF_DAG_MC #define RPL_DAG_MC RPL_CONF_DAG_MC #else #define RPL_DAG_MC RPL_DAG_MC_NONE #endif /* RPL_CONF_DAG_MC */ -/* - * The objective function used by RPL is configurable through the - * RPL_CONF_OF parameter. This should be defined to be the name of an - * rpl_of object linked into the system image, e.g., rpl_of0. - */ -#ifdef RPL_CONF_OF -#define RPL_OF RPL_CONF_OF -#else -/* ETX is the default objective function. */ -#define RPL_OF rpl_mrhof -#endif /* RPL_CONF_OF */ - /* This value decides which DAG instance we should participate in by default. */ #ifdef RPL_CONF_DEFAULT_INSTANCE #define RPL_DEFAULT_INSTANCE RPL_CONF_DEFAULT_INSTANCE @@ -186,15 +207,6 @@ #define RPL_DIO_REDUNDANCY 10 #endif -/* - * Initial metric attributed to a link when the ETX is unknown - */ -#ifndef RPL_CONF_INIT_LINK_METRIC -#define RPL_INIT_LINK_METRIC 2 -#else -#define RPL_INIT_LINK_METRIC RPL_CONF_INIT_LINK_METRIC -#endif - /* * Default route lifetime unit. This is the granularity of time * used in RPL lifetime values, in seconds. @@ -287,22 +299,13 @@ #define RPL_PROBING_INTERVAL (120 * CLOCK_SECOND) #endif -/* - * RPL probing expiration time. - */ -#ifdef RPL_CONF_PROBING_EXPIRATION_TIME -#define RPL_PROBING_EXPIRATION_TIME RPL_CONF_PROBING_EXPIRATION_TIME -#else -#define RPL_PROBING_EXPIRATION_TIME (10 * 60 * CLOCK_SECOND) -#endif - /* * Function used to select the next parent to be probed. */ #ifdef RPL_CONF_PROBING_SELECT_FUNC #define RPL_PROBING_SELECT_FUNC RPL_CONF_PROBING_SELECT_FUNC #else -#define RPL_PROBING_SELECT_FUNC(dag) get_probing_target((dag)) +#define RPL_PROBING_SELECT_FUNC get_probing_target #endif /* @@ -325,8 +328,7 @@ #ifdef RPL_CONF_PROBING_DELAY_FUNC #define RPL_PROBING_DELAY_FUNC RPL_CONF_PROBING_DELAY_FUNC #else -#define RPL_PROBING_DELAY_FUNC() ((RPL_PROBING_INTERVAL / 2) \ - + random_rand() % (RPL_PROBING_INTERVAL)) +#define RPL_PROBING_DELAY_FUNC get_probing_delay #endif /* diff --git a/core/net/rpl/rpl-dag.c b/core/net/rpl/rpl-dag.c index 27f781bdf..c4108fd5a 100644 --- a/core/net/rpl/rpl-dag.c +++ b/core/net/rpl/rpl-dag.c @@ -44,6 +44,7 @@ */ #include "contiki.h" +#include "net/link-stats.h" #include "net/rpl/rpl-private.h" #include "net/ip/uip.h" #include "net/ipv6/uip-nd6.h" @@ -66,8 +67,8 @@ void RPL_CALLBACK_PARENT_SWITCH(rpl_parent_t *old, rpl_parent_t *new); #endif /* RPL_CALLBACK_PARENT_SWITCH */ /*---------------------------------------------------------------------------*/ -extern rpl_of_t RPL_OF; -static rpl_of_t * const objective_functions[] = {&RPL_OF}; +extern rpl_of_t rpl_of0, rpl_mrhof; +static rpl_of_t * const objective_functions[] = RPL_SUPPORTED_OFS; /*---------------------------------------------------------------------------*/ /* RPL definitions. */ @@ -91,22 +92,26 @@ void rpl_print_neighbor_list(void) { if(default_instance != NULL && default_instance->current_dag != NULL && - default_instance->of != NULL && default_instance->of->calculate_rank != NULL) { + default_instance->of != NULL) { int curr_dio_interval = default_instance->dio_intcurrent; int curr_rank = default_instance->current_dag->rank; rpl_parent_t *p = nbr_table_head(rpl_parents); - clock_time_t now = clock_time(); + clock_time_t clock_now = clock_time(); - printf("RPL: rank %u dioint %u, %u nbr(s)\n", curr_rank, curr_dio_interval, uip_ds6_nbr_num()); + printf("RPL: OCP %u rank %u dioint %u, nbr count %u\n", + default_instance->of->ocp, curr_rank, curr_dio_interval, uip_ds6_nbr_num()); while(p != NULL) { - uip_ds6_nbr_t *nbr = rpl_get_nbr(p); - printf("RPL: nbr %3u %5u, %5u => %5u %c%c (last tx %u min ago)\n", - nbr_table_get_lladdr(rpl_parents, p)->u8[7], - p->rank, nbr ? nbr->link_metric : 0, - default_instance->of->calculate_rank(p, 0), - default_instance->current_dag == p->dag ? 'd' : ' ', - p == default_instance->current_dag->preferred_parent ? '*' : ' ', - (unsigned)((now - p->last_tx_time) / (60 * CLOCK_SECOND))); + const struct link_stats *stats = rpl_get_parent_link_stats(p); + printf("RPL: nbr %3u %5u, %5u => %5u -- %2u %c%c (last tx %u min ago)\n", + rpl_get_parent_ipaddr(p)->u8[15], + p->rank, + rpl_get_parent_link_metric(p), + rpl_rank_via_parent(p), + stats != NULL ? stats->freshness : 0, + link_stats_is_fresh(stats) ? 'f' : ' ', + p == default_instance->current_dag->preferred_parent ? 'p' : ' ', + (unsigned)((clock_now - stats->last_tx_time) / (60 * CLOCK_SECOND)) + ); p = nbr_table_next(rpl_parents, p); } printf("RPL: end of list\n"); @@ -116,8 +121,7 @@ rpl_print_neighbor_list(void) uip_ds6_nbr_t * rpl_get_nbr(rpl_parent_t *parent) { - linkaddr_t *lladdr = NULL; - lladdr = nbr_table_get_lladdr(rpl_parents, parent); + const linkaddr_t *lladdr = rpl_get_parent_lladdr(parent); if(lladdr != NULL) { return nbr_table_get_from_lladdr(ds6_neighbors, lladdr); } else { @@ -151,30 +155,78 @@ rpl_get_parent_rank(uip_lladdr_t *addr) if(p != NULL) { return p->rank; } else { - return 0; + return INFINITE_RANK; } } /*---------------------------------------------------------------------------*/ uint16_t -rpl_get_parent_link_metric(const uip_lladdr_t *addr) +rpl_get_parent_link_metric(rpl_parent_t *p) { - uip_ds6_nbr_t *nbr; - nbr = nbr_table_get_from_lladdr(ds6_neighbors, (const linkaddr_t *)addr); - - if(nbr != NULL) { - return nbr->link_metric; - } else { - return 0; + if(p != NULL && p->dag != NULL) { + rpl_instance_t *instance = p->dag->instance; + if(instance != NULL && instance->of != NULL && instance->of->parent_link_metric != NULL) { + return instance->of->parent_link_metric(p); + } } + return 0xffff; +} +/*---------------------------------------------------------------------------*/ +rpl_rank_t +rpl_rank_via_parent(rpl_parent_t *p) +{ + if(p != NULL && p->dag != NULL) { + rpl_instance_t *instance = p->dag->instance; + if(instance != NULL && instance->of != NULL && instance->of->rank_via_parent != NULL) { + return instance->of->rank_via_parent(p); + } + } + return INFINITE_RANK; +} +/*---------------------------------------------------------------------------*/ +const linkaddr_t * +rpl_get_parent_lladdr(rpl_parent_t *p) +{ + return nbr_table_get_lladdr(rpl_parents, p); } /*---------------------------------------------------------------------------*/ uip_ipaddr_t * rpl_get_parent_ipaddr(rpl_parent_t *p) { - linkaddr_t *lladdr = nbr_table_get_lladdr(rpl_parents, p); + const linkaddr_t *lladdr = rpl_get_parent_lladdr(p); return uip_ds6_nbr_ipaddr_from_lladdr((uip_lladdr_t *)lladdr); } /*---------------------------------------------------------------------------*/ +const struct link_stats * +rpl_get_parent_link_stats(rpl_parent_t *p) +{ + const linkaddr_t *lladdr = rpl_get_parent_lladdr(p); + return link_stats_from_lladdr(lladdr); +} +/*---------------------------------------------------------------------------*/ +int +rpl_parent_is_fresh(rpl_parent_t *p) +{ + const struct link_stats *stats = rpl_get_parent_link_stats(p); + return link_stats_is_fresh(stats); +} +/*---------------------------------------------------------------------------*/ +int +rpl_parent_is_reachable(rpl_parent_t *p) { + if(p == NULL || p->dag == NULL || p->dag->instance == NULL || p->dag->instance->of == NULL) { + return 0; + } else { +#ifndef UIP_CONF_ND6_SEND_NA + uip_ds6_nbr_t *nbr = rpl_get_nbr(p); + /* Exclude links to a neighbor that is not reachable at a NUD level */ + if(nbr == NULL || nbr->state != NBR_REACHABLE) { + return 0; + } +#endif /* UIP_CONF_ND6_SEND_NA */ + /* If we don't have fresh link information, assume the parent is reachable. */ + return !rpl_parent_is_fresh(p) || p->dag->instance->of->parent_has_usable_link(p); + } +} +/*---------------------------------------------------------------------------*/ static void rpl_set_preferred_parent(rpl_dag_t *dag, rpl_parent_t *p) { @@ -340,7 +392,12 @@ rpl_set_root(uint8_t instance_id, uip_ipaddr_t *dag_id) dag->grounded = RPL_GROUNDED; dag->preference = RPL_PREFERENCE; instance->mop = RPL_MOP_DEFAULT; - instance->of = &RPL_OF; + instance->of = rpl_find_of(RPL_OF_OCP); + if(instance->of == NULL) { + PRINTF("RPL: OF with OCP %u not supported\n", RPL_OF_OCP); + return NULL; + } + rpl_set_preferred_parent(dag, NULL); memcpy(&dag->dag_id, dag_id, sizeof(dag->dag_id)); @@ -640,20 +697,12 @@ rpl_add_parent(rpl_dag_t *dag, rpl_dio_t *dio, uip_ipaddr_t *addr) if(p == NULL) { PRINTF("RPL: rpl_add_parent p NULL\n"); } else { - uip_ds6_nbr_t *nbr; - nbr = rpl_get_nbr(p); - p->dag = dag; p->rank = dio->rank; p->dtsn = dio->dtsn; - - /* Check whether we have a neighbor that has not gotten a link metric yet */ - if(nbr != NULL && nbr->link_metric == 0) { - nbr->link_metric = RPL_INIT_LINK_METRIC * RPL_DAG_MC_ETX_DIVISOR; - } -#if RPL_DAG_MC != RPL_DAG_MC_NONE +#if RPL_WITH_MC memcpy(&p->mc, &dio->mc, sizeof(p->mc)); -#endif /* RPL_DAG_MC != RPL_DAG_MC_NONE */ +#endif /* RPL_WITH_MC */ } } @@ -757,10 +806,14 @@ rpl_select_dag(rpl_instance_t *instance, rpl_parent_t *p) instance->of->update_metric_container(instance); /* Update the DAG rank. */ - best_dag->rank = instance->of->calculate_rank(best_dag->preferred_parent, 0); + best_dag->rank = rpl_rank_via_parent(best_dag->preferred_parent); if(last_parent == NULL || best_dag->rank < best_dag->min_rank) { + /* This is a slight departure from RFC6550: if we had no preferred parent before, + * reset min_rank. This helps recovering from temporary bad link conditions. */ best_dag->min_rank = best_dag->rank; - } else if(!acceptable_rank(best_dag, best_dag->rank)) { + } + + if(!acceptable_rank(best_dag, best_dag->rank)) { PRINTF("RPL: New rank unacceptable!\n"); rpl_set_preferred_parent(instance->current_dag, NULL); if(instance->mop != RPL_MOP_NO_DOWNWARD_ROUTES && last_parent != NULL) { @@ -796,22 +849,42 @@ rpl_select_dag(rpl_instance_t *instance, rpl_parent_t *p) } /*---------------------------------------------------------------------------*/ static rpl_parent_t * -best_parent(rpl_dag_t *dag) +best_parent(rpl_dag_t *dag, int fresh_only) { - rpl_parent_t *p, *best; + rpl_parent_t *p; + rpl_of_t *of; + rpl_parent_t *best = NULL; - best = NULL; + if(dag == NULL || dag->instance == NULL || dag->instance->of == NULL) { + return NULL; + } - p = nbr_table_head(rpl_parents); - while(p != NULL) { + of = dag->instance->of; + /* Search for the best parent according to the OF */ + for(p = nbr_table_head(rpl_parents); p != NULL; p = nbr_table_next(rpl_parents, p)) { + + /* Exclude parents from other DAGs or announcing an infinite rank */ if(p->dag != dag || p->rank == INFINITE_RANK) { - /* ignore this neighbor */ - } else if(best == NULL) { - best = p; - } else { - best = dag->instance->of->best_parent(best, p); + continue; } - p = nbr_table_next(rpl_parents, p); + + if(fresh_only && !rpl_parent_is_fresh(p)) { + /* Filter out non-fresh parents if fresh_only is set */ + continue; + } + +#ifndef UIP_CONF_ND6_SEND_NA + { + uip_ds6_nbr_t *nbr = rpl_get_nbr(p); + /* Exclude links to a neighbor that is not reachable at a NUD level */ + if(nbr == NULL || nbr->state != NBR_REACHABLE) { + continue; + } + } +#endif /* UIP_CONF_ND6_SEND_NA */ + + /* Now we have an acceptable parent, check if it is the new best */ + best = of->best_parent(best, p); } return best; @@ -820,16 +893,37 @@ best_parent(rpl_dag_t *dag) rpl_parent_t * rpl_select_parent(rpl_dag_t *dag) { - rpl_parent_t *best = best_parent(dag); + /* Look for best parent (regardless of freshness) */ + rpl_parent_t *best = best_parent(dag, 0); if(best != NULL) { - rpl_set_preferred_parent(dag, best); - dag->rank = dag->instance->of->calculate_rank(dag->preferred_parent, 0); +#if RPL_WITH_PROBING + if(rpl_parent_is_fresh(best)) { + rpl_set_preferred_parent(dag, best); + } else { + /* The best is not fresh. Look for the best fresh now. */ + rpl_parent_t *best_fresh = best_parent(dag, 1); + if(best_fresh == NULL) { + /* No fresh parent around, use best (non-fresh) */ + rpl_set_preferred_parent(dag, best); + } else { + /* Use best fresh */ + rpl_set_preferred_parent(dag, best_fresh); + } + /* Probe the best parent shortly in order to get a fresh estimate */ + dag->instance->urgent_probing_target = best; + rpl_schedule_probing(dag->instance); +#else /* RPL_WITH_PROBING */ + rpl_set_preferred_parent(dag, best); + dag->rank = rpl_rank_via_parent(dag->preferred_parent); +#endif /* RPL_WITH_PROBING */ + } } else { - dag->rank = INFINITE_RANK; + rpl_set_preferred_parent(dag, NULL); } - return best; + dag->rank = rpl_rank_via_parent(dag->preferred_parent); + return dag->preferred_parent; } /*---------------------------------------------------------------------------*/ void @@ -1003,6 +1097,10 @@ rpl_join_instance(uip_ipaddr_t *from, rpl_dio_t *dio) instance->of = of; instance->mop = dio->mop; + instance->mc.type = dio->mc.type; + instance->mc.flags = dio->mc.flags; + instance->mc.aggr = dio->mc.aggr; + instance->mc.prec = dio->mc.prec; instance->current_dag = dag; instance->dtsn_out = RPL_LOLLIPOP_INIT; @@ -1022,7 +1120,7 @@ rpl_join_instance(uip_ipaddr_t *from, rpl_dio_t *dio) rpl_set_preferred_parent(dag, p); instance->of->update_metric_container(instance); - dag->rank = instance->of->calculate_rank(p, 0); + dag->rank = rpl_rank_via_parent(p); /* So far this is the lowest rank we are aware of. */ dag->min_rank = dag->rank; @@ -1045,6 +1143,8 @@ rpl_join_instance(uip_ipaddr_t *from, rpl_dio_t *dio) } else { PRINTF("RPL: The DIO does not meet the prerequisites for sending a DAO\n"); } + + instance->of->reset(dag); } #if RPL_MAX_DAG_PER_INSTANCE > 1 @@ -1115,7 +1215,7 @@ rpl_add_dag(uip_ipaddr_t *from, rpl_dio_t *dio) memcpy(&dag->prefix_info, &dio->prefix_info, sizeof(rpl_prefix_t)); rpl_set_preferred_parent(dag, p); - dag->rank = instance->of->calculate_rank(p, 0); + dag->rank = rpl_rank_via_parent(p); dag->min_rank = dag->rank; /* So far this is the lowest rank we know of. */ PRINTF("RPL: Joined DAG with instance ID %u, rank %hu, DAG ID ", @@ -1157,7 +1257,7 @@ global_repair(uip_ipaddr_t *from, rpl_dag_t *dag, rpl_dio_t *dio) PRINTF("RPL: Failed to add a parent during the global repair\n"); dag->rank = INFINITE_RANK; } else { - dag->rank = dag->instance->of->calculate_rank(p, 0); + dag->rank = rpl_rank_via_parent(p); dag->min_rank = dag->rank; PRINTF("RPL: rpl_process_parent_event global repair\n"); rpl_process_parent_event(dag->instance, p); @@ -1224,6 +1324,7 @@ int rpl_process_parent_event(rpl_instance_t *instance, rpl_parent_t *p) { int return_value; + rpl_parent_t *last_parent = instance->current_dag->preferred_parent; #if DEBUG rpl_rank_t old_rank; @@ -1232,10 +1333,18 @@ rpl_process_parent_event(rpl_instance_t *instance, rpl_parent_t *p) return_value = 1; + if(uip_ds6_route_is_nexthop(rpl_get_parent_ipaddr(p)) && !rpl_parent_is_reachable(p)) { + PRINTF("RPL: Unacceptable link %u, removing routes via: ", rpl_get_parent_link_metric(p)); + PRINT6ADDR(rpl_get_parent_ipaddr(p)); + PRINTF("\n"); + rpl_remove_routes_by_nexthop(rpl_get_parent_ipaddr(p), p->dag); + } + if(!acceptable_rank(p->dag, p->rank)) { /* The candidate parent is no longer valid: the rank increase resulting from the choice of it as a parent would be too high. */ - PRINTF("RPL: Unacceptable rank %u\n", (unsigned)p->rank); + PRINTF("RPL: Unacceptable rank %u (Current min %u, MaxRankInc %u)\n", (unsigned)p->rank, + p->dag->min_rank, p->dag->instance->max_rankinc); rpl_nullify_parent(p); if(p != instance->current_dag->preferred_parent) { return 0; @@ -1245,10 +1354,12 @@ rpl_process_parent_event(rpl_instance_t *instance, rpl_parent_t *p) } if(rpl_select_dag(instance, p) == NULL) { - /* No suitable parent; trigger a local repair. */ - PRINTF("RPL: No parents found in any DAG\n"); - rpl_local_repair(instance); - return 0; + if(last_parent != NULL) { + /* No suitable parent anymore; trigger a local repair. */ + PRINTF("RPL: No parents found in any DAG\n"); + rpl_local_repair(instance); + return 0; + } } #if DEBUG @@ -1371,8 +1482,6 @@ rpl_process_dio(uip_ipaddr_t *from, rpl_dio_t *dio) PRINTF("RPL: Ignoring DIO with too low rank: %u\n", (unsigned)dio->rank); return; - } else if(dio->rank == INFINITE_RANK && dag->joined) { - rpl_reset_dio_timer(instance); } /* Prefix Information Option treated to add new prefix */ @@ -1439,6 +1548,11 @@ rpl_process_dio(uip_ipaddr_t *from, rpl_dio_t *dio) } p->rank = dio->rank; + if(dio->rank == INFINITE_RANK && p == dag->preferred_parent) { + /* Our preferred parent advertised an infinite rank, reset DIO timer */ + rpl_reset_dio_timer(instance); + } + /* Parent info has been updated, trigger rank recalculation */ p->flags |= RPL_PARENT_FLAG_UPDATED; @@ -1446,14 +1560,14 @@ rpl_process_dio(uip_ipaddr_t *from, rpl_dio_t *dio) PRINT6ADDR(&instance->current_dag->dag_id); PRINTF(", rank %u, min_rank %u, ", instance->current_dag->rank, instance->current_dag->min_rank); - PRINTF("parent rank %u, parent etx %u, link metric %u, instance etx %u\n", - p->rank, -1/*p->mc.obj.etx*/, rpl_get_nbr(p)->link_metric, instance->mc.obj.etx); + PRINTF("parent rank %u, link metric %u\n", + p->rank, rpl_get_parent_link_metric(p)); /* We have allocated a candidate parent; process the DIO further. */ -#if RPL_DAG_MC != RPL_DAG_MC_NONE +#if RPL_WITH_MC memcpy(&p->mc, &dio->mc, sizeof(p->mc)); -#endif /* RPL_DAG_MC != RPL_DAG_MC_NONE */ +#endif /* RPL_WITH_MC */ if(rpl_process_parent_event(instance, p) == 0) { PRINTF("RPL: The candidate parent is rejected\n"); return; diff --git a/core/net/rpl/rpl-ext-header.c b/core/net/rpl/rpl-ext-header.c index a2392c124..fd7cf8604 100644 --- a/core/net/rpl/rpl-ext-header.c +++ b/core/net/rpl/rpl-ext-header.c @@ -125,14 +125,6 @@ rpl_verify_header(int uip_ext_opt_offset) } sender_rank = UIP_HTONS(UIP_EXT_HDR_OPT_RPL_BUF->senderrank); - sender_closer = sender_rank < instance->current_dag->rank; - - PRINTF("RPL: Packet going %s, sender closer %d (%d < %d)\n", down == 1 ? "down" : "up", - sender_closer, - sender_rank, - instance->current_dag->rank - ); - sender = nbr_table_get_from_lladdr(rpl_parents, packetbuf_addr(PACKETBUF_ADDR_SENDER)); if(sender != NULL && (UIP_EXT_HDR_OPT_RPL_BUF->flags & RPL_HDR_OPT_RANK_ERR)) { @@ -142,6 +134,14 @@ rpl_verify_header(int uip_ext_opt_offset) rpl_select_dag(instance, sender); } + sender_closer = sender_rank < instance->current_dag->rank; + + PRINTF("RPL: Packet going %s, sender closer %d (%d < %d)\n", down == 1 ? "down" : "up", + sender_closer, + sender_rank, + instance->current_dag->rank + ); + if((down && !sender_closer) || (!down && sender_closer)) { PRINTF("RPL: Loop detected - senderrank: %d my-rank: %d sender_closer: %d\n", sender_rank, instance->current_dag->rank, diff --git a/core/net/rpl/rpl-icmp6.c b/core/net/rpl/rpl-icmp6.c index 774e3ba6a..6ea9b3e02 100644 --- a/core/net/rpl/rpl-icmp6.c +++ b/core/net/rpl/rpl-icmp6.c @@ -91,8 +91,6 @@ void RPL_DEBUG_DAO_OUTPUT(rpl_parent_t *); static uint8_t dao_sequence = RPL_LOLLIPOP_INIT; -extern rpl_of_t RPL_OF; - #if RPL_CONF_MULTICAST static uip_mcast6_route_t *mcast_group; #endif @@ -302,7 +300,7 @@ dio_input(void) dio.dag_redund = RPL_DIO_REDUNDANCY; dio.dag_min_hoprankinc = RPL_MIN_HOPRANKINC; dio.dag_max_rankinc = RPL_MAX_RANKINC; - dio.ocp = RPL_OF.ocp; + dio.ocp = RPL_OF_OCP; dio.default_lifetime = RPL_DEFAULT_LIFETIME; dio.lifetime_unit = RPL_DEFAULT_LIFETIME_UNIT; diff --git a/core/net/rpl/rpl-mrhof.c b/core/net/rpl/rpl-mrhof.c index f9ea76080..c51ac8694 100644 --- a/core/net/rpl/rpl-mrhof.c +++ b/core/net/rpl/rpl-mrhof.c @@ -32,7 +32,7 @@ /** * \file - * The Minimum Rank with Hysteresis Objective Function (MRHOF) + * The Minimum Rank with Hysteresis Objective Function (MRHOF), RFC6719 * * This implementation uses the estimated number of * transmissions (ETX) as the additive routing metric, @@ -46,83 +46,55 @@ * @{ */ +#include "net/rpl/rpl.h" #include "net/rpl/rpl-private.h" #include "net/nbr-table.h" +#include "net/link-stats.h" #define DEBUG DEBUG_NONE #include "net/ip/uip-debug.h" -static void reset(rpl_dag_t *); -static void neighbor_link_callback(rpl_parent_t *, int, int); -#if RPL_WITH_DAO_ACK -static void dao_ack_callback(rpl_parent_t *, int); -#endif -static rpl_parent_t *best_parent(rpl_parent_t *, rpl_parent_t *); -static rpl_dag_t *best_dag(rpl_dag_t *, rpl_dag_t *); -static rpl_rank_t calculate_rank(rpl_parent_t *, rpl_rank_t); -static void update_metric_container(rpl_instance_t *); +/* RFC6551 and RFC6719 do not mandate the use of a specific formula to + * compute the ETX value. This MRHOF implementation relies on the value + * computed by the link-stats module. It has an optional feature, + * RPL_MRHOF_CONF_SQUARED_ETX, that consists in squaring this value. + * This basically penalizes bad links while preserving the semantics of ETX + * (1 = perfect link, more = worse link). As a result, MRHOF will favor + * good links over short paths. Recommended when reliability is a priority. + * Without this feature, a hop with 50% PRR (ETX=2) is equivalent to two + * perfect hops with 100% PRR (ETX=1+1=2). With this feature, the former + * path obtains ETX=2*2=4 and the former ETX=1*1+1*1=2. */ +#ifdef RPL_MRHOF_CONF_SQUARED_ETX +#define RPL_MRHOF_SQUARED_ETX RPL_MRHOF_CONF_SQUARED_ETX +#else /* RPL_MRHOF_CONF_SQUARED_ETX */ +#define RPL_MRHOF_SQUARED_ETX 0 +#endif /* RPL_MRHOF_CONF_SQUARED_ETX */ -rpl_of_t rpl_mrhof = { - reset, - neighbor_link_callback, -#if RPL_WITH_DAO_ACK - dao_ack_callback, -#endif - best_parent, - best_dag, - calculate_rank, - update_metric_container, - 1 -}; - -/* Constants for the ETX moving average */ -#define ETX_SCALE 100 -#define ETX_ALPHA 90 - -/* Reject parents that have a higher link metric than the following. */ -#define MAX_LINK_METRIC 10 +#if !RPL_MRHOF_SQUARED_ETX +/* Configuration parameters of RFC6719. Reject parents that have a higher + * link metric than the following. The default value is 512 but we use 1024. */ +#define MAX_LINK_METRIC 1024 /* Eq ETX of 8 */ +/* Hysteresis of MRHOF: the rank must differ more than PARENT_SWITCH_THRESHOLD_DIV + * in order to switch preferred parent. Default in RFC6719: 192, eq ETX of 1.5. + * We use a more aggressive setting: 96, eq ETX of 0.75. + */ +#define PARENT_SWITCH_THRESHOLD 96 /* Eq ETX of 0.75 */ +#else /* !RPL_MRHOF_SQUARED_ETX */ +#define MAX_LINK_METRIC 2048 /* Eq ETX of 4 */ +#define PARENT_SWITCH_THRESHOLD 160 /* Eq ETX of 1.25 (results in a churn comparable +to the threshold of 96 in the non-squared case) */ +#endif /* !RPL_MRHOF_SQUARED_ETX */ /* Reject parents that have a higher path cost than the following. */ -#define MAX_PATH_COST 100 - -/* - * The rank must differ more than 1/PARENT_SWITCH_THRESHOLD_DIV in order - * to switch preferred parent. - */ -#define PARENT_SWITCH_THRESHOLD_DIV 2 - -typedef uint16_t rpl_path_metric_t; - -static rpl_path_metric_t -calculate_path_metric(rpl_parent_t *p) -{ - uip_ds6_nbr_t *nbr; - if(p == NULL) { - return MAX_PATH_COST * RPL_DAG_MC_ETX_DIVISOR; - } - nbr = rpl_get_nbr(p); - if(nbr == NULL) { - return MAX_PATH_COST * RPL_DAG_MC_ETX_DIVISOR; - } -#if RPL_DAG_MC == RPL_DAG_MC_NONE - { - return p->rank + (uint16_t)nbr->link_metric; - } -#elif RPL_DAG_MC == RPL_DAG_MC_ETX - return p->mc.obj.etx + (uint16_t)nbr->link_metric; -#elif RPL_DAG_MC == RPL_DAG_MC_ENERGY - return p->mc.obj.energy.energy_est + (uint16_t)nbr->link_metric; -#else -#error "Unsupported RPL_DAG_MC configured. See rpl.h." -#endif /* RPL_DAG_MC */ -} +#define MAX_PATH_COST 32768 /* Eq path ETX of 256 */ +/*---------------------------------------------------------------------------*/ static void reset(rpl_dag_t *dag) { PRINTF("RPL: Reset MRHOF\n"); } - +/*---------------------------------------------------------------------------*/ #if RPL_WITH_DAO_ACK static void dao_ack_callback(rpl_parent_t *p, int status) @@ -134,87 +106,127 @@ dao_ack_callback(rpl_parent_t *p, int status) PRINTF("RPL: MRHOF - DAO ACK received with status: %d\n", status); if(status >= RPL_DAO_ACK_UNABLE_TO_ACCEPT) { /* punish the ETX as if this was 10 packets lost */ - neighbor_link_callback(p, MAC_TX_OK, 10); + link_stats_packet_sent(rpl_get_parent_lladdr(p), MAC_TX_OK, 10); } else if(status == RPL_DAO_ACK_TIMEOUT) { /* timeout = no ack */ /* punish the total lack of ACK with a similar punishment */ - neighbor_link_callback(p, MAC_TX_OK, 10); + link_stats_packet_sent(rpl_get_parent_lladdr(p), MAC_TX_OK, 10); } } #endif /* RPL_WITH_DAO_ACK */ - -static void -neighbor_link_callback(rpl_parent_t *p, int status, int numtx) +/*---------------------------------------------------------------------------*/ +static uint16_t +parent_link_metric(rpl_parent_t *p) { - uint16_t recorded_etx = 0; - uint16_t packet_etx = numtx * RPL_DAG_MC_ETX_DIVISOR; - uint16_t new_etx; - uip_ds6_nbr_t *nbr = NULL; - - nbr = rpl_get_nbr(p); - if(nbr == NULL) { - /* No neighbor for this parent - something bad has occurred */ - return; - } - - recorded_etx = nbr->link_metric; - - /* Do not penalize the ETX when collisions or transmission errors occur. */ - if(status == MAC_TX_OK || status == MAC_TX_NOACK) { - if(status == MAC_TX_NOACK) { - packet_etx = MAX_LINK_METRIC * RPL_DAG_MC_ETX_DIVISOR; - } - - if(p->flags & RPL_PARENT_FLAG_LINK_METRIC_VALID) { - /* We already have a valid link metric, use weighted moving average to update it */ - new_etx = ((uint32_t)recorded_etx * ETX_ALPHA + - (uint32_t)packet_etx * (ETX_SCALE - ETX_ALPHA)) / ETX_SCALE; - } else { - /* We don't have a valid link metric, set it to the current packet's ETX */ - new_etx = packet_etx; - /* Set link metric as valid */ - p->flags |= RPL_PARENT_FLAG_LINK_METRIC_VALID; - } - - PRINTF("RPL: ETX changed from %u to %u (packet ETX = %u)\n", - (unsigned)(recorded_etx / RPL_DAG_MC_ETX_DIVISOR), - (unsigned)(new_etx / RPL_DAG_MC_ETX_DIVISOR), - (unsigned)(packet_etx / RPL_DAG_MC_ETX_DIVISOR)); - /* update the link metric for this nbr */ - nbr->link_metric = new_etx; + const struct link_stats *stats = rpl_get_parent_link_stats(p); + if(stats != NULL) { +#if RPL_MRHOF_SQUARED_ETX + uint32_t squared_etx = ((uint32_t)stats->etx * stats->etx) / LINK_STATS_ETX_DIVISOR; + return (uint16_t)MIN(squared_etx, 0xffff); +#else /* RPL_MRHOF_SQUARED_ETX */ + return stats->etx; +#endif /* RPL_MRHOF_SQUARED_ETX */ } + return 0xffff; } +/*---------------------------------------------------------------------------*/ +static uint16_t +parent_path_cost(rpl_parent_t *p) +{ + uint16_t base; + if(p == NULL || p->dag == NULL || p->dag->instance == NULL) { + return 0xffff; + } + +#if RPL_WITH_MC + /* Handle the different MC types */ + switch(p->dag->instance->mc.type) { + case RPL_DAG_MC_ETX: + base = p->mc.obj.etx; + break; + case RPL_DAG_MC_ENERGY: + base = p->mc.obj.energy.energy_est << 8; + break; + default: + base = p->rank; + break; + } +#else /* RPL_WITH_MC */ + base = p->rank; +#endif /* RPL_WITH_MC */ + + /* path cost upper bound: 0xffff */ + return MIN((uint32_t)base + parent_link_metric(p), 0xffff); +} +/*---------------------------------------------------------------------------*/ static rpl_rank_t -calculate_rank(rpl_parent_t *p, rpl_rank_t base_rank) +rank_via_parent(rpl_parent_t *p) { - rpl_rank_t new_rank; - rpl_rank_t rank_increase; - uip_ds6_nbr_t *nbr; + uint16_t min_hoprankinc; + uint16_t path_cost; - if(p == NULL || (nbr = rpl_get_nbr(p)) == NULL) { - if(base_rank == 0) { - return INFINITE_RANK; - } - rank_increase = RPL_INIT_LINK_METRIC * RPL_DAG_MC_ETX_DIVISOR; - } else { - rank_increase = nbr->link_metric; - if(base_rank == 0) { - base_rank = p->rank; - } + if(p == NULL || p->dag == NULL || p->dag->instance == NULL) { + return INFINITE_RANK; } - if(INFINITE_RANK - base_rank < rank_increase) { - /* Reached the maximum rank. */ - new_rank = INFINITE_RANK; - } else { - /* Calculate the rank based on the new rank information from DIO or - stored otherwise. */ - new_rank = base_rank + rank_increase; - } + min_hoprankinc = p->dag->instance->min_hoprankinc; + path_cost = parent_path_cost(p); - return new_rank; + /* Rank lower-bound: parent rank + min_hoprankinc */ + return MAX(MIN((uint32_t)p->rank + min_hoprankinc, 0xffff), path_cost); } +/*---------------------------------------------------------------------------*/ +static int +parent_is_acceptable(rpl_parent_t *p) +{ + uint16_t link_metric = parent_link_metric(p); + uint16_t path_cost = parent_path_cost(p); + /* Exclude links with too high link metrics or path cost (RFC6719, 3.2.2) */ + return link_metric <= MAX_LINK_METRIC && path_cost <= MAX_PATH_COST; +} +/*---------------------------------------------------------------------------*/ +static int +parent_has_usable_link(rpl_parent_t *p) +{ + uint16_t link_metric = parent_link_metric(p); + /* Exclude links with too high link metrics */ + return link_metric <= MAX_LINK_METRIC; +} +/*---------------------------------------------------------------------------*/ +static rpl_parent_t * +best_parent(rpl_parent_t *p1, rpl_parent_t *p2) +{ + rpl_dag_t *dag; + uint16_t p1_cost; + uint16_t p2_cost; + int p1_is_acceptable; + int p2_is_acceptable; + p1_is_acceptable = p1 != NULL && parent_is_acceptable(p1); + p2_is_acceptable = p2 != NULL && parent_is_acceptable(p2); + + if(!p1_is_acceptable) { + return p2_is_acceptable ? p2 : NULL; + } + if(!p2_is_acceptable) { + return p1_is_acceptable ? p1 : NULL; + } + + dag = p1->dag; /* Both parents are in the same DAG. */ + p1_cost = parent_path_cost(p1); + p2_cost = parent_path_cost(p2); + + /* Maintain stability of the preferred parent in case of similar ranks. */ + if(p1 == dag->preferred_parent || p2 == dag->preferred_parent) { + if(p1_cost < p2_cost + PARENT_SWITCH_THRESHOLD && + p1_cost > p2_cost - PARENT_SWITCH_THRESHOLD) { + return dag->preferred_parent; + } + } + + return p1_cost < p2_cost ? p1 : p2; +} +/*---------------------------------------------------------------------------*/ static rpl_dag_t * best_dag(rpl_dag_t *d1, rpl_dag_t *d2) { @@ -228,93 +240,77 @@ best_dag(rpl_dag_t *d1, rpl_dag_t *d2) return d1->rank < d2->rank ? d1 : d2; } - -static rpl_parent_t * -best_parent(rpl_parent_t *p1, rpl_parent_t *p2) -{ - rpl_dag_t *dag; - rpl_path_metric_t min_diff; - rpl_path_metric_t p1_metric; - rpl_path_metric_t p2_metric; - - dag = p1->dag; /* Both parents are in the same DAG. */ - - min_diff = RPL_DAG_MC_ETX_DIVISOR / - PARENT_SWITCH_THRESHOLD_DIV; - - p1_metric = calculate_path_metric(p1); - p2_metric = calculate_path_metric(p2); - - /* Maintain stability of the preferred parent in case of similar ranks. */ - if(p1 == dag->preferred_parent || p2 == dag->preferred_parent) { - if(p1_metric < p2_metric + min_diff && - p1_metric > p2_metric - min_diff) { - PRINTF("RPL: MRHOF hysteresis: %u <= %u <= %u\n", - p2_metric - min_diff, - p1_metric, - p2_metric + min_diff); - return dag->preferred_parent; - } - } - - return p1_metric < p2_metric ? p1 : p2; -} - -#if RPL_DAG_MC == RPL_DAG_MC_NONE +/*---------------------------------------------------------------------------*/ +#if !RPL_WITH_MC static void update_metric_container(rpl_instance_t *instance) { - instance->mc.type = RPL_DAG_MC; + instance->mc.type = RPL_DAG_MC_NONE; } -#else +#else /* RPL_WITH_MC */ static void update_metric_container(rpl_instance_t *instance) { - rpl_path_metric_t path_metric; rpl_dag_t *dag; -#if RPL_DAG_MC == RPL_DAG_MC_ENERGY + uint16_t path_cost; uint8_t type; -#endif - - instance->mc.type = RPL_DAG_MC; - instance->mc.flags = RPL_DAG_MC_FLAG_P; - instance->mc.aggr = RPL_DAG_MC_AGGR_ADDITIVE; - instance->mc.prec = 0; dag = instance->current_dag; - - if (!dag->joined) { + if(dag == NULL || !dag->joined) { PRINTF("RPL: Cannot update the metric container when not joined\n"); return; } if(dag->rank == ROOT_RANK(instance)) { - path_metric = 0; + /* Configure MC at root only, other nodes are auto-configured when joining */ + instance->mc.type = RPL_DAG_MC; + instance->mc.flags = 0; + instance->mc.aggr = RPL_DAG_MC_AGGR_ADDITIVE; + instance->mc.prec = 0; + path_cost = dag->rank; } else { - path_metric = calculate_path_metric(dag->preferred_parent); + path_cost = parent_path_cost(dag->preferred_parent); } -#if RPL_DAG_MC == RPL_DAG_MC_ETX - instance->mc.length = sizeof(instance->mc.obj.etx); - instance->mc.obj.etx = path_metric; - - PRINTF("RPL: My path ETX to the root is %u.%u\n", - instance->mc.obj.etx / RPL_DAG_MC_ETX_DIVISOR, - (instance->mc.obj.etx % RPL_DAG_MC_ETX_DIVISOR * 100) / - RPL_DAG_MC_ETX_DIVISOR); -#elif RPL_DAG_MC == RPL_DAG_MC_ENERGY - instance->mc.length = sizeof(instance->mc.obj.energy); - - if(dag->rank == ROOT_RANK(instance)) { - type = RPL_DAG_MC_ENERGY_TYPE_MAINS; - } else { - type = RPL_DAG_MC_ENERGY_TYPE_BATTERY; + /* Handle the different MC types */ + switch(instance->mc.type) { + case RPL_DAG_MC_NONE: + break; + case RPL_DAG_MC_ETX: + instance->mc.length = sizeof(instance->mc.obj.etx); + instance->mc.obj.etx = path_cost; + break; + case RPL_DAG_MC_ENERGY: + instance->mc.length = sizeof(instance->mc.obj.energy); + if(dag->rank == ROOT_RANK(instance)) { + type = RPL_DAG_MC_ENERGY_TYPE_MAINS; + } else { + type = RPL_DAG_MC_ENERGY_TYPE_BATTERY; + } + instance->mc.obj.energy.flags = type << RPL_DAG_MC_ENERGY_TYPE; + /* Energy_est is only one byte, use the least significant byte of the path metric. */ + instance->mc.obj.energy.energy_est = path_cost >> 8; + break; + default: + PRINTF("RPL: MRHOF, non-supported MC %u\n", instance->mc.type); + break; } - - instance->mc.obj.energy.flags = type << RPL_DAG_MC_ENERGY_TYPE; - instance->mc.obj.energy.energy_est = path_metric; -#endif /* RPL_DAG_MC == RPL_DAG_MC_ETX */ } -#endif /* RPL_DAG_MC == RPL_DAG_MC_NONE */ +#endif /* RPL_WITH_MC */ +/*---------------------------------------------------------------------------*/ +rpl_of_t rpl_mrhof = { + reset, +#if RPL_WITH_DAO_ACK + dao_ack_callback, +#endif + parent_link_metric, + parent_has_usable_link, + parent_path_cost, + rank_via_parent, + best_parent, + best_dag, + update_metric_container, + RPL_OCP_MRHOF +}; /** @}*/ diff --git a/core/net/rpl/rpl-nbr-policy.c b/core/net/rpl/rpl-nbr-policy.c index 87849af40..0af5a9041 100644 --- a/core/net/rpl/rpl-nbr-policy.c +++ b/core/net/rpl/rpl-nbr-policy.c @@ -138,7 +138,7 @@ update_nbr(void) parent->rank > 0 && parent->dag != NULL && parent->dag->instance != NULL && - (rank = parent->dag->instance->of->calculate_rank(parent, 0)) > worst_rank) { + (rank = parent->dag->instance->of->rank_via_parent(parent)) > worst_rank) { /* This is the worst-rank neighbor - this is a good candidate for removal */ worst_rank = rank; worst_rank_nbr = lladdr; @@ -190,7 +190,6 @@ const linkaddr_t * find_removable_dio(uip_ipaddr_t *from, rpl_dio_t *dio) { rpl_instance_t *instance; - rpl_rank_t rank; update_nbr(); @@ -201,8 +200,7 @@ find_removable_dio(uip_ipaddr_t *from, rpl_dio_t *dio) } /* Add the new neighbor only if it is better than the worst parent. */ - rank = instance->of->calculate_rank(NULL, dio->rank); - if(rank < worst_rank - instance->min_hoprankinc / 2) { + if(dio->rank + instance->min_hoprankinc < worst_rank - instance->min_hoprankinc / 2) { /* Found *great* neighbor - add! */ PRINTF("Found better neighbor %d < %d - add to cache...\n", rank, worst_rank); diff --git a/core/net/rpl/rpl-of0.c b/core/net/rpl/rpl-of0.c index b0a909a4d..d0bfd1296 100644 --- a/core/net/rpl/rpl-of0.c +++ b/core/net/rpl/rpl-of0.c @@ -27,11 +27,12 @@ * SUCH DAMAGE. * * This file is part of the Contiki operating system. + * */ /** * \file - * An implementation of RPL's objective function 0. + * An implementation of RPL's objective function 0, RFC6552 * * \author Joakim Eriksson , Nicolas Tsiftes */ @@ -41,136 +42,197 @@ * @{ */ +#include "net/rpl/rpl.h" #include "net/rpl/rpl-private.h" +#include "net/nbr-table.h" +#include "net/link-stats.h" #define DEBUG DEBUG_NONE #include "net/ip/uip-debug.h" -static void reset(rpl_dag_t *); -static rpl_parent_t *best_parent(rpl_parent_t *, rpl_parent_t *); -static rpl_dag_t *best_dag(rpl_dag_t *, rpl_dag_t *); -static rpl_rank_t calculate_rank(rpl_parent_t *, rpl_rank_t); -static void update_metric_container(rpl_instance_t *); +/* Constants from RFC6552. We use the default values. */ +#define RANK_STRETCH 0 /* Must be in the range [0;5] */ +#define RANK_FACTOR 1 /* Must be in the range [1;4] */ -rpl_of_t rpl_of0 = { - reset, - NULL, -#if RPL_WITH_DAO_ACK - NULL, -#endif - best_parent, - best_dag, - calculate_rank, - update_metric_container, - 0 -}; +#define MIN_STEP_OF_RANK 1 +#define MAX_STEP_OF_RANK 9 -#define DEFAULT_RANK_INCREMENT RPL_MIN_HOPRANKINC +/* OF0 computes rank increase as follows: + * rank_increase = (RANK_FACTOR * STEP_OF_RANK + RANK_STRETCH) * min_hop_rank_increase + * STEP_OF_RANK is an implementation-specific scalar value in the range [1;9]. + * RFC6552 provides a default value of 3 but recommends to use a dynamic link metric + * such as ETX. + * */ -#define MIN_DIFFERENCE (RPL_MIN_HOPRANKINC + RPL_MIN_HOPRANKINC / 2) +#define RPL_OF0_FIXED_SR 0 +#define RPL_OF0_ETX_BASED_SR 1 +/* Select RPL_OF0_FIXED_SR or RPL_OF0_ETX_BASED_SR */ +#ifdef RPL_OF0_CONF_SR +#define RPL_OF0_SR RPL_OF0_CONF_SR +#else /* RPL_OF0_CONF_SR */ +#define RPL_OF0_SR RPL_OF0_ETX_BASED_SR +#endif /* RPL_OF0_CONF_SR */ +#if RPL_OF0_FIXED_SR +#define STEP_OF_RANK(p) (3) +#endif /* RPL_OF0_FIXED_SR */ + +#if RPL_OF0_ETX_BASED_SR +/* Numbers suggested by P. Thubert for in the 6TiSCH WG. Anything that maps ETX to + * a step between 1 and 9 works. */ +#define STEP_OF_RANK(p) (((3 * parent_link_metric(p)) / LINK_STATS_ETX_DIVISOR) - 2) +#endif /* RPL_OF0_ETX_BASED_SR */ + +/*---------------------------------------------------------------------------*/ static void reset(rpl_dag_t *dag) { - PRINTF("RPL: Resetting OF0\n"); + PRINTF("RPL: Reset OF0\n"); } - -static rpl_rank_t -calculate_rank(rpl_parent_t *p, rpl_rank_t base_rank) +/*---------------------------------------------------------------------------*/ +#if RPL_WITH_DAO_ACK +static void +dao_ack_callback(rpl_parent_t *p, int status) { - rpl_rank_t increment; - if(base_rank == 0) { - if(p == NULL) { - return INFINITE_RANK; - } - base_rank = p->rank; + if(status == RPL_DAO_ACK_UNABLE_TO_ADD_ROUTE_AT_ROOT) { + return; } - - increment = p != NULL ? - p->dag->instance->min_hoprankinc : - DEFAULT_RANK_INCREMENT; - - if((rpl_rank_t)(base_rank + increment) < base_rank) { - PRINTF("RPL: OF0 rank %d incremented to infinite rank due to wrapping\n", - base_rank); + /* here we need to handle failed DAO's and other stuff */ + PRINTF("RPL: OF0 - DAO ACK received with status: %d\n", status); + if(status >= RPL_DAO_ACK_UNABLE_TO_ACCEPT) { + /* punish the ETX as if this was 10 packets lost */ + link_stats_packet_sent(rpl_get_parent_lladdr(p), MAC_TX_OK, 10); + } else if(status == RPL_DAO_ACK_TIMEOUT) { /* timeout = no ack */ + /* punish the total lack of ACK with a similar punishment */ + link_stats_packet_sent(rpl_get_parent_lladdr(p), MAC_TX_OK, 10); + } +} +#endif /* RPL_WITH_DAO_ACK */ +/*---------------------------------------------------------------------------*/ +static uint16_t +parent_link_metric(rpl_parent_t *p) +{ + /* OF0 operates without metric container; the only metric we have is ETX */ + const struct link_stats *stats = rpl_get_parent_link_stats(p); + return stats != NULL ? stats->etx : 0xffff; +} +/*---------------------------------------------------------------------------*/ +static uint16_t +parent_rank_increase(rpl_parent_t *p) +{ + uint16_t min_hoprankinc; + if(p == NULL || p->dag == NULL || p->dag->instance == NULL) { return INFINITE_RANK; } - return base_rank + increment; - + min_hoprankinc = p->dag->instance->min_hoprankinc; + return (RANK_FACTOR * STEP_OF_RANK(p) + RANK_STRETCH) * min_hoprankinc; } - -static rpl_dag_t * -best_dag(rpl_dag_t *d1, rpl_dag_t *d2) +/*---------------------------------------------------------------------------*/ +static uint16_t +parent_path_cost(rpl_parent_t *p) { - if(d1->grounded) { - if (!d2->grounded) { - return d1; - } - } else if(d2->grounded) { - return d2; + if(p == NULL) { + return 0xffff; } - - if(d1->preference < d2->preference) { - return d2; + /* path cost upper bound: 0xffff */ + return MIN((uint32_t)p->rank + parent_link_metric(p), 0xffff); +} +/*---------------------------------------------------------------------------*/ +static rpl_rank_t +rank_via_parent(rpl_parent_t *p) +{ + if(p == NULL) { + return INFINITE_RANK; } else { - if(d1->preference > d2->preference) { - return d1; - } - } - - if(d2->rank < d1->rank) { - return d2; - } else { - return d1; + return MIN((uint32_t)p->rank + parent_rank_increase(p), INFINITE_RANK); } } - +/*---------------------------------------------------------------------------*/ +static int +parent_is_acceptable(rpl_parent_t *p) +{ + return STEP_OF_RANK(p) >= MIN_STEP_OF_RANK + && STEP_OF_RANK(p) <= MAX_STEP_OF_RANK; +} +/*---------------------------------------------------------------------------*/ +static int +parent_has_usable_link(rpl_parent_t *p) +{ + return parent_is_acceptable(p); +} +/*---------------------------------------------------------------------------*/ static rpl_parent_t * best_parent(rpl_parent_t *p1, rpl_parent_t *p2) { - rpl_rank_t r1, r2; - rpl_dag_t *dag; - uip_ds6_nbr_t *nbr1, *nbr2; - nbr1 = rpl_get_nbr(p1); - nbr2 = rpl_get_nbr(p2); + rpl_dag_t *dag; + uint16_t p1_cost; + uint16_t p2_cost; + int p1_is_acceptable; + int p2_is_acceptable; - dag = (rpl_dag_t *)p1->dag; /* Both parents must be in the same DAG. */ + p1_is_acceptable = p1 != NULL && parent_is_acceptable(p1); + p2_is_acceptable = p2 != NULL && parent_is_acceptable(p2); - if(nbr1 == NULL || nbr2 == NULL) { - return dag->preferred_parent; + if(!p1_is_acceptable) { + return p2_is_acceptable ? p2 : NULL; + } + if(!p2_is_acceptable) { + return p1_is_acceptable ? p1 : NULL; } - PRINTF("RPL: Comparing parent "); - PRINT6ADDR(rpl_get_parent_ipaddr(p1)); - PRINTF(" (confidence %d, rank %d) with parent ", - nbr1->link_metric, p1->rank); - PRINT6ADDR(rpl_get_parent_ipaddr(p2)); - PRINTF(" (confidence %d, rank %d)\n", - nbr2->link_metric, p2->rank); + dag = p1->dag; /* Both parents are in the same DAG. */ + p1_cost = parent_path_cost(p1); + p2_cost = parent_path_cost(p2); - - r1 = DAG_RANK(p1->rank, p1->dag->instance) * RPL_MIN_HOPRANKINC + - nbr1->link_metric; - r2 = DAG_RANK(p2->rank, p1->dag->instance) * RPL_MIN_HOPRANKINC + - nbr2->link_metric; - /* Compare two parents by looking both and their rank and at the ETX - for that parent. We choose the parent that has the most - favourable combination. */ - - if(r1 < r2 + MIN_DIFFERENCE && - r1 > r2 - MIN_DIFFERENCE) { - return dag->preferred_parent; - } else if(r1 < r2) { - return p1; + /* Paths costs coarse-grained (multiple of min_hoprankinc), we operate without hysteresis */ + if(p1_cost != p2_cost) { + /* Pick parent with lowest path cost */ + return p1_cost < p2_cost ? p1 : p2; } else { - return p2; + /* We have a tie! */ + /* Stik to current preferred parent if possible */ + if(p1 == dag->preferred_parent || p2 == dag->preferred_parent) { + return dag->preferred_parent; + } + /* None of the nodes is the current preferred parent, + * choose parent with best link metric */ + return parent_link_metric(p1) < parent_link_metric(p2) ? p1 : p2; } } +/*---------------------------------------------------------------------------*/ +static rpl_dag_t * +best_dag(rpl_dag_t *d1, rpl_dag_t *d2) +{ + if(d1->grounded != d2->grounded) { + return d1->grounded ? d1 : d2; + } + if(d1->preference != d2->preference) { + return d1->preference > d2->preference ? d1 : d2; + } + + return d1->rank < d2->rank ? d1 : d2; +} +/*---------------------------------------------------------------------------*/ static void update_metric_container(rpl_instance_t *instance) { instance->mc.type = RPL_DAG_MC_NONE; } +/*---------------------------------------------------------------------------*/ +rpl_of_t rpl_of0 = { + reset, +#if RPL_WITH_DAO_ACK + dao_ack_callback, +#endif + parent_link_metric, + parent_has_usable_link, + parent_path_cost, + rank_via_parent, + best_parent, + best_dag, + update_metric_container, + RPL_OCP_OF0 +}; /** @}*/ diff --git a/core/net/rpl/rpl-private.h b/core/net/rpl/rpl-private.h index ec885eaea..b127bb9c4 100644 --- a/core/net/rpl/rpl-private.h +++ b/core/net/rpl/rpl-private.h @@ -144,11 +144,28 @@ ((unsigned long)(instance)->lifetime_unit * (lifetime)) #ifndef RPL_CONF_MIN_HOPRANKINC +/* RFC6550 defines the default MIN_HOPRANKINC as 256. + * However, we use MRHOF as a default Objective Function (RFC6719), + * which recommends setting MIN_HOPRANKINC with care, in particular + * when used with ETX as a metric. ETX is computed as a fixed point + * real with a divisor of 128 (RFC6719, RFC6551). We choose to also + * use 128 for RPL_MIN_HOPRANKINC, resulting in a rank equal to the + * ETX path cost. Larger values may also be desirable, as discussed + * in section 6.1 of RFC6719. */ +#if RPL_OF_OCP == RPL_OCP_MRHOF +#define RPL_MIN_HOPRANKINC 128 +#else /* RPL_OF_OCP == RPL_OCP_MRHOF */ #define RPL_MIN_HOPRANKINC 256 -#else +#endif /* RPL_OF_OCP == RPL_OCP_MRHOF */ +#else /* RPL_CONF_MIN_HOPRANKINC */ #define RPL_MIN_HOPRANKINC RPL_CONF_MIN_HOPRANKINC -#endif +#endif /* RPL_CONF_MIN_HOPRANKINC */ + +#ifndef RPL_CONF_MAX_RANKINC #define RPL_MAX_RANKINC (7 * RPL_MIN_HOPRANKINC) +#else /* RPL_CONF_MAX_RANKINC */ +#define RPL_MAX_RANKINC RPL_CONF_MAX_RANKINC +#endif /* RPL_CONF_MAX_RANKINC */ #define DAG_RANK(fixpt_rank, instance) \ ((fixpt_rank) / (instance)->min_hoprankinc) @@ -199,13 +216,6 @@ #define RPL_MCAST_LIFETIME 3 #endif -/* - * The ETX in the metric container is expressed as a fixed-point value - * whose integer part can be obtained by dividing the value by - * RPL_DAG_MC_ETX_DIVISOR. - */ -#define RPL_DAG_MC_ETX_DIVISOR 256 - /* DIS related */ #define RPL_DIS_SEND 1 diff --git a/core/net/rpl/rpl-timers.c b/core/net/rpl/rpl-timers.c index 158f5fd74..7ddfbcba9 100644 --- a/core/net/rpl/rpl-timers.c +++ b/core/net/rpl/rpl-timers.c @@ -43,6 +43,7 @@ #include "contiki-conf.h" #include "net/rpl/rpl-private.h" +#include "net/link-stats.h" #include "net/ipv6/multicast/uip-mcast6.h" #include "lib/random.h" #include "sys/ctimer.h" @@ -55,6 +56,14 @@ void RPL_CALLBACK_NEW_DIO_INTERVAL(uint8_t dio_interval); #endif /* RPL_CALLBACK_NEW_DIO_INTERVAL */ +#ifdef RPL_PROBING_SELECT_FUNC +rpl_parent_t *RPL_PROBING_SELECT_FUNC(rpl_dag_t *dag); +#endif /* RPL_PROBING_SELECT_FUNC */ + +#ifdef RPL_PROBING_DELAY_FUNC +clock_time_t RPL_PROBING_DELAY_FUNC(rpl_dag_t *dag); +#endif /* RPL_PROBING_DELAY_FUNC */ + /*---------------------------------------------------------------------------*/ static struct ctimer periodic_timer; @@ -358,42 +367,57 @@ rpl_schedule_unicast_dio_immediately(rpl_instance_t *instance) } /*---------------------------------------------------------------------------*/ #if RPL_WITH_PROBING -static rpl_parent_t * +clock_time_t +get_probing_delay(rpl_dag_t *dag) +{ + if(dag != NULL && dag->instance != NULL + && dag->instance->urgent_probing_target != NULL) { + /* Urgent probing needed (to find out if a neighbor may become preferred parent) */ + return random_rand() % (CLOCK_SECOND * 10); + } else { + /* Else, use normal probing interval */ + return ((RPL_PROBING_INTERVAL) / 2) + random_rand() % (RPL_PROBING_INTERVAL); + } +} +/*---------------------------------------------------------------------------*/ +rpl_parent_t * get_probing_target(rpl_dag_t *dag) { - /* Returns the next probing target. The current implementation probes the current - * preferred parent if we have not updated its link for RPL_PROBING_EXPIRATION_TIME. + /* Returns the next probing target. The current implementation probes the urgent + * probing target if any, or the preferred parent if its link statistics need refresh. * Otherwise, it picks at random between: - * (1) selecting the best parent not updated for RPL_PROBING_EXPIRATION_TIME + * (1) selecting the best parent with non-fresh link statistics * (2) selecting the least recently updated parent */ rpl_parent_t *p; rpl_parent_t *probing_target = NULL; rpl_rank_t probing_target_rank = INFINITE_RANK; - /* min_last_tx is the clock time RPL_PROBING_EXPIRATION_TIME in the past */ - clock_time_t min_last_tx = clock_time(); - min_last_tx = min_last_tx > 2 * RPL_PROBING_EXPIRATION_TIME - ? min_last_tx - RPL_PROBING_EXPIRATION_TIME : 1; + clock_time_t probing_target_age = 0; + clock_time_t clock_now = clock_time(); if(dag == NULL || - dag->instance == NULL || - dag->preferred_parent == NULL) { + dag->instance == NULL) { return NULL; } - /* Our preferred parent needs probing */ - if(dag->preferred_parent->last_tx_time < min_last_tx) { - probing_target = dag->preferred_parent; + /* There is an urgent probing target */ + if(dag->instance->urgent_probing_target != NULL) { + return dag->instance->urgent_probing_target; } - /* With 50% probability: probe best parent not updated for RPL_PROBING_EXPIRATION_TIME */ - if(probing_target == NULL && (random_rand() % 2) == 0) { + /* The preferred parent needs probing */ + if(dag->preferred_parent != NULL && !rpl_parent_is_fresh(dag->preferred_parent)) { + return dag->preferred_parent; + } + + /* With 50% probability: probe best non-fresh parent */ + if(random_rand() % 2 == 0) { p = nbr_table_head(rpl_parents); while(p != NULL) { - if(p->dag == dag && p->last_tx_time < min_last_tx) { + if(p->dag == dag && !rpl_parent_is_fresh(p)) { /* p is in our dag and needs probing */ - rpl_rank_t p_rank = dag->instance->of->calculate_rank(p, 0); + rpl_rank_t p_rank = rpl_rank_via_parent(p); if(probing_target == NULL || p_rank < probing_target_rank) { probing_target = p; @@ -404,14 +428,16 @@ get_probing_target(rpl_dag_t *dag) } } - /* The default probing target is the least recently updated parent */ + /* If we still do not have a probing target: pick the least recently updated parent */ if(probing_target == NULL) { p = nbr_table_head(rpl_parents); while(p != NULL) { - if(p->dag == dag) { + const struct link_stats *stats =rpl_get_parent_link_stats(p); + if(p->dag == dag && stats != NULL) { if(probing_target == NULL - || p->last_tx_time < probing_target->last_tx_time) { + || clock_now - stats->last_tx_time > probing_target_age) { probing_target = p; + probing_target_age = clock_now - stats->last_tx_time; } } p = nbr_table_next(rpl_parents, p); @@ -430,11 +456,17 @@ handle_probing_timer(void *ptr) /* Perform probing */ if(target_ipaddr != NULL) { - PRINTF("RPL: probing %u ((last tx %u min ago))\n", - nbr_table_get_lladdr(rpl_parents, probing_target)->u8[7], - (unsigned)((clock_time() - probing_target->last_tx_time) / (60 * CLOCK_SECOND))); - /* Send probe (unicast DIO or DIS) */ + const struct link_stats *stats = rpl_get_parent_link_stats(probing_target); + (void)stats; + PRINTF("RPL: probing %u %s last tx %u min ago\n", + rpl_get_parent_llpaddr(probing_target)->u8[7], + instance->urgent_probing_target != NULL ? "(urgent)" : "", + probing_target != NULL ? + (unsigned)((clock_time() - stats->last_tx_time) / (60 * CLOCK_SECOND)) : 0 + ); + /* Send probe, e.g. unicast DIO or DIS */ RPL_PROBING_SEND_FUNC(instance, target_ipaddr); + instance->urgent_probing_target = NULL; } /* Schedule next probing */ @@ -448,7 +480,7 @@ handle_probing_timer(void *ptr) void rpl_schedule_probing(rpl_instance_t *instance) { - ctimer_set(&instance->probing_timer, RPL_PROBING_DELAY_FUNC(), + ctimer_set(&instance->probing_timer, RPL_PROBING_DELAY_FUNC(instance->current_dag), handle_probing_timer, instance); } #endif /* RPL_WITH_PROBING */ diff --git a/core/net/rpl/rpl.c b/core/net/rpl/rpl.c index c541463d4..145ec1cd1 100644 --- a/core/net/rpl/rpl.c +++ b/core/net/rpl/rpl.c @@ -215,12 +215,10 @@ rpl_remove_routes_by_nexthop(uip_ipaddr_t *nexthop, rpl_dag_t *dag) while(r != NULL) { if(uip_ipaddr_cmp(uip_ds6_route_nexthop(r), nexthop) && - r->state.dag == dag) { - uip_ds6_route_rm(r); - r = uip_ds6_route_head(); - } else { - r = uip_ds6_route_next(r); + r->state.dag == dag) { + r->state.lifetime = 0; } + r = uip_ds6_route_next(r); } ANNOTATE("#L %u 0\n", nexthop->u8[sizeof(uip_ipaddr_t) - 1]); } @@ -268,10 +266,6 @@ rpl_link_neighbor_callback(const linkaddr_t *addr, int status, int numtx) /* Trigger DAG rank recalculation. */ PRINTF("RPL: rpl_link_neighbor_callback triggering update\n"); parent->flags |= RPL_PARENT_FLAG_UPDATED; - if(instance->of->neighbor_link_callback != NULL) { - instance->of->neighbor_link_callback(parent, status, numtx); - parent->last_tx_time = clock_time(); - } } } } @@ -350,8 +344,6 @@ rpl_init(void) #if RPL_CONF_STATS memset(&rpl_stats, 0, sizeof(rpl_stats)); #endif - - RPL_OF.reset(NULL); } /*---------------------------------------------------------------------------*/ diff --git a/core/net/rpl/rpl.h b/core/net/rpl/rpl.h index 532df4b3a..89bc70ae8 100644 --- a/core/net/rpl/rpl.h +++ b/core/net/rpl/rpl.h @@ -49,8 +49,8 @@ typedef uint16_t rpl_rank_t; typedef uint16_t rpl_ocp_t; /*---------------------------------------------------------------------------*/ -/* DAG Metric Container Object Types, to be confirmed by IANA. */ -#define RPL_DAG_MC_NONE 0 /* Local identifier for empty MC */ +/* IANA Routing Metric/Constraint Type as defined in RFC6551 */ +#define RPL_DAG_MC_NONE 0 /* Local identifier for empty MC */ #define RPL_DAG_MC_NSA 1 /* Node State and Attributes */ #define RPL_DAG_MC_ENERGY 2 /* Node Energy */ #define RPL_DAG_MC_HOPCOUNT 3 /* Hop Count */ @@ -60,27 +60,31 @@ typedef uint16_t rpl_ocp_t; #define RPL_DAG_MC_ETX 7 /* Expected Transmission Count */ #define RPL_DAG_MC_LC 8 /* Link Color */ -/* DAG Metric Container flags. */ -#define RPL_DAG_MC_FLAG_P 0x8 -#define RPL_DAG_MC_FLAG_C 0x4 -#define RPL_DAG_MC_FLAG_O 0x2 -#define RPL_DAG_MC_FLAG_R 0x1 +/* IANA Routing Metric/Constraint Common Header Flag field as defined in RFC6551 (bit indexes) */ +#define RPL_DAG_MC_FLAG_P 5 +#define RPL_DAG_MC_FLAG_C 6 +#define RPL_DAG_MC_FLAG_O 7 +#define RPL_DAG_MC_FLAG_R 8 -/* DAG Metric Container aggregation mode. */ +/* IANA Routing Metric/Constraint Common Header A Field as defined in RFC6551 */ #define RPL_DAG_MC_AGGR_ADDITIVE 0 #define RPL_DAG_MC_AGGR_MAXIMUM 1 #define RPL_DAG_MC_AGGR_MINIMUM 2 #define RPL_DAG_MC_AGGR_MULTIPLICATIVE 3 -/* The bit index within the flags field of - the rpl_metric_object_energy structure. */ -#define RPL_DAG_MC_ENERGY_INCLUDED 3 -#define RPL_DAG_MC_ENERGY_TYPE 1 -#define RPL_DAG_MC_ENERGY_ESTIMATION 0 +/* The bit index within the flags field of the rpl_metric_object_energy structure. */ +#define RPL_DAG_MC_ENERGY_INCLUDED 3 +#define RPL_DAG_MC_ENERGY_TYPE 1 +#define RPL_DAG_MC_ENERGY_ESTIMATION 0 -#define RPL_DAG_MC_ENERGY_TYPE_MAINS 0 -#define RPL_DAG_MC_ENERGY_TYPE_BATTERY 1 -#define RPL_DAG_MC_ENERGY_TYPE_SCAVENGING 2 +/* IANA Node Type Field as defined in RFC6551 */ +#define RPL_DAG_MC_ENERGY_TYPE_MAINS 0 +#define RPL_DAG_MC_ENERGY_TYPE_BATTERY 1 +#define RPL_DAG_MC_ENERGY_TYPE_SCAVENGING 2 + +/* IANA Objective Code Point as defined in RFC6550 */ +#define RPL_OCP_OF0 0 +#define RPL_OCP_MRHOF 1 struct rpl_metric_object_energy { uint8_t flags; @@ -109,11 +113,10 @@ struct rpl_dag; struct rpl_parent { struct rpl_dag *dag; -#if RPL_DAG_MC != RPL_DAG_MC_NONE +#if RPL_WITH_MC rpl_metric_container_t mc; -#endif /* RPL_DAG_MC != RPL_DAG_MC_NONE */ +#endif /* RPL_WITH_MC */ rpl_rank_t rank; - clock_time_t last_tx_time; uint8_t dtsn; uint8_t flags; }; @@ -155,11 +158,25 @@ typedef struct rpl_instance rpl_instance_t; * Resets the objective function state for a specific DAG. This function is * called when doing a global repair on the DAG. * - * neighbor_link_callback(parent, known, etx) + * parent_link_metric(parent) * - * Receives link-layer neighbor information. The parameter "known" is set - * either to 0 or 1. The "etx" parameter specifies the current - * ETX(estimated transmissions) for the neighbor. + * Returns the link metric of a parent + * + * parent_has_usable_link(parent) + * + * Returns 1 iff we have a usable link to this parent + * + * parent_path_cost(parent) + * + * Returns the path cost of a parent + * + * rank_via_parent(parent) + * + * Returns our rank if we select a given parent as preferred parent + * + * parent_is_acceptable + * + * Returns 1 if a parent is usable as preferred parent, 0 otherwise * * best_parent(parent1, parent2) * @@ -169,13 +186,6 @@ typedef struct rpl_instance rpl_instance_t; * * Compares two DAGs and returns the best one, according to the OF. * - * calculate_rank(parent, base_rank) - * - * Calculates a rank value using the parent rank and a base rank. - * If "parent" is NULL, the objective function selects a default increment - * that is adds to the "base_rank". Otherwise, the OF uses information known - * about "parent" to select an increment to the "base_rank". - * * update_metric_container(dag) * * Updates the metric container for outgoing DIOs in a certain DAG. @@ -190,20 +200,20 @@ typedef struct rpl_instance rpl_instance_t; */ struct rpl_of { void (*reset)(struct rpl_dag *); - void (*neighbor_link_callback)(rpl_parent_t *, int, int); #if RPL_WITH_DAO_ACK void (*dao_ack_callback)(rpl_parent_t *, int status); #endif + uint16_t (*parent_link_metric)(rpl_parent_t *); + int (*parent_has_usable_link)(rpl_parent_t *); + uint16_t (*parent_path_cost)(rpl_parent_t *); + rpl_rank_t (*rank_via_parent)(rpl_parent_t *); rpl_parent_t *(*best_parent)(rpl_parent_t *, rpl_parent_t *); rpl_dag_t *(*best_dag)(rpl_dag_t *, rpl_dag_t *); - rpl_rank_t (*calculate_rank)(rpl_parent_t *, rpl_rank_t); void (*update_metric_container)( rpl_instance_t *); rpl_ocp_t ocp; }; typedef struct rpl_of rpl_of_t; -/* Declare the selected objective function. */ -extern rpl_of_t RPL_OF; /*---------------------------------------------------------------------------*/ /* Instance */ struct rpl_instance { @@ -241,6 +251,7 @@ struct rpl_instance { clock_time_t dio_next_delay; /* delay for completion of dio interval */ #if RPL_WITH_PROBING struct ctimer probing_timer; + rpl_parent_t *urgent_probing_target; #endif /* RPL_WITH_PROBING */ struct ctimer dio_timer; struct ctimer dao_timer; @@ -268,10 +279,15 @@ int rpl_verify_header(int); void rpl_insert_header(void); void rpl_remove_header(void); uint8_t rpl_invert_header(void); +const struct link_stats *rpl_get_parent_link_stats(rpl_parent_t *p); +int rpl_parent_is_fresh(rpl_parent_t *p); +int rpl_parent_is_reachable(rpl_parent_t *p); +uint16_t rpl_get_parent_link_metric(rpl_parent_t *p); +rpl_rank_t rpl_rank_via_parent(rpl_parent_t *p); +const linkaddr_t *rpl_get_parent_lladdr(rpl_parent_t *p); uip_ipaddr_t *rpl_get_parent_ipaddr(rpl_parent_t *nbr); rpl_parent_t *rpl_get_parent(uip_lladdr_t *addr); rpl_rank_t rpl_get_parent_rank(uip_lladdr_t *addr); -uint16_t rpl_get_parent_link_metric(const uip_lladdr_t *addr); void rpl_dag_init(void); uip_ds6_nbr_t *rpl_get_nbr(rpl_parent_t *parent); void rpl_print_neighbor_list(void); diff --git a/examples/ipv6/rpl-collect/project-conf.h b/examples/ipv6/rpl-collect/project-conf.h index 4b39f7de9..c33ca27fb 100644 --- a/examples/ipv6/rpl-collect/project-conf.h +++ b/examples/ipv6/rpl-collect/project-conf.h @@ -56,4 +56,11 @@ #define RPL_CONF_DEFAULT_ROUTE_INFINITE_LIFETIME 1 +/* Save some ROM */ +#undef UIP_CONF_TCP +#define UIP_CONF_TCP 0 + +#undef SICSLOWPAN_CONF_FRAG +#define SICSLOWPAN_CONF_FRAG 0 + #endif /* PROJECT_CONF_H_ */ diff --git a/examples/ipv6/rpl-tsch/project-conf.h b/examples/ipv6/rpl-tsch/project-conf.h index a725cbba0..8ac0a93b6 100644 --- a/examples/ipv6/rpl-tsch/project-conf.h +++ b/examples/ipv6/rpl-tsch/project-conf.h @@ -139,7 +139,7 @@ #undef UIP_CONF_TCP #define UIP_CONF_TCP 0 #undef QUEUEBUF_CONF_NUM -#define QUEUEBUF_CONF_NUM 4 +#define QUEUEBUF_CONF_NUM 3 #undef UIP_CONF_MAX_ROUTES #define UIP_CONF_MAX_ROUTES 8 #undef NBR_TABLE_CONF_MAX_NEIGHBORS diff --git a/platform/avr-ravenusb/Makefile.avr-ravenusb b/platform/avr-ravenusb/Makefile.avr-ravenusb index b6301b329..6c702a261 100644 --- a/platform/avr-ravenusb/Makefile.avr-ravenusb +++ b/platform/avr-ravenusb/Makefile.avr-ravenusb @@ -71,5 +71,5 @@ MODULES+=core/net/mac core/net/mac/sicslowmac \ core/net/llsec else vpath %.c $(CONTIKI)/core/net/ipv6 -CONTIKI_SOURCEFILES += sicslowpan.c linkaddr.c +CONTIKI_SOURCEFILES += sicslowpan.c linkaddr.c link-stats.c nbr-table.c endif diff --git a/platform/micaz/contiki-conf.h b/platform/micaz/contiki-conf.h index 04aaccf6b..50e2ace1e 100644 --- a/platform/micaz/contiki-conf.h +++ b/platform/micaz/contiki-conf.h @@ -125,8 +125,8 @@ #define UIP_CONF_ROUTER 0 /* configure number of neighbors and routes */ -#define NBR_TABLE_CONF_MAX_NEIGHBORS 5 -#define UIP_CONF_MAX_ROUTES 5 +#define NBR_TABLE_CONF_MAX_NEIGHBORS 4 +#define UIP_CONF_MAX_ROUTES 4 #define RPL_CONF_MAX_PARENTS 4 #define RPL_CONF_MAX_DAG_PER_INSTANCE 1 diff --git a/platform/sky/contiki-conf.h b/platform/sky/contiki-conf.h index 39d8bc882..7665d146e 100644 --- a/platform/sky/contiki-conf.h +++ b/platform/sky/contiki-conf.h @@ -150,10 +150,10 @@ /* configure number of neighbors and routes */ #ifndef NBR_TABLE_CONF_MAX_NEIGHBORS -#define NBR_TABLE_CONF_MAX_NEIGHBORS 20 +#define NBR_TABLE_CONF_MAX_NEIGHBORS 16 #endif /* NBR_TABLE_CONF_MAX_NEIGHBORS */ #ifndef UIP_CONF_MAX_ROUTES -#define UIP_CONF_MAX_ROUTES 20 +#define UIP_CONF_MAX_ROUTES 16 #endif /* UIP_CONF_MAX_ROUTES */ #define UIP_CONF_ND6_SEND_RA 0 diff --git a/platform/zoul/contiki-conf.h b/platform/zoul/contiki-conf.h index 3d07103ef..c28d789d3 100644 --- a/platform/zoul/contiki-conf.h +++ b/platform/zoul/contiki-conf.h @@ -512,10 +512,10 @@ typedef uint32_t rtimer_clock_t; #define UIP_CONF_ND6_RETRANS_TIMER 10000 #ifndef NBR_TABLE_CONF_MAX_NEIGHBORS -#define NBR_TABLE_CONF_MAX_NEIGHBORS 20 +#define NBR_TABLE_CONF_MAX_NEIGHBORS 16 #endif #ifndef UIP_CONF_MAX_ROUTES -#define UIP_CONF_MAX_ROUTES 20 +#define UIP_CONF_MAX_ROUTES 16 #endif /* uIP */ diff --git a/regression-tests/12-rpl/06-rpl-temporary-root-loss.csc b/regression-tests/12-rpl/06-rpl-temporary-root-loss.csc index 096713102..6c29c643f 100644 --- a/regression-tests/12-rpl/06-rpl-temporary-root-loss.csc +++ b/regression-tests/12-rpl/06-rpl-temporary-root-loss.csc @@ -289,9 +289,9 @@ lostMsgs = 0; // we check that we got up to at least message 62 and // (the simulation is 4000000ms = 66 minutes long) -// that we did not lose anything since 34 +// that we did not lose anything since 45 // (the sink is back at 2000000ms = 33 minutes) -TIMEOUT(4000000, if(lastMsg >= 62 && lastMissed <= 34) { log.testOK(); } ); +TIMEOUT(4000000, if(lastMsg >= 62 && lastMissed <= 45) { log.testOK(); } ); lastMsg = -1; lastMissed = -1;