mbed-os/connectivity/nanostack/sal-stack-nanostack/source/RPL/rpl_control.c

2016 lines
72 KiB
C

/*
* Copyright (c) 2015-2021, Pelion and affiliates.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* rpl_control.c deals with packet handling, and management of domains. The
* workings of individual instances are dealt elsewhere.
*
* rpl_domain_t is accessible.
* rpl_instance_t, rpl_dodag_t, rpl_dodag_version_t, rpl_neighbout_t are all opaque
*/
/*
* State machine still not fully developed - we're basically free-running,
* with a simple kick to advance the bootstrap when we first get a DAO-ACK
* on any instance.
*
* A larger set of events is needed, including some sort of policy interaction
* to establish which instances "count" for triggering an overall system (or
* domain) state machine.
*/
#include "nsconfig.h"
#ifdef HAVE_RPL
#include <string.h>
#include "ns_trace.h"
#include "common_functions.h"
#include "nsdynmemLIB.h"
#include "Core/include/ns_buffer.h"
#include "NWK_INTERFACE/Include/protocol.h"
#include "NWK_INTERFACE/Include/protocol_stats.h"
#include "Common_Protocols/ipv6_constants.h"
#include "Common_Protocols/icmpv6.h"
#include "Common_Protocols/ip.h"
#include "ipv6_stack/protocol_ipv6.h"
#include "Service_Libs/etx/etx.h" /* slight ick */
#include "net_rpl.h"
#include "RPL/rpl_protocol.h"
#include "RPL/rpl_upward.h"
#include "RPL/rpl_downward.h"
#include "RPL/rpl_policy.h"
#include "RPL/rpl_control.h"
#include "6LoWPAN/ws/ws_common.h"
#define TRACE_GROUP "rplc"
const uint8_t ADDR_LINK_LOCAL_ALL_RPL_NODES[16] = { 0xff, 0x02, [15] = 0x1a };
/* Sensible default limits for a 6LoWPAN-ND node */
static size_t rpl_purge_threshold = 2 * 1024;
static size_t rpl_alloc_limit = 4 * 1024; // 0 means no limit
static size_t rpl_alloc_total;
#define RPL_ALLOC_OVERHEAD 8
static NS_LIST_DEFINE(rpl_domains, rpl_domain_t, link);
void rpl_control_set_memory_limits(size_t soft_limit, size_t hard_limit)
{
rpl_purge_threshold = soft_limit;
rpl_alloc_limit = hard_limit;
}
/* Send all RPL allocations through central point - gives a way of counting and
* limiting RPL allocations. Allocation rejected if total would go above the
* hard limit rpl_alloc_limit. This should normally be avoided by the timers
* trying to keep allocations below the soft limit rpl_purge_threshold.
*/
void *rpl_realloc(void *p, uint16_t old_size, uint16_t new_size)
{
if (old_size == new_size) {
return p;
}
/* If increasing memory, check if we're going above the hard limit */
if (rpl_alloc_limit != 0 && new_size > old_size) {
uint16_t size_diff = p ? new_size - old_size : new_size + RPL_ALLOC_OVERHEAD;
if (rpl_alloc_total + size_diff > rpl_alloc_limit) {
protocol_stats_update(STATS_RPL_MEMORY_OVERFLOW, size_diff);
return NULL;
}
}
void *n = ns_dyn_mem_alloc(new_size);
if (n) {
/* rpl_free() below will subtract (old_size + RPL_ALLOC_OVERHEAD) if reallocing */
rpl_alloc_total += (size_t) new_size + RPL_ALLOC_OVERHEAD;
protocol_stats_update(STATS_RPL_MEMORY_ALLOC, new_size);
if (p) {
memcpy(n, p, old_size < new_size ? old_size : new_size);
}
} else {
protocol_stats_update(STATS_RPL_MEMORY_OVERFLOW, new_size);
}
rpl_free(p, old_size);
return n;
}
void *rpl_alloc(uint16_t size)
{
return rpl_realloc(NULL, 0, size);
}
void rpl_free(void *p, uint16_t size)
{
if (p) {
rpl_alloc_total -= (size_t) size + RPL_ALLOC_OVERHEAD;
protocol_stats_update(STATS_RPL_MEMORY_FREE, size);
}
ns_dyn_mem_free(p);
}
void rpl_control_event(struct rpl_domain *domain, rpl_event_t event)
{
if (domain->callback) {
domain->callback(event, domain->cb_handle);
}
}
static void rpl_control_convert_internal_config(rpl_dodag_conf_int_t *conf, const rpl_dodag_conf_t *external_conf)
{
conf->dio_interval_min = external_conf->dio_interval_min;
conf->dio_interval_doublings = external_conf->dio_interval_doublings;
conf->dio_redundancy_constant = external_conf->dio_redundancy_constant;
conf->default_lifetime = external_conf->default_lifetime;
conf->dag_max_rank_increase = external_conf->dag_max_rank_increase;
conf->min_hop_rank_increase = external_conf->min_hop_rank_increase;
conf->objective_code_point = external_conf->objective_code_point;
conf->lifetime_unit = external_conf->lifetime_unit;
conf->options = rpl_conf_options(external_conf->authentication, external_conf->path_control_size);
conf->reserved = 0;
}
/* When we join a new instance, we need to publish existing addresses.
* Later addresses additions/removals are handled by rpl_control_addr_notifier.
*/
static void rpl_control_publish_own_addresses(rpl_domain_t *domain, rpl_instance_t *instance)
{
int8_t last_id = -1;
protocol_interface_info_entry_t *cur;
while ((cur = protocol_stack_interface_info_get_by_rpl_domain(domain, last_id)) != NULL) {
ns_list_foreach(if_address_entry_t, addr, &cur->ip_addresses) {
if (!addr->tentative && !addr_is_ipv6_link_local(addr->address)) {
/* TODO - wouldn't need to publish address if within a prefix published by parent */
uint32_t descriptor = 0;
bool want_descriptor = rpl_policy_target_descriptor_for_own_address(domain, addr->address, addr->source, addr->data, &descriptor);
rpl_instance_publish_dao_target(instance, addr->address, 128, addr->valid_lifetime, true, want_descriptor, descriptor);
}
}
last_id = cur->id;
}
}
void rpl_control_publish_host_address(rpl_domain_t *domain, const uint8_t addr[16], uint32_t lifetime)
{
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
if (!rpl_instance_am_root(instance)) {
/* TODO - Wouldn't need to publish host address if within a published prefix */
uint32_t descriptor = 0;
bool want_descriptor = rpl_policy_target_descriptor_for_host_address(domain, addr, &descriptor);
rpl_instance_publish_dao_target(instance, addr, 128, lifetime, false, want_descriptor, descriptor);
}
}
}
/* Is unpublish a word? */
void rpl_control_unpublish_address(rpl_domain_t *domain, const uint8_t addr[16])
{
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
rpl_instance_delete_published_dao_target(instance, addr, 128);
}
}
void rpl_control_request_parent_link_confirmation(bool requested)
{
rpl_policy_set_parent_confirmation_request(requested);
}
void rpl_control_set_dio_multicast_min_config_advertisment_count(uint8_t min_count)
{
rpl_policy_set_dio_multicast_config_advertisment_min_count(min_count);
}
void rpl_control_set_address_registration_timeout(uint16_t timeout_in_minutes)
{
rpl_policy_set_address_registration_timeout(timeout_in_minutes);
}
void rpl_control_set_dao_retry_count(uint8_t count)
{
rpl_policy_set_dao_retry_count(count);
}
void rpl_control_set_minimum_dao_target_refresh(uint16_t seconds)
{
rpl_policy_set_minimum_dao_target_refresh(seconds);
}
void rpl_control_set_initial_dao_ack_wait(uint16_t timeout_in_ms)
{
rpl_policy_set_initial_dao_ack_wait(timeout_in_ms);
}
void rpl_control_set_mrhof_parent_set_size(uint16_t parent_set_size)
{
rpl_policy_set_mrhof_parent_set_size(parent_set_size);
}
/* True Force RPL to use IPv6 tunneling when it send and forward data to Border router direction, This feature is disabled by default */
void rpl_control_set_force_tunnel(bool requested)
{
rpl_policy_force_tunnel_set(requested);
}
/* Send address registration to either specified address, or to non-registered address */
void rpl_control_register_address(protocol_interface_info_entry_t *interface, const uint8_t addr[16])
{
if (!interface->rpl_domain) {
return;
}
if (!rpl_policy_parent_confirmation_requested()) {
return;
}
ns_list_foreach(struct rpl_instance, instance, &interface->rpl_domain->instances) {
rpl_instance_send_address_registration(instance, addr);
}
}
bool rpl_control_address_register_done(protocol_interface_info_entry_t *interface, const uint8_t ll_addr[16], uint8_t status)
{
bool blacklist_neighbour = false;
if (!interface->rpl_domain) {
return false;
}
if (!rpl_policy_parent_confirmation_requested()) {
return false;
}
ns_list_foreach(struct rpl_instance, instance, &interface->rpl_domain->instances) {
rpl_neighbour_t *neighbour = rpl_lookup_neighbour_by_ll_address(instance, ll_addr, interface->id);
if (neighbour) {
blacklist_neighbour = blacklist_neighbour || rpl_instance_address_registration_done(interface, instance, neighbour, status);
}
}
return blacklist_neighbour;
}
bool rpl_control_is_dodag_parent(protocol_interface_info_entry_t *interface, const uint8_t ll_addr[16])
{
if (!interface->rpl_domain) {
return false;
}
// go through instances and parents and check if they match the address.
ns_list_foreach(struct rpl_instance, instance, &interface->rpl_domain->instances) {
if (rpl_instance_address_is_parent(instance, ll_addr)) {
return true;
}
}
return false;
}
bool rpl_control_is_dodag_parent_candidate(protocol_interface_info_entry_t *interface, const uint8_t ll_addr[16], uint16_t candidate_cmp_limiter)
{
if (!interface->rpl_domain) {
return false;
}
// go through instances and parents and check if they match the address.
ns_list_foreach(struct rpl_instance, instance, &interface->rpl_domain->instances) {
if (rpl_instance_address_is_candidate(instance, ll_addr, candidate_cmp_limiter)) {
return true;
}
}
return false;
}
uint16_t rpl_control_candidate_list_size(protocol_interface_info_entry_t *interface, rpl_instance_t *rpl_instance)
{
if (!interface->rpl_domain) {
return 0;
}
return rpl_instance_address_candidate_count(rpl_instance, false);
}
uint16_t rpl_control_selected_parent_count(protocol_interface_info_entry_t *interface, rpl_instance_t *rpl_instance)
{
if (!interface->rpl_domain) {
return 0;
}
return rpl_instance_address_candidate_count(rpl_instance, true);
}
bool rpl_control_probe_parent_candidate(protocol_interface_info_entry_t *interface, const uint8_t ll_addr[16])
{
if (!interface->rpl_domain) {
return false;
}
// go through instances and parents and check if they match the address.
ns_list_foreach(struct rpl_instance, instance, &interface->rpl_domain->instances) {
if (rpl_lookup_neighbour_by_ll_address(instance, ll_addr, interface->id)) {
return true;
}
}
return false;
}
uint16_t rpl_control_neighbor_info_get(struct protocol_interface_info_entry *interface, const uint8_t ll_addr[16], uint8_t *global_address)
{
if (!interface->rpl_domain) {
return 0xffff;
}
ns_list_foreach(struct rpl_instance, instance, &interface->rpl_domain->instances) {
rpl_neighbour_t *neighbour = rpl_lookup_neighbour_by_ll_address(instance, ll_addr, interface->id);
if (neighbour) {
const uint8_t *global_address_ptr = rpl_neighbour_global_address(neighbour);
if (global_address && global_address_ptr) {
memcpy(global_address, global_address_ptr, 16);
}
return rpl_instance_candidate_rank(neighbour);
}
}
return 0xffff;
}
bool rpl_possible_better_candidate(struct protocol_interface_info_entry *interface, rpl_instance_t *rpl_instance, const uint8_t ll_addr[16], uint16_t candidate_rank, uint16_t etx)
{
if (!interface->rpl_domain) {
return false;
}
rpl_neighbour_t *neighbour = rpl_lookup_neighbour_by_ll_address(rpl_instance, ll_addr, interface->id);
if (!neighbour) {
return false;
}
return rpl_instance_possible_better_candidate(rpl_instance, neighbour, candidate_rank, etx);
}
uint16_t rpl_control_parent_candidate_list_size(protocol_interface_info_entry_t *interface, bool parent_list)
{
if (!interface->rpl_domain) {
return 0;
}
uint16_t parent_list_size = 0;
// go through instances and parents and check if they match the address.
ns_list_foreach(struct rpl_instance, instance, &interface->rpl_domain->instances) {
uint16_t current_size = rpl_instance_address_candidate_count(instance, parent_list);
if (current_size > parent_list_size) {
parent_list_size = current_size;
}
}
return parent_list_size;
}
void rpl_control_neighbor_delete_from_instance(protocol_interface_info_entry_t *interface, rpl_instance_t *instance, const uint8_t ll_addr[16])
{
rpl_neighbour_t *neighbour = rpl_lookup_neighbour_by_ll_address(instance, ll_addr, interface->id);
if (neighbour) {
rpl_delete_neighbour(instance, neighbour);
}
}
void rpl_control_neighbor_delete(protocol_interface_info_entry_t *interface, const uint8_t ll_addr[16])
{
if (!interface->rpl_domain) {
return;
}
// go through instances and delete address.
ns_list_foreach(struct rpl_instance, instance, &interface->rpl_domain->instances) {
rpl_control_neighbor_delete_from_instance(interface, instance, ll_addr);
}
}
bool rpl_control_find_worst_neighbor(protocol_interface_info_entry_t *interface, rpl_instance_t *rpl_instance, uint8_t ll_addr[static 16])
{
if (!interface->rpl_domain) {
return false;
}
rpl_neighbour_t *neighbour = rpl_lookup_last_candidate_from_list(rpl_instance);
if (neighbour) {
memcpy(ll_addr, rpl_neighbour_ll_address(neighbour), 16);
return true;
}
return false;
}
/* Address changes need to trigger DAO target re-evaluation */
static void rpl_control_addr_notifier(struct protocol_interface_info_entry *interface, const if_address_entry_t *addr, if_address_callback_t reason)
{
/* Don't care about link-local addresses */
if (addr_is_ipv6_link_local(addr->address)) {
return;
}
/* Only publish addresses on the domain attached to their interface */
if (!interface->rpl_domain) {
return;
}
switch (reason) {
case ADDR_CALLBACK_DELETED:
rpl_control_unpublish_address(interface->rpl_domain, addr->address);
break;
default:
break;
}
}
static void rpl_control_etx_change_callback(int8_t nwk_id, uint16_t previous_etx, uint16_t current_etx, uint8_t attribute_index, const uint8_t *mac64)
{
protocol_interface_info_entry_t *cur = protocol_stack_interface_info_get_by_id(nwk_id);
if (!cur || !cur->rpl_domain) {
return;
}
(void) attribute_index;
// ETX is "better" if now lower, or previous was "unknown" and new isn't infinite
bool better = current_etx < previous_etx || (previous_etx == 0 && current_etx != 0xffff);
rpl_domain_t *domain = cur->rpl_domain;
uint16_t delay = rpl_policy_etx_change_parent_selection_delay(domain);
tr_debug("Triggering parent selection due to ETX %s on neigh index %u, etx %u", better ? "better" : "worse", attribute_index, current_etx);
rpl_dodag_t *dodag = NULL;
//Define Link Local Address
uint8_t ll_parent_address[16];
memcpy(ll_parent_address, ADDR_LINK_LOCAL_PREFIX, 8);
memcpy(ll_parent_address + 8, mac64, 8);
ll_parent_address[8] ^= 2;
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
if (rpl_instance_am_root(instance)) {
rpl_downward_paths_invalidate(instance);
} else {
if (better) {
//Only react here for candidate updates and when DODAG version is configured
if (rpl_instance_address_is_candidate(instance, ll_parent_address, 0)) {
dodag = rpl_instance_current_dodag(instance);
if (dodag) {
rpl_instance_trigger_parent_selection(instance, delay, dodag);
}
}
} else if (rpl_instance_address_is_parent(instance, ll_parent_address)) {
//Quick reaction for selected parent only
rpl_instance_trigger_parent_selection(instance, delay, NULL);
}
}
}
}
/* Create a RPL domain - it can be associated with one or more interfaces */
rpl_domain_t *rpl_control_create_domain(void)
{
rpl_domain_t *domain = rpl_alloc(sizeof(rpl_domain_t));
if (!domain) {
return NULL;
}
ns_list_init(&domain->instances);
domain->non_storing_downstream_interface = -1;
domain->callback = NULL;
domain->new_parent_add = NULL;
domain->parent_dis = NULL;
domain->prefix_cb = NULL;
domain->cb_handle = NULL;
domain->force_leaf = false;
domain->process_routes = true;
ns_list_add_to_start(&rpl_domains, domain);
addr_notification_register(rpl_control_addr_notifier);
return domain;
}
void rpl_control_delete_domain(rpl_domain_t *domain)
{
ns_list_foreach_safe(rpl_instance_t, instance, &domain->instances) {
rpl_delete_instance(instance);
}
ns_list_remove(&rpl_domains, domain);
rpl_free(domain, sizeof * domain);
}
static void rpl_control_remove_interface_from_domain(protocol_interface_info_entry_t *cur, rpl_domain_t *domain, bool free_instances)
{
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
rpl_instance_remove_interface(instance, cur->id);
}
ns_list_foreach(if_address_entry_t, addr, &cur->ip_addresses) {
if (!addr_is_ipv6_link_local(addr->address)) {
rpl_control_unpublish_address(domain, addr->address);
}
}
if (free_instances) {
ns_list_foreach_safe(rpl_instance_t, instance, &domain->instances) {
rpl_delete_instance(instance);
}
}
if (domain->non_storing_downstream_interface == cur->id) {
domain->non_storing_downstream_interface = -1;
}
}
void rpl_control_set_domain_on_interface(protocol_interface_info_entry_t *cur, rpl_domain_t *domain, bool downstream)
{
if (cur->rpl_domain != domain) {
rpl_control_remove_domain_from_interface(cur);
cur->rpl_domain = domain;
addr_add_group(cur, ADDR_LINK_LOCAL_ALL_RPL_NODES);
}
if (downstream) {
domain->non_storing_downstream_interface = cur->id;
}
/* This is a bit icky - why assume that our Objective Functions use ETX? */
/* But this is the easiest place to add an interface registration */
etx_value_change_callback_register(cur->nwk_id, cur->id, rpl_policy_etx_hysteresis(domain), rpl_control_etx_change_callback);
}
void rpl_control_remove_domain_from_interface(protocol_interface_info_entry_t *cur)
{
if (cur->rpl_domain) {
rpl_control_remove_interface_from_domain(cur, cur->rpl_domain, false);
addr_delete_group(cur, ADDR_LINK_LOCAL_ALL_RPL_NODES);
cur->rpl_domain = NULL;
}
}
void rpl_control_free_domain_instances_from_interface(protocol_interface_info_entry_t *cur)
{
if (cur->rpl_domain) {
rpl_control_remove_interface_from_domain(cur, cur->rpl_domain, true);
addr_delete_group(cur, ADDR_LINK_LOCAL_ALL_RPL_NODES);
cur->rpl_domain = NULL;
}
}
void rpl_control_set_callback(rpl_domain_t *domain, rpl_domain_callback_t callback, rpl_prefix_callback_t prefix_learn_cb, rpl_new_parent_callback_t new_parent_add, rpl_parent_dis_callback_t parent_dis, void *cb_handle)
{
domain->callback = callback;
domain->prefix_cb = prefix_learn_cb;
domain->cb_handle = cb_handle;
domain->new_parent_add = new_parent_add;
domain->parent_dis = parent_dis;
}
/* To do - this should live somewhere nicer. Basically a bootstrap
* thing to stop being a host as soon as we get our first RPL parent
* (any instance?)
*/
void rpl_control_disable_ra_routes(struct rpl_domain *domain)
{
int8_t last_id = -1;
protocol_interface_info_entry_t *cur;
while ((cur = protocol_stack_interface_info_get_by_rpl_domain(domain, last_id)) != NULL) {
icmpv6_recv_ra_routes(cur, false);
last_id = cur->id;
}
}
bool rpl_control_have_dodag(rpl_domain_t *domain)
{
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
if (rpl_instance_current_dodag(instance)) {
return true;
}
}
return false;
}
typedef void rpl_control_predicate_loop_fn_t(rpl_instance_t *instance, rpl_dodag_version_t *version, void *arg);
typedef struct rpl_loopfn_trigger_unicast_dio_arg {
struct protocol_interface_info_entry *interface;
const uint8_t *dst;
} rpl_loopfn_trigger_unicast_dio_arg_t;
/* Callbacks for rpl_control_predicate_loop */
static void rpl_loopfn_reset_dio_timer(rpl_instance_t *instance, rpl_dodag_version_t *dodag_version, void *handle)
{
(void)dodag_version;
(void)handle;
rpl_instance_inconsistency(instance);
//Check was Multicast DIS from parent
rpl_loopfn_trigger_unicast_dio_arg_t *arg = handle;
rpl_domain_t *domain = arg->interface->rpl_domain;
if (domain && domain->parent_dis) {
if (rpl_instance_address_is_parent(instance, arg->dst)) {
// Call Multicast DIS parent Callback
domain->parent_dis(arg->dst, arg->interface, instance);
}
}
}
static void rpl_loopfn_trigger_unicast_dio(rpl_instance_t *instance, rpl_dodag_version_t *dodag_version, void *handle)
{
(void)dodag_version;
rpl_loopfn_trigger_unicast_dio_arg_t *arg = handle;
rpl_instance_dio_trigger(instance, arg->interface, arg->dst);
}
/* For each DODAG Version we're a member of, matching the predicates, call the
* provided function. Mainly used for handling DODAG Information Solicitations.
*/
static void rpl_control_predicate_loop(rpl_domain_t *domain, rpl_control_predicate_loop_fn_t *fn, void *fn_arg, uint8_t pred, uint8_t instance_id, const uint8_t *dodagid, uint8_t version_num)
{
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
rpl_dodag_version_t *version = rpl_instance_predicate_match(instance, pred, instance_id, dodagid, version_num);
if (version) {
fn(instance, version, fn_arg);
}
}
}
/* Manipulation of roots */
rpl_dodag_t *rpl_control_create_dodag_root(rpl_domain_t *domain, uint8_t instance_id, const uint8_t *dodagid, const rpl_dodag_conf_t *conf, uint16_t rank, uint8_t g_mop_prf)
{
rpl_instance_t *instance = rpl_lookup_instance(domain, instance_id, dodagid);
if (!instance) {
instance = rpl_create_instance(domain, instance_id);
if (!instance) {
return NULL;
}
}
rpl_dodag_t *dodag = rpl_lookup_dodag(instance, dodagid);
if (dodag) {
if (rpl_dodag_am_root(dodag)) {
tr_error("Root DODAG already exists");
return NULL;
}
// Delete non root information and recreate dodag
rpl_delete_dodag(dodag);
}
dodag = rpl_create_dodag(instance, dodagid, g_mop_prf);
if (!dodag) {
tr_error("No mem for DODAG root");
return NULL;
}
rpl_dodag_conf_int_t internal_conf;
rpl_control_convert_internal_config(&internal_conf, conf);
rpl_dodag_update_config(dodag, &internal_conf, NULL, NULL);
rpl_dodag_set_root(dodag, true);
rpl_dodag_version_t *version = rpl_create_dodag_version(dodag, rpl_seq_init());
if (!version) {
rpl_delete_dodag(dodag);
tr_error("No mem for DODAG root");
return NULL;
}
rpl_instance_set_dodag_version(instance, version, rank);
return dodag;
}
void rpl_control_delete_dodag_root(rpl_domain_t *domain, rpl_dodag_t *dodag)
{
(void)domain;
rpl_delete_dodag_root(dodag);
}
void rpl_control_update_dodag_route(rpl_dodag_t *dodag, const uint8_t *prefix, uint8_t prefix_len, uint8_t flags, uint32_t lifetime, bool age)
{
/* Not clear if non-root nodes should be able to publish routes */
if (rpl_dodag_am_root(dodag)) {
rpl_dodag_update_dio_route(dodag, prefix, prefix_len, flags, lifetime, age);
}
}
void rpl_control_update_dodag_prefix(rpl_dodag_t *dodag, const uint8_t *prefix, uint8_t prefix_len, uint8_t flags, uint32_t lifetime, uint32_t preftime, bool age)
{
/* Don't let them set weird flags. We do allow them to add prefixes if not
* a root though - they might want this to add a L prefix?
*/
flags &= (PIO_A | PIO_L);
rpl_dodag_update_dio_prefix(dodag, prefix, prefix_len, flags, lifetime, preftime, /*publish=*/true, age);
}
void rpl_control_increment_dtsn(rpl_dodag_t *dodag)
{
rpl_dodag_increment_dtsn(dodag);
//rpl_dodag_inconsistency(dodag); currently implied by rpl_dodag_increment_dtsn
}
uint8_t rpl_control_increment_dodag_version(rpl_dodag_t *dodag)
{
uint8_t new_version = 240;
if (rpl_dodag_am_root(dodag)) {
new_version = rpl_seq_inc(rpl_dodag_get_version_number_as_root(dodag));
rpl_dodag_set_version_number_as_root(dodag, new_version);
}
return new_version;
}
void rpl_control_update_dodag_config(struct rpl_dodag *dodag, const rpl_dodag_conf_t *conf)
{
if (rpl_dodag_am_root(dodag)) {
rpl_dodag_conf_int_t internal_conf;
rpl_control_convert_internal_config(&internal_conf, conf);
rpl_dodag_update_config(dodag, &internal_conf, NULL, NULL);
}
}
void rpl_control_set_dodag_pref(rpl_dodag_t *dodag, uint8_t pref)
{
if (rpl_dodag_am_root(dodag)) {
rpl_dodag_set_pref(dodag, pref);
}
}
void rpl_control_poison(rpl_domain_t *domain, uint8_t count)
{
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
rpl_instance_poison(instance, count);
}
}
void rpl_control_force_leaf(rpl_domain_t *domain, bool leaf)
{
domain->force_leaf = leaf;
if (leaf) {
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
rpl_instance_force_leaf(instance);
}
}
}
void rpl_control_dao_timeout(rpl_domain_t *domain, uint16_t seconds)
{
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
rpl_instance_dao_timeout(instance, seconds);
}
}
void rpl_control_process_routes(rpl_domain_t *domain, bool process_routes)
{
domain->process_routes = process_routes;
}
/* Check whether the options section of a RPL control message is well-formed */
static bool rpl_control_options_well_formed(const uint8_t *dptr, uint_fast16_t dlen)
{
while (dlen) {
uint_fast16_t opt_len;
if (dptr[0] == RPL_PAD1_OPTION) {
opt_len = 1;
} else {
if (dlen < 2) {
return false;
}
opt_len = 2 + dptr[1];
}
if (opt_len > dlen) {
return false;
}
dptr += opt_len;
dlen -= opt_len;
}
return true;
}
static bool rpl_control_options_well_formed_in_buffer(const buffer_t *buf, uint16_t offset)
{
if (buffer_data_length(buf) < offset) {
return false;
}
return rpl_control_options_well_formed(buffer_data_pointer(buf) + offset,
buffer_data_length(buf) - offset);
}
/*
* Search for the first option of the specified type (and optionally length).
* Caller must have already checked the options are well-formed.
* Returns pointer to type byte, so length is at +1, data at +2.
*/
static const uint8_t *rpl_control_find_option(const uint8_t *dptr, uint_fast16_t dlen, uint8_t option, uint8_t optlen)
{
while (dlen) {
uint8_t type = dptr[0];
if (type == RPL_PAD1_OPTION) {
dptr++, dlen--;
continue;
}
uint_fast16_t len = dptr[1];
if (type == option && (optlen == 0 || optlen == len)) {
return dptr;
}
dlen -= 2 + len;
dptr += 2 + len;
}
return NULL;
}
static const uint8_t *rpl_control_find_option_in_buffer(const buffer_t *buf, uint_fast16_t offset, uint8_t option, uint8_t optlen)
{
return rpl_control_find_option(buffer_data_pointer(buf) + offset,
buffer_data_length(buf) - offset,
option, optlen);
}
/* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Type = 0x04 |Opt Length = 14| Flags |A| PCS | DIOIntDoubl. |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | DIOIntMin. | DIORedun. | MaxRankIncrease |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | MinHopRankIncrease | OCP |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved | Def. Lifetime | Lifetime Unit |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* Figure 24: Format of the DODAG Configuration Option
*/
static const uint8_t *rpl_control_read_conf(rpl_dodag_conf_int_t *conf_out, const uint8_t *opt)
{
conf_out->options = opt[2];
conf_out->dio_interval_doublings = opt[3];
conf_out->dio_interval_min = opt[4];
conf_out->dio_redundancy_constant = opt[5];
conf_out->dag_max_rank_increase = common_read_16_bit(opt + 6);
conf_out->min_hop_rank_increase = common_read_16_bit(opt + 8);
conf_out->objective_code_point = common_read_16_bit(opt + 10);
conf_out->reserved = opt[12];
conf_out->default_lifetime = opt[13];
conf_out->lifetime_unit = common_read_16_bit(opt + 14);
return opt + 16;
}
static uint8_t *rpl_control_write_conf(uint8_t *opt_out, const rpl_dodag_conf_int_t *conf)
{
opt_out[0] = RPL_DODAG_CONF_OPTION;
opt_out[1] = 14;
opt_out[2] = conf->options;
opt_out[3] = conf->dio_interval_doublings;
opt_out[4] = conf->dio_interval_min;
opt_out[5] = conf->dio_redundancy_constant;
common_write_16_bit(conf->dag_max_rank_increase, opt_out + 6);
common_write_16_bit(conf->min_hop_rank_increase, opt_out + 8);
common_write_16_bit(conf->objective_code_point, opt_out + 10);
opt_out[12] = conf->reserved;
opt_out[13] = conf->default_lifetime;
common_write_16_bit(conf->lifetime_unit, opt_out + 14);
return opt_out + 16;
}
static uint_fast8_t rpl_control_conf_length(void)
{
return 16;
}
/*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Type = 0x08 |Opt Length = 30| Prefix Length |L|A|R|Reserved1|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Valid Lifetime |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Preferred Lifetime |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Reserved2 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* + +
* | |
* + Prefix +
* | |
* + +
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* Figure 29: Format of the Prefix Information Option
*/
static void rpl_control_process_prefix_options(protocol_interface_info_entry_t *cur, rpl_instance_t *instance, rpl_dodag_t *dodag, rpl_neighbour_t *neighbour, const uint8_t *start, const uint8_t *end)
{
(void)cur;
bool router_addr_set = false;
rpl_neighbour_t *pref_parent = rpl_instance_preferred_parent(instance);
if (neighbour == pref_parent) {
rpl_dodag_update_unpublished_dio_prefix_start(dodag);
}
for (;;) {
const uint8_t *ptr = rpl_control_find_option(start, end - start, RPL_PREFIX_INFO_OPTION, 30);
if (!ptr) {
break;
}
uint8_t prefix_len = ptr[2];
uint8_t flags = ptr[3];
uint32_t valid = common_read_32_bit(ptr + 4);
uint32_t preferred = common_read_32_bit(ptr + 8);
const uint8_t *prefix = ptr + 16;
if (ws_info(cur)) {
//For Wi-SUN Interoperability force length to 64
prefix_len = 64;
}
if (rpl_upward_accept_prefix_update(dodag, neighbour, pref_parent)) {
/* Store prefixes for possible forwarding */
/* XXX if leaf - don't bother? Or do we want to remember them for
* when we switch DODAG, as mentioned above?
*/
prefix_entry_t *prefix_entry = rpl_dodag_update_dio_prefix(dodag, prefix, prefix_len, flags, valid, preferred, false, true);
if (prefix_entry && pref_parent) {
rpl_control_process_prefix_option(prefix_entry, cur);
rpl_domain_t *domain = cur->rpl_domain;
if (domain && domain->prefix_cb) {
uint8_t ll_address[16];
memcpy(ll_address, rpl_neighbour_ll_address(pref_parent), 16);
domain->prefix_cb(prefix_entry, domain->cb_handle, ll_address);
}
}
}
if ((flags & PIO_R) && !router_addr_set) {
/* Routers can have multiple global addresses - one for each
* prefix being advertised. We don't attempt to track this;
* we just remember their first-listed address.
* (
*/
router_addr_set = true;
rpl_neighbour_update_global_address(neighbour, ptr + 16);
}
start = ptr + 32;
}
if (neighbour == pref_parent) {
rpl_dodag_update_unpublished_dio_prefix_finish(dodag);
}
}
void rpl_control_process_prefix_option(prefix_entry_t *prefix, protocol_interface_info_entry_t *cur)
{
//Check is L Flag active
if (prefix->options & PIO_L) {
//define ONLink Route Information
//tr_debug("Register On Link Prefix to routing table");
ipv6_route_add(prefix->prefix, prefix->prefix_len, cur->id, NULL, ROUTE_RADV, prefix->lifetime, 0);
}
}
/*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Type = 0x03 | Option Length | Prefix Length |Resvd|Prf|Resvd|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Route Lifetime |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* . Prefix (Variable Length) .
* . .
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* Figure 23: Format of the Route Information Option
*/
static void rpl_control_process_route_options(rpl_instance_t *instance, rpl_dodag_t *dodag, rpl_dodag_version_t *version, rpl_neighbour_t *neighbour, uint16_t rank, const uint8_t *start, const uint8_t *end)
{
(void)neighbour;
/* For non-current DODAGs, it's simplest to accept routes from anyone. */
if (rpl_instance_current_dodag(instance) == dodag) {
/* It's for the current DODAG - more care required. Check versions */
rpl_cmp_t v_cmp = rpl_dodag_version_compare(version, rpl_instance_current_dodag_version(instance));
if (v_cmp & RPL_CMP_EQUAL) {
/* If coming from a neighbour in the same version, make sure they have lower rank */
if (!(rpl_rank_compare(dodag, rank, rpl_instance_current_rank(instance)) & RPL_CMP_LESS)) {
return;
}
} else if (v_cmp & RPL_CMP_GREATER) {
/* If coming from a neighbour in a newer version, we'll accept it */
} else {
/* From older or unknown version, we reject it */
return;
}
}
for (;;) {
const uint8_t *ptr = rpl_control_find_option(start, end - start, RPL_ROUTE_INFO_OPTION, 0);
if (!ptr) {
break;
}
uint8_t opt_len = ptr[1];
start = ptr + 2 + opt_len;
if (opt_len < 6) {
tr_warn("Malformed RIO");
continue;
}
uint8_t prefix_len = ptr[2];
uint8_t flags = ptr[3];
uint32_t lifetime = common_read_32_bit(ptr + 4);
const uint8_t *prefix = ptr + 8;
if (opt_len < 6 + (prefix_len + 7u) / 8) {
tr_warn("Malformed RIO");
continue;
}
rpl_dodag_update_dio_route(dodag, prefix, prefix_len, flags, lifetime, true);
}
/* We do not purge unadvertised routes. Thus if the root wants to purge
* a route before its lifetime is up, stopping advertising it is not
* sufficient, it has to advertise it with low or zero lifetime. This fits
* with ND Router Advertisement behaviour.
*/
}
static void rpl_control_dao_trigger_request(rpl_instance_t *instance, rpl_dodag_t *dodag, rpl_neighbour_t *neighbour)
{
switch (rpl_dodag_mop(dodag)) {
case RPL_MODE_NON_STORING:
rpl_instance_dao_request(instance, NULL);
rpl_dodag_increment_dtsn(dodag);
break;
case RPL_MODE_STORING:
case RPL_MODE_STORING_MULTICAST:
rpl_instance_dao_request(instance, neighbour);
break;
default:
/* Nothing */
break;
}
}
/*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | RPLInstanceID |Version Number | Rank |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |G|0| MOP | Prf | DTSN | Flags | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* + +
* | |
* + DODAGID +
* | |
* + +
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Option(s)...
* +-+-+-+-+-+-+-+-+
*
* Figure 14: The DIO Base Object
*/
static buffer_t *rpl_control_dio_handler(protocol_interface_info_entry_t *cur, rpl_domain_t *domain, buffer_t *buf)
{
if (!rpl_control_options_well_formed_in_buffer(buf, 24)) {
malformed:
protocol_stats_update(STATS_RPL_MALFORMED_MESSAGE, 1);
return buffer_free(buf);
}
/* Read the base object */
const uint8_t *ptr = buffer_data_pointer(buf);
uint8_t instance_id, version_num, g_mop_prf, dtsn;
uint16_t rank;
const uint8_t *dodagid;
bool become_leaf = false;
instance_id = ptr[0];
version_num = ptr[1];
rank = common_read_16_bit(ptr + 2);
g_mop_prf = ptr[4];
dtsn = ptr[5];
dodagid = ptr + 8;
ptr += 24;
tr_info("DIO from %s, rank %x", trace_ipv6(buf->src_sa.address), rank);
if (addr_is_ipv6_link_local(dodagid) || addr_is_ipv6_multicast(dodagid)) {
tr_error("DIO DODAGID");
goto malformed;
}
/* Check if this is for an existing RPL Instance - create if necessary */
rpl_instance_t *instance = rpl_lookup_instance(domain, instance_id, dodagid);
if (!instance) {
/* Policy can gate at this point, by instance ID and DODAG ID */
if (!rpl_policy_join_instance(domain, instance_id, dodagid)) {
tr_info("Policy ignoring instance (%d,%s)", instance_id, trace_ipv6(dodagid));
return buffer_free(buf);
}
instance = rpl_create_instance(domain, instance_id);
if (!instance) {
return buffer_free(buf);
}
}
/* Lookup any existing neighbour entry */
rpl_neighbour_t *neighbour = rpl_lookup_neighbour_by_ll_address(instance, buf->src_sa.address, cur->id);
/* From this point on, we are potentially updating an existing neighbour */
/* Any decision to reject the DIO, or failure to handle it, means removing that neighbour */
/* Any DIO with infinite rank is immediate poison - no further analysis required */
if (rank == RPL_RANK_INFINITE) {
goto invalid_parent;
}
/* Policy can gate by DODAGID and other bits */
if (!rpl_policy_join_dodag(domain, g_mop_prf, instance_id, dodagid)) {
tr_info("Policy ignoring DODAG (%02x,%s)", g_mop_prf, trace_ipv6(dodagid));
goto invalid_parent;
}
/* Look up and create the DODAG structure */
rpl_dodag_t *dodag = rpl_lookup_dodag(instance, dodagid);
if (!dodag) {
/* Policy can gate by DODAGID and other bits */
if (!rpl_policy_join_dodag(domain, g_mop_prf, instance_id, dodagid)) {
tr_info("Policy ignoring DODAG (%02x,%s)", g_mop_prf, trace_ipv6(dodagid));
goto invalid_parent;
}
dodag = rpl_create_dodag(instance, dodagid, g_mop_prf);
if (!dodag) {
goto invalid_parent;
}
}
/* Never listen to nodes in a DODAG we're rooting or were root*/
if (rpl_dodag_am_root(dodag) ||
rpl_dodag_was_root(dodag)) {
/* TODO - if version is newer or unordered, increment our version to be higher? */
/* Old code had this trick - actually, would need to go further. Want to listen first, then use a higher
* than existing. */
/*
uint8_t our_version = rpl_dodag_get_version_number_as_root(dodag);
if (rpl_seq_compare(version_num, our_version) & (RPL_CMP_UNORDERED|RPL_CMP_GREATER))
*/
goto invalid_parent;
}
/* Even if we're not currently rooting - what if it's our address? Ignore stale info on network */
if (addr_interface_address_compare(cur, dodagid) == 0) {
tr_info("DIO our DODAGID %s", trace_ipv6(dodagid));
/* Should we transmit poison? */
goto invalid_parent;
}
/* Update DODAG config information, if option present, and either we don't have it or version is newer */
const uint8_t *dodag_conf_ptr = rpl_control_find_option_in_buffer(buf, 24, RPL_DODAG_CONF_OPTION, 14);
if (dodag_conf_ptr) {
rpl_dodag_conf_int_t conf_buf;
rpl_control_read_conf(&conf_buf, dodag_conf_ptr);
if (!rpl_dodag_update_config(dodag, &conf_buf, buf->src_sa.address, &become_leaf)) {
goto invalid_parent;
}
}
/* If we don't have any DODAG config information, ask by unicast DIS */
const rpl_dodag_conf_int_t *conf = rpl_dodag_get_config(dodag);
if (!conf) {
/* TODO - rate limit DIS? */
if (domain->new_parent_add && !domain->new_parent_add(buf->src_sa.address, domain->cb_handle, instance, rank)) {
goto invalid_parent;
}
rpl_control_transmit_dis(domain, cur, RPL_SOLINFO_PRED_DODAGID | RPL_SOLINFO_PRED_INSTANCEID, instance_id, dodagid, 0, buf->src_sa.address);
goto invalid_parent;
}
/* Check whether config is acceptable */
if (!rpl_policy_join_config(domain, conf, &become_leaf)) {
goto invalid_parent;
}
/* Lookup or create DODAG Version state info */
rpl_dodag_version_t *version = rpl_lookup_dodag_version(dodag, version_num);
if (!version) {
version = rpl_create_dodag_version(dodag, version_num);
if (!version) {
goto invalid_parent;
}
}
const uint8_t *metric_ptr = rpl_control_find_option_in_buffer(buf, 24, RPL_DAG_METRIC_OPTION, 0);
/* We currently don't understand anything about metrics, so to be on the safe side, we don't join */
if (metric_ptr) {
become_leaf = true;
}
/* We mustn't process DIOs from our potential sub-DODAG, unless local repair is ongoing */
if (!rpl_instance_local_repair(instance) && rpl_dodag_version_rank_indicates_possible_sub_dodag(version, rank)) {
goto invalid_parent;
}
/* RFC 6550 8.3: A DIO from a sender with lesser DAGRank that causes no
* changes to the recipient's parent set, preferred parent, or Rank SHOULD
* be considered consistent with respect to the Trickle timer.
*
* Now, if we don't run parent selection immediately, how do we know if it's
* consistent or not? Compromise is to treat all lower ranked DIOs as
* consistent, and reset (and hold) the consistent counter to 0 if any of
* the above change. This actually seems better than the RFC 6550 rule, as
* it guarantees we will transmit if those change. The rule as stated
* would mean a large number of parent messages would stop us advertising
* a Rank change.
*/
if (version == rpl_instance_current_dodag_version(instance) &&
(rpl_rank_compare(dodag, rank, rpl_instance_current_rank(instance)) & RPL_CMP_LESS)) {
rpl_instance_consistent_rx(instance);
}
/* Now we create the neighbour, if we don't already have a record */
if (!neighbour) {
if (domain->new_parent_add) {
if (!domain->new_parent_add(buf->src_sa.address, domain->cb_handle, instance, rank)) {
goto invalid_parent;
}
}
neighbour = rpl_create_neighbour(version, buf->src_sa.address, cur->id, g_mop_prf, dtsn);
//Call Here new parent create
if (!neighbour) {
goto invalid_parent;
}
}
/* Update neighbour info */
rpl_neighbour_update_dodag_version(neighbour, version, rank, g_mop_prf);
if (rpl_neighbour_update_dtsn(neighbour, dtsn)) {
tr_info("Parent %s incremented DTSN", trace_ipv6(buf->src_sa.address));
rpl_control_dao_trigger_request(instance, dodag, neighbour);
}
rpl_control_process_prefix_options(cur, instance, dodag, neighbour, ptr, buffer_data_end(buf));
//rpl_dodag_update_implicit_system_routes(dodag, neighbour);
rpl_control_process_route_options(instance, dodag, version, neighbour, rank, ptr, buffer_data_end(buf));
//rpl_control_process_metric_containers(neighbour, ptr, buffer_data_end(buf))
if (become_leaf) {
rpl_dodag_set_leaf(dodag, true);
}
rpl_instance_neighbours_changed(instance, dodag);
return buffer_free(buf);
invalid_parent:
if (neighbour) {
rpl_delete_neighbour(instance, neighbour);
}
return buffer_free(buf);
}
static void rpl_control_transmit_one_interface(protocol_interface_info_entry_t *cur, buffer_t *buf)
{
/* If destination is global, this will end up routed, regardless of original interface */
/* (But we do currently need an interface specified anyway?) */
if (cur) {
buf->interface = cur;
}
/* RPL requires us to use link-local source for all messages except DAO/DAO-ACK
* in storing mode, which use global source. Default address selection in
* icmpv6_down should do the right thing, as long as the interface does have
* both LL and global, so don't bother to set source here.
*/
protocol_push(buf);
}
static void rpl_control_transmit_all_interfaces(rpl_domain_t *domain, buffer_t *buf)
{
protocol_interface_info_entry_t *first_if = NULL, *cur;
int8_t last_id = -1;
while ((cur = protocol_stack_interface_info_get_by_rpl_domain(domain, last_id)) != NULL) {
if (!first_if) {
/* Note first interface - it will get original buffer after loop */
first_if = cur;
} else {
/* This is a subsequent interface - send a clone */
buffer_t *clone = buffer_clone(buf);
if (clone) {
rpl_control_transmit_one_interface(cur, clone);
}
}
last_id = cur->id;
}
if (first_if) {
rpl_control_transmit_one_interface(first_if, buf);
} else {
// RPL domain with no interfaces? Odd...
buffer_free(buf);
}
}
/* Complete and send a RPL control message - all interfaces multicast if dst+cur are NULL, else unicast */
void rpl_control_transmit(rpl_domain_t *domain, protocol_interface_info_entry_t *cur, uint8_t code, buffer_t *buf, const uint8_t *dst)
{
buf->info = (buffer_info_t)(B_FROM_ICMP | B_TO_ICMP | B_DIR_DOWN);
buf->options.type = ICMPV6_TYPE_INFO_RPL_CONTROL;
buf->options.code = code;
buf->dst_sa.addr_type = ADDR_IPV6;
memcpy(buf->dst_sa.address, dst ? dst : ADDR_LINK_LOCAL_ALL_RPL_NODES, 16);
/* Use 255 hop limit for link-local stuff, akin to other ICMP */
/* Others set "0", which means use interface default */
buf->options.hop_limit = addr_ipv6_scope(buf->dst_sa.address, cur) <= IPV6_SCOPE_LINK_LOCAL ? 255 : 0;
if (code == ICMPV6_CODE_RPL_DAO || code == ICMPV6_CODE_RPL_DAO_ACK || buf->dst_sa.address[0] != 0xff) {
//DAO and DAO ACK and unicast traffic with Higher priority
buf->options.traffic_class = IP_DSCP_CS6 << IP_TCLASS_DSCP_SHIFT;
}
if (dst == NULL && cur == NULL) {
rpl_control_transmit_all_interfaces(domain, buf);
} else {
/* Fudge - need a dummy interface for non-storing DAO to root - won't
* actually be used as it'll get globally routed.
*/
if (!cur) {
cur = protocol_stack_interface_info_get_by_id(domain->non_storing_downstream_interface);
}
rpl_control_transmit_one_interface(cur, buf);
}
}
/* Transmit a DIO (unicast or multicast); cur may be NULL if multicast */
void rpl_control_transmit_dio(rpl_domain_t *domain, protocol_interface_info_entry_t *cur, uint8_t instance_id, uint8_t dodag_version, uint16_t rank, uint8_t g_mop_prf, uint8_t dtsn, rpl_dodag_t *dodag, const uint8_t dodagid[16], const rpl_dodag_conf_int_t *conf, const uint8_t *dst)
{
uint16_t length;
const rpl_dio_route_list_t *routes = rpl_dodag_get_route_list(dodag);
const prefix_list_t *prefixes = rpl_dodag_get_prefix_list(dodag);
tr_info("transmit dio, rank: %x", rank);
protocol_interface_info_entry_t *downstream_if = protocol_stack_interface_info_get_by_id(domain->non_storing_downstream_interface);
length = 24;
if (conf) {
length += rpl_control_conf_length();
}
ns_list_foreach(prefix_entry_t, prefix, prefixes) {
/* We must not forward 'L' prefixes */
if ((prefix->options & (PIO_L | RPL_PIO_PUBLISHED)) == PIO_L) {
continue;
}
/* In storing mode, do not forward a prefix until we have an
* address for it. (RFC 6550 6.7.10 - "A non-storing node SHOULD
* refrain from advertising a prefix until it owns an address of
* that prefix, and then it SHOULD advertise its full address in
* this field, with the 'R' flag set.
*/
/* XXX We must also be advertising the corresponding address as a DAO target */
const uint8_t *addr = downstream_if ? addr_select_with_prefix(downstream_if, prefix->prefix, prefix->prefix_len, 0) : NULL;
if (addr) {
prefix->options |= PIO_R;
memcpy(prefix->prefix, addr, 16);
} else {
prefix->options &= ~ PIO_R;
if (rpl_dodag_mop(dodag) == RPL_MODE_NON_STORING && (prefix->lifetime != 0 || !(prefix->options & PIO_A))) {
continue;
}
}
length += 32;
}
ns_list_foreach(rpl_dio_route_t, route, routes) {
length += 8 + (route->prefix_len + 7u) / 8;
}
/* Add metric lengths here */
buffer_t *buf = buffer_get(length);
if (!buf) {
return;
}
uint8_t *ptr = buffer_data_pointer(buf);
ptr[0] = instance_id;
ptr[1] = dodag_version;
common_write_16_bit(rank, ptr + 2);
ptr[4] = g_mop_prf;
ptr[5] = dtsn;
ptr[6] = 0;
ptr[7] = 0;
memcpy(ptr + 8, dodagid, 16);
ptr += 24;
if (conf) {
ptr = rpl_control_write_conf(ptr, conf);
}
/* Write prefix/route/metric options here */
ns_list_foreach_safe(prefix_entry_t, prefix, prefixes) {
/* See equivalent checks in length calculation above */
if ((prefix->options & (PIO_L | RPL_PIO_PUBLISHED)) == PIO_L ||
(!(prefix->options & PIO_R) && rpl_dodag_mop(dodag) == RPL_MODE_NON_STORING && (prefix->lifetime != 0 || !(prefix->options & PIO_A)))) {
continue;
}
ptr[0] = RPL_PREFIX_INFO_OPTION;
ptr[1] = 30;
ptr[2] = prefix->prefix_len;
ptr[3] = prefix->options & (PIO_R | PIO_A | PIO_L);
common_write_32_bit(prefix->lifetime, ptr + 4);
common_write_32_bit(prefix->preftime, ptr + 8);
common_write_32_bit(0, ptr + 12); // reserved
memcpy(ptr + 16, prefix->prefix, 16);
ptr += 32;
/* Transmitting a multicast DIO decrements the hold count for 0 lifetime prefixes */
if (dst == NULL && (prefix->options & RPL_PIO_AGE)) {
int hold_count = prefix->options & RPL_PIO_HOLD_MASK;
if (hold_count) {
hold_count--;
prefix->options = (prefix->options & ~RPL_PIO_HOLD_MASK) | hold_count;
}
}
}
ns_list_foreach_safe(rpl_dio_route_t, route, routes) {
uint8_t prefix_bytes = (route->prefix_len + 7u) / 8u;
ptr[0] = RPL_ROUTE_INFO_OPTION;
ptr[1] = 6 + prefix_bytes;
ptr[2] = route->prefix_len;
ptr[3] = route->flags;
common_write_32_bit(route->lifetime, ptr + 4);
bitcopy0(ptr + 8, route->prefix, route->prefix_len);
ptr += 8 + prefix_bytes;
/* Transmitting a multicast DIO decrements the hold count for 0 lifetime routes */
if (dst == NULL && route->lifetime == 0) {
if (route->hold_count) {
route->hold_count--;
}
if (route->hold_count == 0) {
rpl_dodag_delete_dio_route(dodag, route);
}
}
}
buffer_data_end_set(buf, ptr);
rpl_control_transmit(domain, cur, ICMPV6_CODE_RPL_DIO, buf, dst);
}
/* 0 1 2
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Flags | Reserved | Option(s)...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* Figure 13: The DIS Base Object
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Type = 0x07 |Opt Length = 19| RPLInstanceID |V|I|D| Flags |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* + +
* | |
* + DODAGID +
* | |
* + +
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |Version Number |
* +-+-+-+-+-+-+-+-+
*
* Figure 28: Format of the Solicited Information Option
*/
static buffer_t *rpl_control_dis_handler(protocol_interface_info_entry_t *cur, rpl_domain_t *domain, buffer_t *buf, bool multicast)
{
if (!rpl_control_options_well_formed_in_buffer(buf, 2)) {
protocol_stats_update(STATS_RPL_MALFORMED_MESSAGE, 1);
return buffer_free(buf);
}
/* Base object irrelevant - find the Solicited Information option, if any */
const uint8_t *sol = rpl_control_find_option_in_buffer(buf, 2, RPL_SOL_INFO_OPTION, 19);
uint8_t preds, sol_instance_id, sol_version;
const uint8_t *sol_dodagid;
if (sol) {
preds = sol[3];
sol_instance_id = sol[2];
sol_dodagid = sol + 4;
sol_version = sol[20];
} else {
/* No option present means "match any" */
preds = 0;
sol_instance_id = 0;
sol_dodagid = NULL;
sol_version = 0;
}
/* If it's a multicast DIS, then we reset trickle timer for each matching
* instances.
*
* If it's a unicast DIS, we unicast a DIO back to the sender for each
* matching instance.
*/
rpl_loopfn_trigger_unicast_dio_arg_t arg;
arg.interface = cur;
arg.dst = buf->src_sa.address;
rpl_control_predicate_loop(domain,
multicast ? rpl_loopfn_reset_dio_timer : rpl_loopfn_trigger_unicast_dio,
&arg,
preds, sol_instance_id, sol_dodagid, sol_version);
return buffer_free(buf);
}
void rpl_control_transmit_dio_trigger(protocol_interface_info_entry_t *cur, struct rpl_domain *domain)
{
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
rpl_instance_dio_trigger(instance, cur, NULL);
}
}
void rpl_control_parent_selection_trigger(struct rpl_domain *domain)
{
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
rpl_instance_run_parent_selection(instance);
}
}
void rpl_control_transmit_dis(rpl_domain_t *domain, protocol_interface_info_entry_t *cur, uint8_t pred, uint8_t instance_id, const uint8_t *dodagid, const uint8_t version, const uint8_t *dst)
{
uint16_t length = 2;
if (pred) {
length += 2 + 19;
}
buffer_t *buf = buffer_get(length);
if (!buf) {
return;
}
uint8_t *ptr = buffer_data_pointer(buf);
*ptr++ = 0; // flags
*ptr++ = 0; // reserved
if (pred) {
ptr[0] = RPL_SOL_INFO_OPTION;
ptr[1] = 19;
if (pred & RPL_SOLINFO_PRED_INSTANCEID) {
ptr[2] = instance_id;
} else {
ptr[2] = 0;
}
ptr[3] = pred;
if (pred & RPL_SOLINFO_PRED_DODAGID) {
memcpy(ptr + 4, dodagid, 16);
} else {
memset(ptr + 4, 0, 16);
}
if (pred & RPL_SOLINFO_PRED_VERSION) {
ptr[20] = version;
} else {
ptr[20] = 0;
}
ptr += 21;
}
buffer_data_end_set(buf, ptr);
rpl_control_transmit(domain, cur, ICMPV6_CODE_RPL_DIS, buf, dst);
tr_info("Transmit DIS");
}
#ifdef HAVE_RPL_DAO_HANDLING
/*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | RPLInstanceID |D| Reserved | DAOSequence | Status |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* + +
* | |
* + DODAGID* +
* | |
* + +
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Option(s)...
* +-+-+-+-+-+-+-+-+
*
* Figure 17: The DAO ACK Base Object
*/
static void rpl_control_transmit_dao_ack(rpl_domain_t *domain, protocol_interface_info_entry_t *cur, uint8_t instance_id, uint8_t dao_sequence, uint8_t status, const uint8_t dodagid[16], const uint8_t *dst)
{
buffer_t *buf = buffer_get(dodagid ? 4 + 20 : 4);
if (!buf) {
return;
}
uint8_t *ptr = buffer_data_pointer(buf);
ptr[0] = instance_id;
ptr[1] = dodagid ? RPL_DAO_ACK_FLAG_DODAGID : 0;
ptr[2] = dao_sequence;
ptr[3] = status;
ptr += 4;
if (dodagid) {
memcpy(ptr, dodagid, 16);
ptr += 16;
}
buffer_data_end_set(buf, ptr);
rpl_control_transmit(domain, cur, ICMPV6_CODE_RPL_DAO_ACK, buf, dst);
tr_info("Transmit DAO-ACK to: %s", trace_ipv6(dst));
}
#endif // HAVE_RPL_DAO_HANDLING
static buffer_t *rpl_control_dao_ack_handler(protocol_interface_info_entry_t *cur, rpl_domain_t *domain, buffer_t *buf)
{
(void)cur;
if (buffer_data_length(buf) < 4) {
format_error:
protocol_stats_update(STATS_RPL_MALFORMED_MESSAGE, 1);
return buffer_free(buf);
}
const uint8_t *ptr = buffer_data_pointer(buf);
uint8_t instance_id = ptr[0];
const uint8_t *dodagid;
if (ptr[1] & RPL_DAO_ACK_FLAG_DODAGID) {
if (buffer_data_length(buf) < 4 + 16) {
goto format_error;
}
dodagid = ptr + 4;
} else {
dodagid = NULL;
}
rpl_instance_t *instance = rpl_lookup_instance(domain, instance_id, dodagid);
if (!instance) {
protocol_stats_update(STATS_RPL_UNKNOWN_INSTANCE, 1);
return buffer_free(buf);
}
uint8_t dao_sequence = ptr[2];
uint8_t status = ptr[3];
rpl_instance_dao_acked(instance, buf->src_sa.address, buf->interface->id, dao_sequence, status);
return buffer_free(buf);
}
/*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | RPLInstanceID |K|D| Flags | Reserved | DAOSequence |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* + +
* | |
* + DODAGID* +
* | |
* + +
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Option(s)...
* +-+-+-+-+-+-+-+-+
*
* Figure 16: The DAO Base Object
*/
bool rpl_control_transmit_dao(rpl_domain_t *domain, protocol_interface_info_entry_t *cur, rpl_instance_t *instance, uint8_t instance_id, uint8_t dao_sequence, const uint8_t dodagid[16], const uint8_t *opts, uint16_t opts_size, const uint8_t *dst)
{
uint8_t dodagid_flag = 0;
if (rpl_instance_id_is_local(instance_id)) {
dodagid_flag = RPL_DAO_FLAG_DODAGID;
}
uint8_t base_size = dodagid_flag ? 4 + 16 : 4;
buffer_t *buf = buffer_get(base_size + opts_size);
if (!buf) {
return true;
}
uint8_t *ptr = buffer_data_pointer(buf);
ptr[0] = instance_id;
ptr[1] = dodagid_flag;
if (rpl_policy_request_dao_acks(domain, rpl_instance_mop(instance))) {
ptr[1] |= RPL_DAO_FLAG_ACK_REQ;
}
ptr[2] = 0;
ptr[3] = dao_sequence;
if (dodagid_flag) {
memcpy(ptr + 4, dodagid, 16);
}
memcpy(ptr + base_size, opts, opts_size);
buffer_data_end_set(buf, ptr + base_size + opts_size);
rpl_control_transmit(domain, cur, ICMPV6_CODE_RPL_DAO, buf, dst);
return ptr[1] & RPL_DAO_FLAG_ACK_REQ;
}
#ifdef HAVE_RPL_DAO_HANDLING
static buffer_t *rpl_control_dao_handler(protocol_interface_info_entry_t *cur, rpl_domain_t *domain, buffer_t *buf, bool multicast)
{
if (buffer_data_length(buf) < 4) {
format_error:
protocol_stats_update(STATS_RPL_MALFORMED_MESSAGE, 1);
return buffer_free(buf);
}
const uint8_t *ptr = buffer_data_pointer(buf);
uint8_t instance_id = ptr[0];
uint8_t flags = ptr[1];
uint8_t dao_sequence = ptr[3];
const uint8_t *dodagid;
tr_info("DAO from %s", trace_ipv6(buf->src_sa.address));
ptr += 4;
if (flags & RPL_DAO_FLAG_DODAGID) {
if (buffer_data_length(buf) < 4 + 16) {
goto format_error;
}
dodagid = ptr;
ptr += 16;
} else {
dodagid = NULL;
}
rpl_instance_t *instance = rpl_lookup_instance(domain, instance_id, dodagid);
if (!instance) {
protocol_stats_update(STATS_RPL_UNKNOWN_INSTANCE, 1);
return buffer_free(buf);
}
uint16_t opts_len = buffer_data_end(buf) - ptr;
if (!rpl_control_options_well_formed(ptr, opts_len)) {
goto format_error;
}
#if 0
rpl_dodag_t *dodag = rpl_instance_current_dodag(instance);
if (!dodag) {
return buffer_free(buf);
}
uint8_t mode = rpl_dodag_mop(dodag);
switch (mo)!rpl_instance_am_root(instance))
/* No current processing - pretend to accept */
uint8_t status = 0;
}
#endif
uint8_t status;
bool reply_ok = rpl_instance_dao_received(instance, buf->src_sa.address, buf->interface->id, multicast, ptr, opts_len, &status);
/* Ack if requested or non-zero status */
if (reply_ok && ((flags &RPL_DAO_FLAG_ACK_REQ) || status != 0))
{
rpl_control_transmit_dao_ack(domain, cur, instance_id, dao_sequence, status, dodagid, buf->src_sa.address);
}
return buffer_free(buf);
}
#endif // HAVE_RPL_DAO_HANDLING
buffer_t *rpl_control_handler(buffer_t *buf)
{
protocol_interface_info_entry_t *cur = buf->interface;
rpl_domain_t *domain = cur ? cur->rpl_domain : NULL;
if (!domain) {
tr_warning("RPL control on non-RPL interface");
return buffer_free(buf);
}
bool multicast = addr_is_ipv6_multicast(buf->dst_sa.address);
if (addr_is_ipv6_multicast(buf->src_sa.address)) {
protocol_stats_update(STATS_RPL_MALFORMED_MESSAGE, 1);
return buffer_free(buf);
}
switch (buf->options.code) {
case ICMPV6_CODE_RPL_DIS:
return rpl_control_dis_handler(cur, domain, buf, multicast);
case ICMPV6_CODE_RPL_DIO:
return rpl_control_dio_handler(cur, domain, buf);
#ifdef HAVE_RPL_DAO_HANDLING
case ICMPV6_CODE_RPL_DAO:
return rpl_control_dao_handler(cur, domain, buf, multicast);
#endif
case ICMPV6_CODE_RPL_DAO_ACK:
return rpl_control_dao_ack_handler(cur, domain, buf);
default:
tr_warning("Unknown code 0x%02x", buf->options.code);
protocol_stats_update(STATS_RPL_MALFORMED_MESSAGE, 1);
return buffer_free(buf);
}
}
#ifdef HAVE_RPL_ROOT
/* Buffer contains ICMP payload, so 4 bytes unused, followed by invoking packet */
buffer_t *rpl_control_source_route_error_handler(buffer_t *buf, protocol_interface_info_entry_t *cur)
{
if (buffer_data_length(buf) < 40 || !cur->rpl_domain) {
return buf;
}
const uint8_t *target = buffer_data_pointer(buf) + 4 + 24; // Dest in IP header in ICMP invoking packet payload
const uint8_t *transit = buf->src_sa.address; // Source in IP header of ICMP packet
tr_warn("Source route error: %s->%s", trace_ipv6(transit), trace_ipv6(target));
/* We can't identify the instance - logically though it's instance-independent.
* If transit can't reach target, that applies to all instances.
*/
ns_list_foreach(rpl_instance_t, instance, &cur->rpl_domain->instances) {
rpl_downward_transit_error(instance, target, transit);
}
return buf;
}
#endif
void rpl_control_fast_timer(uint16_t ticks)
{
ns_list_foreach(rpl_domain_t, domain, &rpl_domains) {
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
rpl_upward_dio_timer(instance, ticks);
rpl_downward_dao_timer(instance, ticks);
}
}
}
#if 0
static void trace_info_print(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vtracef(TRACE_LEVEL_INFO, TRACE_GROUP, fmt, ap);
va_end(ap);
}
#endif
void rpl_control_slow_timer(uint16_t seconds)
{
bool purge = rpl_alloc_total > rpl_purge_threshold;
ns_list_foreach(rpl_domain_t, domain, &rpl_domains) {
ns_list_foreach_safe(rpl_instance_t, instance, &domain->instances) {
rpl_control_publish_own_addresses(domain, instance);
rpl_instance_slow_timer(instance, seconds);
rpl_downward_dao_slow_timer(instance, seconds);
/* We purge one item from each instance, so as not to favour one domain or instance */
if (purge) {
rpl_instance_purge(instance);
}
}
}
#if 0 // If including this, make sure to include the above trace_info_print helper function as well.
static int rpl_print_timer;
if ((rpl_print_timer += seconds) >= 50) {
rpl_print_timer = 0;
void arm_print_routing_table2(void (*print_fn)(const char *fmt, ...));
protocol_interface_info_entry_t *cur = protocol_stack_interface_info_get(IF_6LoWPAN);
if (cur) {
ipv6_neighbour_cache_print(&cur->ipv6_neighbour_cache, trace_info_print);
}
arm_print_routing_table2(trace_info_print);
rpl_control_print(trace_info_print);
}
#endif
}
rpl_instance_t *rpl_control_enumerate_instances(rpl_domain_t *domain, rpl_instance_t *instance)
{
if (instance) {
return ns_list_get_next(&domain->instances, instance);
} else {
return ns_list_get_first(&domain->instances);
}
}
rpl_instance_t *rpl_control_lookup_instance(rpl_domain_t *domain, uint8_t instance_id, const uint8_t *dodagid)
{
return rpl_lookup_instance(domain, instance_id, dodagid);
}
bool rpl_control_get_instance_dao_target_count(rpl_domain_t *domain, uint8_t instance_id, const uint8_t *dodagid, const uint8_t *prefix, uint16_t *target_count)
{
rpl_instance_t *instance = rpl_lookup_instance(domain, instance_id, dodagid);
if (!instance) {
return false;
}
*target_count = rpl_upward_read_dao_target_list_size(instance, prefix);
return true;
}
/* Backwards-compatibility implementation of net_rpl.h API designed for old implementation */
bool rpl_control_read_dodag_info(const rpl_instance_t *instance, rpl_dodag_info_t *dodag_info)
{
return rpl_upward_read_dodag_info(instance, dodag_info);
}
const rpl_dodag_conf_int_t *rpl_control_get_dodag_config(const rpl_instance_t *instance)
{
rpl_dodag_t *dodag = rpl_instance_current_dodag(instance);
if (!dodag) {
return NULL;
}
return rpl_dodag_get_config(dodag);
}
const uint8_t *rpl_control_preferred_parent_addr(const rpl_instance_t *instance, bool global)
{
const rpl_neighbour_t *parent = rpl_instance_preferred_parent(instance);
if (!parent) {
return NULL;
}
if (global) {
return rpl_neighbour_global_address(parent);
} else {
return rpl_neighbour_ll_address(parent);
}
}
uint16_t rpl_control_current_rank(const struct rpl_instance *instance)
{
return rpl_instance_current_rank(instance);
}
static void rpl_domain_print(const rpl_domain_t *domain, route_print_fn_t *print_fn)
{
print_fn("RPL Domain %p", (void *) domain);
ns_list_foreach(rpl_instance_t, instance, &domain->instances) {
rpl_upward_print_instance(instance, print_fn);
rpl_downward_print_instance(instance, print_fn);
}
}
uint16_t rpl_control_route_table_get(struct rpl_instance *instance, uint8_t *prefix, rpl_route_info_t *output_table, uint16_t output_table_len)
{
return rpl_downward_route_table_get(instance, prefix, output_table, output_table_len);
}
void rpl_control_print(route_print_fn_t *print_fn)
{
unsigned t = protocol_core_monotonic_time % 10;
unsigned s_full = protocol_core_monotonic_time / 10;
unsigned m = s_full / 60;
unsigned s = s_full % 60;
unsigned h = m / 60;
m %= 60;
// %zu doesn't work on some Mbed toolchains
print_fn("Time %02u:%02u:%02u.%u (%u.%u) RPL memory usage %" PRIu32, h, m, s, t, s_full, t, (uint32_t) rpl_alloc_total);
ns_list_foreach(rpl_domain_t, domain, &rpl_domains) {
rpl_domain_print(domain, print_fn);
}
}
uint8_t rpl_policy_mrhof_parent_set_size_get(const rpl_domain_t *domain)
{
return rpl_policy_mrhof_parent_set_size(domain);
}
#ifdef RPL_STRUCTURES_H_
#error "rpl_structures.h should not be included by rpl_control.c"
#endif
#endif /* HAVE_RPL */