mirror of https://github.com/ARMmbed/mbed-os.git
1411 lines
46 KiB
C
1411 lines
46 KiB
C
/*
|
|
* Copyright (c) 2008, 2010-2018, Arm Limited 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.
|
|
*/
|
|
/**
|
|
* \file address.c
|
|
* \brief Utility functions concernig addresses
|
|
*
|
|
* This file contains all the utility functions that can be used to
|
|
* check, manipulate etc. addresses.
|
|
*/
|
|
#include "nsconfig.h"
|
|
#include "ns_types.h"
|
|
#include "ip6string.h"
|
|
#include "ns_trace.h"
|
|
#include "randLIB.h"
|
|
#include "string.h"
|
|
#include "nsdynmemLIB.h"
|
|
#include "ns_sha256.h"
|
|
#include "socket_api.h"
|
|
#include "Common_Protocols/ipv6_constants.h"
|
|
#include "Common_Protocols/icmpv6.h"
|
|
#include "Common_Protocols/mld.h"
|
|
#include "6LoWPAN/Thread/thread_common.h"
|
|
#include "common_functions.h"
|
|
|
|
#include "RPL/rpl_control.h"
|
|
|
|
#define TRACE_GROUP "addr"
|
|
|
|
typedef struct addr_notification {
|
|
if_address_notification_fn *fn;
|
|
ns_list_link_t link;
|
|
} addr_notification_t;
|
|
|
|
static NS_LIST_DEFINE(addr_notifications, addr_notification_t, link);
|
|
|
|
typedef struct addr_policy_table_entry_t {
|
|
uint8_t prefix[16];
|
|
uint8_t prefix_len;
|
|
uint8_t precedence;
|
|
uint8_t label;
|
|
ns_list_link_t link;
|
|
} addr_policy_table_entry_t;
|
|
|
|
static NS_LIST_DEFINE(addr_policy_table, addr_policy_table_entry_t, link);
|
|
|
|
uint32_t addr_preferences_default = SOCKET_IPV6_PREFER_SRC_TMP | SOCKET_IPV6_PREFER_SRC_6LOWPAN_SHORT;
|
|
|
|
static void addr_policy_table_reset(void);
|
|
static void addr_max_entries_check(protocol_interface_info_entry_t *cur, if_address_source_t source);
|
|
|
|
const uint8_t ADDR_LINK_LOCAL_PREFIX[8] = { 0xfe, 0x80 };
|
|
const uint8_t ADDR_SHORT_ADR_SUFFIC[6] = { 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00};
|
|
|
|
const uint8_t ADDR_MULTICAST_SOLICITED[13] = { 0xff, 0x02, [11] = 0x01, 0xff};
|
|
const uint8_t ADDR_IF_LOCAL_ALL_NODES[16] = { 0xff, 0x01, [15] = 0x01 };
|
|
const uint8_t ADDR_IF_LOCAL_ALL_ROUTERS[16] = { 0xff, 0x01, [15] = 0x02 };
|
|
const uint8_t ADDR_LINK_LOCAL_ALL_NODES[16] = { 0xff, 0x02, [15] = 0x01 };
|
|
const uint8_t ADDR_LINK_LOCAL_ALL_ROUTERS[16] = { 0xff, 0x02, [15] = 0x02 };
|
|
const uint8_t ADDR_LINK_LOCAL_ALL_MLDV2_ROUTERS[16] = { 0xff, 0x02, [15] = 0x16 };
|
|
const uint8_t ADDR_LINK_LOCAL_MDNS[16] = { 0xff, 0x02, [15] = 0xfb };
|
|
const uint8_t ADDR_REALM_LOCAL_ALL_NODES[16] = { 0xff, 0x03, [15] = 0x01 };
|
|
const uint8_t ADDR_REALM_LOCAL_ALL_ROUTERS[16] = { 0xff, 0x03, [15] = 0x02 };
|
|
const uint8_t ADDR_SITE_LOCAL_ALL_ROUTERS[16] = { 0xff, 0x05, [15] = 0x02 };
|
|
const uint8_t ADDR_ALL_MPL_FORWARDERS[16] = { 0xff, 0x03, [15] = 0xfc };
|
|
const uint8_t ADDR_ALL_DHCP_RELAY_AGENTS_AND_SERVERS[16] = { 0xff, 0x02, [13] = 0x01, 0x00, 0x02 };
|
|
|
|
const uint8_t ADDR_IPV4_MAPPED_PREFIX[12] = { [10] = 0xff, 0xff };
|
|
const uint8_t ADDR_LOOPBACK[16] = { [15] = 1 };
|
|
const uint8_t ADDR_UNSPECIFIED[16] = { 0 }; /* Note a few bits of code check for pointer equality with ADDR_UNSPECIFIED */
|
|
#define ADDR_IPV4_COMPATIBLE ADDR_LOOPBACK /* First 96 bits match...*/
|
|
|
|
#define ADDR_MULTICAST_LINK_PREFIX ADDR_LINK_LOCAL_ALL_NODES /* ff02::xx */
|
|
#define ADDR_MULTICAST_REALM_PREFIX ADDR_ALL_MPL_FORWARDERS /* ff03::xx */
|
|
|
|
static const uint8_t *addr_iid_secret_key;
|
|
static const uint8_t *addr_initial_iid;
|
|
static uint8_t addr_iid_secret_key_len;
|
|
|
|
static bool addr_am_implicit_group_member(const uint8_t group[static 16])
|
|
{
|
|
static const uint8_t * const implicit_groups[] = {
|
|
ADDR_LINK_LOCAL_ALL_NODES,
|
|
ADDR_IF_LOCAL_ALL_NODES,
|
|
};
|
|
|
|
for (uint_fast8_t i = 0; i < sizeof implicit_groups / sizeof implicit_groups[0]; i++) {
|
|
if (addr_ipv6_equal(implicit_groups[i], group)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint8_t addr_len_from_type(addrtype_t addr_type)
|
|
{
|
|
switch (addr_type) {
|
|
case ADDR_NONE:
|
|
return 0;
|
|
case ADDR_802_15_4_SHORT:
|
|
return 2 + 2; /* Some users don't have the PAN ID */
|
|
case ADDR_802_15_4_LONG:
|
|
return 2 + 8;
|
|
case ADDR_EUI_48:
|
|
return 6;
|
|
case ADDR_IPV6:
|
|
return 16;
|
|
case ADDR_BROADCAST:
|
|
return 0; /* Don't really handle this */
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Check if an address is a broadcast address
|
|
*
|
|
* \param addr pointer to an address_t containing the address to be checked
|
|
* \param addr_type the type of the address_t
|
|
* \return 0 if the address is a broadcast address
|
|
*/
|
|
uint8_t addr_check_broadcast(const address_t addr, addrtype_t addr_type)
|
|
{
|
|
switch (addr_type) {
|
|
case ADDR_802_15_4_SHORT:
|
|
break;
|
|
case ADDR_BROADCAST:
|
|
return 0;
|
|
default:
|
|
return 1;
|
|
}
|
|
|
|
uint8_t size = 2;
|
|
uint8_t *ptr = (uint8_t *) addr;
|
|
ptr += 2;
|
|
|
|
while (size) {
|
|
if ((*ptr++) != 0xFF) {
|
|
break;
|
|
} else {
|
|
size--;
|
|
}
|
|
}
|
|
return (size);
|
|
|
|
}
|
|
|
|
bool addr_is_ipv6_link_local(const uint8_t addr[static 16])
|
|
{
|
|
return addr[0] == 0xfe && (addr[1] & 0xc0) == 0x80;
|
|
}
|
|
|
|
/* Site-local addresses deprecated, but still processed by RFC 6724 address selection */
|
|
static bool addr_is_ipv6_site_local(const uint8_t addr[static 16])
|
|
{
|
|
return addr[0] == 0xfe && (addr[1] & 0xc0) == 0xc0;
|
|
}
|
|
|
|
static bool addr_is_ipv4_mapped(const uint8_t addr[static 16])
|
|
{
|
|
return memcmp(addr, ADDR_IPV4_MAPPED_PREFIX, 12) == 0;
|
|
}
|
|
|
|
/* Scope(A), as defined in RFC 6724 plus RFC 4007 */
|
|
uint_fast8_t addr_ipv6_scope(const uint8_t addr[static 16], const protocol_interface_info_entry_t *interface)
|
|
{
|
|
if (addr_is_ipv6_multicast(addr)) {
|
|
return addr_ipv6_multicast_scope(addr);
|
|
}
|
|
if (addr_is_ipv6_link_local(addr) || addr_is_ipv6_loopback(addr)) {
|
|
return IPV6_SCOPE_LINK_LOCAL;
|
|
}
|
|
if (addr_is_ipv6_site_local(addr)) {
|
|
return IPV6_SCOPE_SITE_LOCAL;
|
|
}
|
|
/* Thread-specific hack - we want to treat one specific "mesh local" ULA as
|
|
* "realm local" scope. (This is necessary, for example, to use a
|
|
* mesh-local ULA address as source when sending realm-local multicast).
|
|
*/
|
|
if (thread_addr_is_mesh_local(addr, interface)) {
|
|
return IPV6_SCOPE_REALM_LOCAL;
|
|
}
|
|
if (addr_is_ipv4_mapped(addr)) {
|
|
if ((addr[12] == 169 && addr[13] == 254) || addr[12] == 127) {
|
|
return IPV6_SCOPE_LINK_LOCAL;
|
|
}
|
|
return IPV6_SCOPE_GLOBAL;
|
|
}
|
|
return IPV6_SCOPE_GLOBAL;
|
|
}
|
|
|
|
void address_module_init(void)
|
|
{
|
|
addr_policy_table_reset();
|
|
//mac_reset_short_address();
|
|
}
|
|
|
|
#ifdef MULTICAST_FORWARDING
|
|
static if_group_fwd_entry_t *addr_multicast_fwd_list_lookup(if_group_fwd_list_t *list, const uint8_t group[16])
|
|
{
|
|
ns_list_foreach(if_group_fwd_entry_t, e, list) {
|
|
if (addr_ipv6_equal(e->group, group)) {
|
|
return e;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool addr_multicast_fwd_check(protocol_interface_info_entry_t *interface, const uint8_t group[16])
|
|
{
|
|
return addr_multicast_fwd_list_lookup(&interface->ip_groups_fwd, group) != NULL;
|
|
}
|
|
|
|
static void addr_multicast_fwd_adjust_upstream(protocol_interface_info_entry_t *downstream, protocol_interface_info_entry_t *upstream, const uint8_t group[16], bool add)
|
|
{
|
|
if (!upstream || downstream == upstream || !downstream->ip_multicast_forwarding) {
|
|
return;
|
|
}
|
|
uint8_t group_scope = addr_ipv6_multicast_scope(group);
|
|
if (downstream->zone_index[group_scope] == upstream->zone_index[group_scope]) {
|
|
tr_debug("Multicast proxy %s %s", add ? "add" : "remove", trace_ipv6(group));
|
|
if (add) {
|
|
addr_add_group(upstream, group);
|
|
} else {
|
|
addr_remove_group(upstream, group);
|
|
}
|
|
}
|
|
}
|
|
|
|
void addr_multicast_fwd_adjust_upstream_full(protocol_interface_info_entry_t *upstream, bool add)
|
|
{
|
|
ns_list_foreach(protocol_interface_info_entry_t, interface, &protocol_interface_info_list) {
|
|
ns_list_foreach(if_group_fwd_entry_t, group, &interface->ip_groups_fwd) {
|
|
addr_multicast_fwd_adjust_upstream(interface, upstream, group->group, add);
|
|
}
|
|
}
|
|
}
|
|
|
|
void addr_multicast_fwd_set_forwarding(struct protocol_interface_info_entry *interface, bool enable)
|
|
{
|
|
/* Do nothing if enable state isn't changing */
|
|
if (interface->ip_multicast_forwarding == enable) {
|
|
return;
|
|
}
|
|
|
|
/* If we disable forwarding on upstream, clear it out to avoid maintenance confusion */
|
|
if (!enable && interface == protocol_core_multicast_upstream) {
|
|
multicast_fwd_set_proxy_upstream(-1);
|
|
}
|
|
|
|
/* Adjust routine checks that forwarding is on before doing any processing. So make sure it's on
|
|
* now, then we'll set requested state afterwards. */
|
|
interface->ip_multicast_forwarding = true;
|
|
|
|
/* Add or remove all groups on changing downstream interface to upstream */
|
|
if (protocol_core_multicast_upstream) {
|
|
ns_list_foreach(if_group_fwd_entry_t, group, &interface->ip_groups_fwd) {
|
|
addr_multicast_fwd_adjust_upstream(interface, protocol_core_multicast_upstream, group->group, enable);
|
|
}
|
|
}
|
|
|
|
interface->ip_multicast_forwarding = enable;
|
|
}
|
|
|
|
bool addr_multicast_fwd_add(protocol_interface_info_entry_t *interface, const uint8_t group[16], uint32_t lifetime)
|
|
{
|
|
if_group_fwd_entry_t *entry = addr_multicast_fwd_list_lookup(&interface->ip_groups_fwd, group);
|
|
if (entry) {
|
|
if (entry->lifetime < lifetime) {
|
|
entry->lifetime = lifetime;
|
|
}
|
|
return true;
|
|
}
|
|
entry = ns_dyn_mem_alloc(sizeof *entry);
|
|
if (!entry) {
|
|
return false;
|
|
}
|
|
memcpy(entry->group, group, 16);
|
|
ns_list_add_to_end(&interface->ip_groups_fwd, entry);
|
|
addr_multicast_fwd_adjust_upstream(interface, protocol_core_multicast_upstream, group, true);
|
|
entry->lifetime = lifetime;
|
|
tr_debug("MC fwd added to IF %d: %s", interface->id, trace_ipv6(group));
|
|
return true;
|
|
}
|
|
|
|
static void addr_multicast_fwd_delete_entry(protocol_interface_info_entry_t *interface, if_group_fwd_entry_t *entry)
|
|
{
|
|
addr_multicast_fwd_adjust_upstream(interface, protocol_core_multicast_upstream, entry->group, false);
|
|
ns_list_remove(&interface->ip_groups_fwd, entry);
|
|
ns_dyn_mem_free(entry);
|
|
}
|
|
|
|
bool addr_multicast_fwd_remove(protocol_interface_info_entry_t *interface, const uint8_t group[16])
|
|
{
|
|
if_group_fwd_entry_t *entry = addr_multicast_fwd_list_lookup(&interface->ip_groups_fwd, group);
|
|
if (!entry) {
|
|
return false;
|
|
}
|
|
|
|
tr_debug("MC fwd removed from IF %d: %s", interface->id, trace_ipv6(group));
|
|
addr_multicast_fwd_delete_entry(interface, entry);
|
|
|
|
return true;
|
|
}
|
|
#endif // MULTICAST_FORWARDING
|
|
|
|
int_fast8_t addr_policy_table_add_entry(const uint8_t *prefix, uint8_t len, uint8_t precedence, uint8_t label)
|
|
{
|
|
addr_policy_table_entry_t *entry = ns_dyn_mem_alloc(sizeof(addr_policy_table_entry_t));
|
|
if (!entry) {
|
|
return -1;
|
|
}
|
|
bitcopy(entry->prefix, prefix, len);
|
|
entry->prefix_len = len;
|
|
entry->precedence = precedence;
|
|
entry->label = label;
|
|
|
|
/* Table is sorted longest-prefix-first, for longest-match searching */
|
|
bool inserted = false;
|
|
ns_list_foreach(addr_policy_table_entry_t, before, &addr_policy_table) {
|
|
if (before->prefix_len > len) {
|
|
continue;
|
|
}
|
|
if (before->prefix_len == len && bitsequal(before->prefix, prefix, len)) {
|
|
ns_dyn_mem_free(entry);
|
|
return -2;
|
|
}
|
|
ns_list_add_before(&addr_policy_table, before, entry);
|
|
inserted = true;
|
|
break;
|
|
}
|
|
|
|
if (!inserted) {
|
|
ns_list_add_to_end(&addr_policy_table, entry);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int_fast8_t addr_policy_table_delete_entry(const uint8_t *prefix, uint8_t len)
|
|
{
|
|
ns_list_foreach(addr_policy_table_entry_t, entry, &addr_policy_table) {
|
|
if (entry->prefix_len == len && bitsequal(entry->prefix, prefix, len)) {
|
|
ns_list_remove(&addr_policy_table, entry);
|
|
ns_dyn_mem_free(entry);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
/// @TODO do we need this test print anymore ?
|
|
void addr_policy_table_print(void)
|
|
{
|
|
ns_list_foreach(addr_policy_table_entry_t, entry, &addr_policy_table) {
|
|
char addr[40];
|
|
ip6tos(entry->prefix, addr);
|
|
tr_debug("%3d %3d %s/%u\n", entry->precedence, entry->label, addr, entry->prefix_len);
|
|
}
|
|
}
|
|
|
|
static void addr_policy_table_reset(void)
|
|
{
|
|
ns_list_foreach_safe(addr_policy_table_entry_t, entry, &addr_policy_table) {
|
|
ns_list_remove(&addr_policy_table, entry);
|
|
ns_dyn_mem_free(entry);
|
|
}
|
|
|
|
/* Default policy table from RFC 6724 */
|
|
addr_policy_table_add_entry(ADDR_LOOPBACK, 128, 50 , 0);
|
|
addr_policy_table_add_entry(NULL, 0, 40 , 1);
|
|
addr_policy_table_add_entry(ADDR_IPV4_MAPPED_PREFIX, 96, 35, 4);
|
|
addr_policy_table_add_entry((const uint8_t[]) { 0x20, 0x02 }, 16, 30, 2);
|
|
addr_policy_table_add_entry((const uint8_t[]) { 0x20, 0x01, 0, 0 }, 32, 5, 5);
|
|
addr_policy_table_add_entry((const uint8_t[]) { 0xfc }, 7, 3, 13);
|
|
addr_policy_table_add_entry(ADDR_IPV4_COMPATIBLE, 96, 1, 3);
|
|
addr_policy_table_add_entry((const uint8_t[]) { 0xfe, 0xc0 }, 10, 1, 11);
|
|
addr_policy_table_add_entry((const uint8_t[]) { 0x3f, 0xfe }, 16, 1, 12);
|
|
//addr_policy_table_print();
|
|
}
|
|
|
|
static const addr_policy_table_entry_t *addr_get_policy(const uint8_t addr[static 16])
|
|
{
|
|
ns_list_foreach(const addr_policy_table_entry_t, entry, &addr_policy_table) {
|
|
if (bitsequal(entry->prefix, addr, entry->prefix_len)) {
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
/* Shouldn't happen - should always have a default entry */
|
|
return NULL;
|
|
}
|
|
|
|
/* RFC 6724 CommonPrefixLen(S, D) */
|
|
static uint_fast8_t addr_common_prefix_len(const uint8_t src[static 16], uint_fast8_t src_prefix_len, const uint8_t dst[static 16])
|
|
{
|
|
uint_fast8_t common = 0;
|
|
|
|
while (src_prefix_len >= 8 && *src == *dst) {
|
|
common += 8;
|
|
src_prefix_len -= 8;
|
|
++src;
|
|
++dst;
|
|
}
|
|
|
|
if (src_prefix_len) {
|
|
uint8_t trail = common_count_leading_zeros_8(*src ^ *dst);
|
|
if (trail > src_prefix_len) {
|
|
trail = src_prefix_len;
|
|
}
|
|
common += trail;
|
|
}
|
|
|
|
return common;
|
|
}
|
|
|
|
if_address_entry_t *addr_get_entry(const protocol_interface_info_entry_t *interface, const uint8_t addr[static 16])
|
|
{
|
|
ns_list_foreach(if_address_entry_t, entry, &interface->ip_addresses) {
|
|
if (addr_ipv6_equal(entry->address, addr)) {
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool addr_is_assigned_to_interface(const protocol_interface_info_entry_t *interface, const uint8_t addr[static 16])
|
|
{
|
|
if_address_entry_t *entry = addr_get_entry(interface, addr);
|
|
return entry && !entry->tentative;
|
|
}
|
|
|
|
bool addr_is_tentative_for_interface(const protocol_interface_info_entry_t *interface, const uint8_t addr[static 16])
|
|
{
|
|
if_address_entry_t *entry = addr_get_entry(interface, addr);
|
|
return entry && entry->tentative;
|
|
}
|
|
|
|
if_group_entry_t *addr_add_group(protocol_interface_info_entry_t *interface, const uint8_t group[static 16])
|
|
{
|
|
if_group_entry_t *entry = addr_get_group_entry(interface, group);
|
|
if (entry) {
|
|
if (entry->ref_count != 0xFFFF) {
|
|
entry->ref_count++;
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
if (!addr_is_ipv6_multicast(group)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (addr_am_implicit_group_member(group)) {
|
|
return NULL;
|
|
}
|
|
|
|
entry = ns_dyn_mem_alloc(sizeof(if_group_entry_t));
|
|
if (!entry) {
|
|
return NULL;
|
|
}
|
|
memcpy(entry->group, group, 16);
|
|
entry->ref_count = 1;
|
|
ns_list_add_to_end(&interface->ip_groups, entry);
|
|
|
|
mld_start_listening(interface, entry);
|
|
|
|
return entry;
|
|
}
|
|
|
|
/* This does reference count */
|
|
void addr_remove_group(protocol_interface_info_entry_t *interface, const uint8_t group[static 16])
|
|
{
|
|
if_group_entry_t *entry = addr_get_group_entry(interface, group);
|
|
if (entry) {
|
|
if (entry->ref_count != 0xFFFF) {
|
|
if (--entry->ref_count == 0) {
|
|
addr_delete_group_entry(interface, entry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This does NOT reference count - it actually deletes the entry */
|
|
void addr_delete_group_entry(protocol_interface_info_entry_t *interface, if_group_entry_t *entry)
|
|
{
|
|
mld_stop_listening(interface, entry);
|
|
ns_list_remove(&interface->ip_groups, entry);
|
|
ns_dyn_mem_free(entry);
|
|
}
|
|
|
|
void addr_delete_group(protocol_interface_info_entry_t *interface, const uint8_t group[static 16])
|
|
{
|
|
if_group_entry_t *entry = addr_get_group_entry(interface, group);
|
|
if (entry) {
|
|
addr_delete_group_entry(interface, entry);
|
|
}
|
|
}
|
|
|
|
if_group_entry_t *addr_get_group_entry(const protocol_interface_info_entry_t *interface, const uint8_t group[static 16])
|
|
{
|
|
ns_list_foreach(if_group_entry_t, entry, &interface->ip_groups) {
|
|
if (addr_ipv6_equal(entry->group, group)) {
|
|
return entry;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void addr_generate_solicited_node_group(uint8_t group[static 16], const uint8_t addr[static 16])
|
|
{
|
|
memcpy(group, ADDR_MULTICAST_SOLICITED, 13);
|
|
memcpy(group + 13, addr + 13, 3);
|
|
}
|
|
|
|
static if_group_entry_t *addr_add_solicited_node_group(protocol_interface_info_entry_t *interface, const uint8_t address[static 16])
|
|
{
|
|
uint8_t group[16];
|
|
addr_generate_solicited_node_group(group, address);
|
|
return addr_add_group(interface, group);
|
|
}
|
|
|
|
static void addr_remove_solicited_node_group(protocol_interface_info_entry_t *interface, const uint8_t address[static 16])
|
|
{
|
|
uint8_t group[16];
|
|
addr_generate_solicited_node_group(group, address);
|
|
addr_remove_group(interface, group);
|
|
}
|
|
|
|
void addr_add_router_groups(protocol_interface_info_entry_t *interface)
|
|
{
|
|
/* The standard IPv6 ones, but not "Realm-Local-All-Routers"
|
|
* which is ZigBee IP / Thread-specific (and not IANA registered)
|
|
*/
|
|
addr_add_group(interface, ADDR_IF_LOCAL_ALL_ROUTERS);
|
|
addr_add_group(interface, ADDR_LINK_LOCAL_ALL_ROUTERS);
|
|
|
|
/* We only want to join the site address on one interface per site zone,
|
|
* or we'd get multiple copies of packets. Exit if we're already a member.
|
|
*/
|
|
ns_list_foreach(protocol_interface_info_entry_t, i, &protocol_interface_info_list) {
|
|
if (i->zone_index[IPV6_SCOPE_SITE_LOCAL] == interface->zone_index[IPV6_SCOPE_SITE_LOCAL] &&
|
|
addr_get_group_entry(i, ADDR_SITE_LOCAL_ALL_ROUTERS)) {
|
|
return;
|
|
}
|
|
}
|
|
addr_add_group(interface, ADDR_SITE_LOCAL_ALL_ROUTERS);
|
|
}
|
|
|
|
bool addr_am_group_member_on_interface(const protocol_interface_info_entry_t *interface, const uint8_t group[static 16])
|
|
{
|
|
return addr_am_implicit_group_member(group) || addr_get_group_entry(interface, group);
|
|
}
|
|
|
|
/* RFC 6724 Default source address selection */
|
|
const uint8_t *addr_select_source(protocol_interface_info_entry_t *interface, const uint8_t dest[static 16], uint32_t addr_preferences)
|
|
{
|
|
/* Let's call existing preferred address "SA" and new candidate "SB", to
|
|
* make it look like a bit like RFC 6724
|
|
*/
|
|
if_address_entry_t *SA = NULL;
|
|
uint_fast8_t scope_D = addr_ipv6_scope(dest, interface);
|
|
const addr_policy_table_entry_t *policy_D = addr_get_policy(dest);
|
|
|
|
if (addr_preferences == 0) {
|
|
addr_preferences = addr_preferences_default;
|
|
}
|
|
|
|
/*
|
|
* As we go around the loop, if we prefer the new "SB", we set SA to SB
|
|
* and continue. If we decide we prefer the existing SA, we just continue.
|
|
*
|
|
* Careful with these macros - they must only be used with if/else, and
|
|
* inside { }, as per the coding style rules.
|
|
*/
|
|
#define PREFER_SA continue
|
|
#define PREFER_SB SA = SB; continue
|
|
|
|
ns_list_foreach(if_address_entry_t, SB, &interface->ip_addresses) {
|
|
/* Ignore tentative addresses */
|
|
if (SB->tentative) {
|
|
continue;
|
|
}
|
|
|
|
/* First (non-tentative) address seen becomes SA */
|
|
if (!SA) {
|
|
PREFER_SB;
|
|
}
|
|
|
|
/* Rule 1: Prefer same address */
|
|
if (memcmp(SB->address, dest, 16) == 0) {
|
|
SA = SB;
|
|
/* It's an exact match, no point checking any other addresses */
|
|
break;
|
|
}
|
|
|
|
/* Rule 2: Prefer appropriate scope */
|
|
uint_fast8_t scope_SA = addr_ipv6_scope(SA->address, interface);
|
|
uint_fast8_t scope_SB = addr_ipv6_scope(SB->address, interface);
|
|
if (scope_SA < scope_SB) {
|
|
if (scope_SA < scope_D) {
|
|
PREFER_SB;
|
|
} else {
|
|
PREFER_SA;
|
|
}
|
|
} else if (scope_SB < scope_SA) {
|
|
if (scope_SB < scope_D) {
|
|
PREFER_SA;
|
|
} else {
|
|
PREFER_SB;
|
|
}
|
|
}
|
|
|
|
/* Rule 3: Avoid deprecated addresses */
|
|
if (SA->preferred_lifetime != 0 && SB->preferred_lifetime == 0) {
|
|
PREFER_SA;
|
|
} else if (SB->preferred_lifetime != 0 && SA->preferred_lifetime == 0) {
|
|
PREFER_SB;
|
|
}
|
|
|
|
/* (Rule 4: Prefer home addresses - Mobile IPv6 not implemented) */
|
|
/* (Rule 5: Prefer outgoing interface - candidate set already limited) */
|
|
/* (Rule 5.5: Prefer addresses in a prefix advertised by the next-hop - we don't track this information) */
|
|
|
|
/* Rule 6: Prefer matching label */
|
|
const addr_policy_table_entry_t *policy_SA = addr_get_policy(SA->address);
|
|
const addr_policy_table_entry_t *policy_SB = addr_get_policy(SB->address);
|
|
if (policy_SA->label == policy_D->label && policy_SB->label != policy_D->label) {
|
|
PREFER_SA;
|
|
} else if (policy_SB->label == policy_D->label && policy_SA->label != policy_D->label) {
|
|
PREFER_SB;
|
|
}
|
|
|
|
/* Rule 7: Prefer temporary addresses (or the opposite) */
|
|
if (SA->temporary != SB->temporary) {
|
|
bool prefer_public = (addr_preferences & SOCKET_IPV6_PREFER_SRC_PUBLIC);
|
|
|
|
if (SA->temporary != prefer_public) {
|
|
PREFER_SA;
|
|
} else {
|
|
PREFER_SB;
|
|
}
|
|
}
|
|
|
|
/* Rule 8: Use longest matching prefix */
|
|
uint_fast8_t common_SA = addr_common_prefix_len(SA->address, SA->prefix_len, dest);
|
|
uint_fast8_t common_SB = addr_common_prefix_len(SB->address, SB->prefix_len, dest);
|
|
if (common_SA > common_SB) {
|
|
PREFER_SA;
|
|
} else if (common_SB > common_SA) {
|
|
PREFER_SB;
|
|
}
|
|
|
|
/* A tie-breaker: Prefer 6LoWPAN short address (or the opposite) */
|
|
bool short_SA = SA->prefix_len == 64 && memcmp(SA->address + 8, ADDR_SHORT_ADR_SUFFIC, 6) == 0;
|
|
bool short_SB = SB->prefix_len == 64 && memcmp(SB->address + 8, ADDR_SHORT_ADR_SUFFIC, 6) == 0;
|
|
if (short_SA != short_SB) {
|
|
bool prefer_short = (addr_preferences & SOCKET_IPV6_PREFER_SRC_6LOWPAN_SHORT);
|
|
|
|
if (short_SA == prefer_short) {
|
|
PREFER_SA;
|
|
} else {
|
|
PREFER_SB;
|
|
}
|
|
}
|
|
|
|
/* Tie */
|
|
PREFER_SA;
|
|
}
|
|
|
|
return SA ? SA->address : NULL;
|
|
}
|
|
|
|
/* A variant of RFC 6724 Default source address selection, to select an address
|
|
* on an interface with a specific prefix. The prefix must match, and some
|
|
* source address rules don't apply, but some are handled similarly. See
|
|
* comments in addr_select_source.
|
|
*/
|
|
const uint8_t *addr_select_with_prefix(protocol_interface_info_entry_t *cur, const uint8_t *prefix, uint8_t prefix_len, uint32_t addr_preferences)
|
|
{
|
|
if_address_entry_t *SA = NULL;
|
|
|
|
if (addr_preferences == 0) {
|
|
addr_preferences = addr_preferences_default;
|
|
}
|
|
|
|
ns_list_foreach(if_address_entry_t, SB, &cur->ip_addresses) {
|
|
/* Ignore tentative addresses */
|
|
if (SB->tentative) {
|
|
continue;
|
|
}
|
|
|
|
/* Prefix must match */
|
|
if (!bitsequal(SB->address, prefix, prefix_len)) {
|
|
continue;
|
|
}
|
|
|
|
/* First (non-tentative, matching prefix) address seen becomes SA */
|
|
if (!SA) {
|
|
PREFER_SB;
|
|
}
|
|
|
|
/* (Rule 1: Prefer same address - doesn't apply here) */
|
|
/* Rule 2: Was prefer appropriate scope - for this purpose we instead prefer wider scope */
|
|
uint_fast8_t scope_SA = addr_ipv6_scope(SA->address, cur);
|
|
uint_fast8_t scope_SB = addr_ipv6_scope(SB->address, cur);
|
|
if (scope_SA < scope_SB) {
|
|
PREFER_SB;
|
|
} else if (scope_SB < scope_SA) {
|
|
PREFER_SA;
|
|
}
|
|
|
|
/* Rule 3: Avoid deprecated addresses */
|
|
if (SA->preferred_lifetime != 0 && SB->preferred_lifetime == 0) {
|
|
PREFER_SA;
|
|
}
|
|
else if (SB->preferred_lifetime != 0 && SA->preferred_lifetime == 0) {
|
|
PREFER_SB;
|
|
}
|
|
|
|
/* (Rule 4: Prefer home addresses - Mobile IPv6 not implemented) */
|
|
/* (Rule 5: Prefer outgoing interface - candidate set already limited) */
|
|
/* (Rule 5.5: Prefer addresses in a prefix advertised by the next-hop - we don't track this information) */
|
|
|
|
/* Rule 6: Prefer matching label - doesn't apply here. But instead,
|
|
* let's use precedence, like rule 6 of destination selection.
|
|
*/
|
|
const addr_policy_table_entry_t *policy_SA = addr_get_policy(SA->address);
|
|
const addr_policy_table_entry_t *policy_SB = addr_get_policy(SB->address);
|
|
if (policy_SA->precedence > policy_SB->precedence) {
|
|
PREFER_SA;
|
|
} else if (policy_SB->precedence > policy_SA->precedence) {
|
|
PREFER_SB;
|
|
}
|
|
|
|
/* Rule 7: Prefer temporary addresses (or the opposite) */
|
|
if (SA->temporary != SB->temporary) {
|
|
bool prefer_public = (addr_preferences & SOCKET_IPV6_PREFER_SRC_PUBLIC);
|
|
|
|
if (SA->temporary != prefer_public) {
|
|
PREFER_SA;
|
|
} else {
|
|
PREFER_SB;
|
|
}
|
|
}
|
|
|
|
/* (Rule 8: Use longest matching prefix - doesn't apply) */
|
|
|
|
/* A tie-breaker: Prefer 6LoWPAN short address (or the opposite) */
|
|
bool short_SA = SA->prefix_len == 64 && memcmp(SA->address + 8, ADDR_SHORT_ADR_SUFFIC, 6) == 0;
|
|
bool short_SB = SB->prefix_len == 64 && memcmp(SB->address + 8, ADDR_SHORT_ADR_SUFFIC, 6) == 0;
|
|
if (short_SA != short_SB) {
|
|
bool prefer_short = (addr_preferences & SOCKET_IPV6_PREFER_SRC_6LOWPAN_SHORT);
|
|
|
|
if (short_SA == prefer_short) {
|
|
PREFER_SA;
|
|
} else {
|
|
PREFER_SB;
|
|
}
|
|
}
|
|
|
|
/* Tie */
|
|
PREFER_SA;
|
|
}
|
|
|
|
return SA ? SA->address : NULL;
|
|
}
|
|
|
|
#undef PREFER_SA
|
|
#undef PREFER_SB
|
|
|
|
void addr_delete_entry(protocol_interface_info_entry_t *cur, if_address_entry_t *addr)
|
|
{
|
|
if (addr->group_added) {
|
|
addr_remove_solicited_node_group(cur, addr->address);
|
|
}
|
|
ns_list_remove(&cur->ip_addresses, addr);
|
|
addr_cb(cur, addr, ADDR_CALLBACK_DELETED);
|
|
ns_dyn_mem_free(addr);
|
|
}
|
|
|
|
/* ticks is in 1/10s */
|
|
void addr_fast_timer(protocol_interface_info_entry_t *cur, uint_fast16_t ticks)
|
|
{
|
|
/* Fast timers only run while the interface is active. */
|
|
if (!(cur->lowpan_info & INTERFACE_NWK_ACTIVE)) {
|
|
return;
|
|
}
|
|
|
|
ns_list_foreach_safe(if_address_entry_t, addr, &cur->ip_addresses) {
|
|
if (addr->state_timer == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (addr->state_timer > ticks) {
|
|
addr->state_timer -= ticks;
|
|
continue;
|
|
}
|
|
|
|
addr->state_timer = 0;
|
|
/* Advance DAD state machine if tentative, else give it to the protocol state machine */
|
|
if (addr->tentative) {
|
|
if (addr->count == 0) {
|
|
#if 0
|
|
// Initial join delay finished
|
|
// We don't have full MLD support - send 1 report for now
|
|
// This will become a real "join" later
|
|
buffer_t *mld_buf;
|
|
uint8_t sol_addr[16];
|
|
memcpy(sol_addr, ADDR_MULTICAST_SOLICITED, 13);
|
|
memcpy(sol_addr + 13, addr->address + 13, 3);
|
|
mld_buf = mld_build(cur, NULL, ICMPV6_TYPE_INFO_MCAST_LIST_REPORT, 0, sol_addr);
|
|
protocol_push(mld_buf);
|
|
#endif
|
|
if (addr_add_solicited_node_group(cur, addr->address)) {
|
|
addr->group_added = true;
|
|
}
|
|
}
|
|
#ifdef HAVE_IPV6_ND
|
|
if (addr->count >= cur->dup_addr_detect_transmits) {
|
|
/* Finished - if we've not been nerfed already, we can transition
|
|
* to non-tentative.
|
|
*/
|
|
addr->tentative = false;
|
|
addr->count = 0;
|
|
tr_info("DAD passed on IF %d: %s", cur->id, trace_ipv6(addr->address));
|
|
addr_cb(cur, addr, ADDR_CALLBACK_DAD_COMPLETE);
|
|
} else {
|
|
buffer_t *ns_buf = icmpv6_build_ns(cur, addr->address, NULL, false, true, NULL);
|
|
protocol_push(ns_buf);
|
|
addr->state_timer = (cur->ipv6_neighbour_cache.retrans_timer + 50) / 100; // ms -> ticks
|
|
addr->count++;
|
|
}
|
|
#endif
|
|
} else {
|
|
addr_cb(cur, addr, ADDR_CALLBACK_TIMER);
|
|
}
|
|
|
|
/* If a callback has shut down the interface, break now - this isn't
|
|
* just a nicety; it avoids an iteration failure if shutdown disrupted
|
|
* the address list (as is likely).
|
|
*/
|
|
if (!(cur->lowpan_info & INTERFACE_NWK_ACTIVE)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void addr_slow_timer(protocol_interface_info_entry_t *cur, uint_fast16_t seconds)
|
|
{
|
|
/* Slow (lifetime) timers run whether the interface is active or not */
|
|
ns_list_foreach_safe(if_address_entry_t, addr, &cur->ip_addresses) {
|
|
if (addr->preferred_lifetime != 0xffffffff && addr->preferred_lifetime != 0) {
|
|
if (addr->preferred_lifetime > seconds) {
|
|
addr->preferred_lifetime -= seconds;
|
|
} else {
|
|
tr_info("Address deprecated: %s", trace_ipv6(addr->address));
|
|
addr->preferred_lifetime = 0;
|
|
addr_cb(cur, addr, ADDR_CALLBACK_DEPRECATED);
|
|
}
|
|
}
|
|
|
|
if (addr->valid_lifetime != 0xffffffff) {
|
|
if (addr->valid_lifetime > seconds) {
|
|
addr->valid_lifetime -= seconds;
|
|
} else {
|
|
tr_info("Address invalidated: %s", trace_ipv6(addr->address));
|
|
addr->valid_lifetime = 0;
|
|
addr_cb(cur, addr, ADDR_CALLBACK_INVALIDATED);
|
|
/* Give them the chance to revalidate */
|
|
if (addr->valid_lifetime == 0) {
|
|
addr_delete_entry(cur, addr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef MULTICAST_FORWARDING
|
|
ns_list_foreach_safe(if_group_fwd_entry_t, group, &cur->ip_groups_fwd) {
|
|
if (group->lifetime != 0xffffffff) {
|
|
if (group->lifetime > seconds) {
|
|
group->lifetime -= seconds;
|
|
} else {
|
|
tr_debug("MC fwd expired: %s", trace_ipv6(group->group));
|
|
addr_multicast_fwd_delete_entry(cur, group);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void addr_max_entries_check(protocol_interface_info_entry_t *cur, if_address_source_t source)
|
|
{
|
|
// Limit only auto configuration addresses
|
|
if (source != ADDR_SOURCE_SLAAC || cur->ip_addresses_max_slaac_entries == 0) {
|
|
return;
|
|
}
|
|
|
|
uint8_t count = 0;
|
|
if_address_entry_t *first_slaac_entry = NULL;
|
|
ns_list_foreach(if_address_entry_t, e, &cur->ip_addresses) {
|
|
if (e->source == ADDR_SOURCE_SLAAC) {
|
|
if (!first_slaac_entry) {
|
|
first_slaac_entry = e;
|
|
}
|
|
if (++count == 0xff) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count > cur->ip_addresses_max_slaac_entries) {
|
|
addr_delete_entry(cur, first_slaac_entry);
|
|
}
|
|
}
|
|
|
|
void addr_max_slaac_entries_set(protocol_interface_info_entry_t *cur, uint8_t max_slaac_entries)
|
|
{
|
|
cur->ip_addresses_max_slaac_entries = max_slaac_entries;
|
|
}
|
|
|
|
if_address_entry_t *addr_add(protocol_interface_info_entry_t *cur, const uint8_t address[static 16], uint_fast8_t prefix_len, if_address_source_t source, uint32_t valid_lifetime, uint32_t preferred_lifetime, bool skip_dad)
|
|
{
|
|
if (addr_get_entry(cur, address)) {
|
|
return NULL;
|
|
}
|
|
|
|
addr_max_entries_check(cur, source);
|
|
|
|
if_address_entry_t *entry = ns_dyn_mem_alloc(sizeof(if_address_entry_t));
|
|
if (!entry) {
|
|
return NULL;
|
|
}
|
|
|
|
memset(entry, 0, sizeof *entry);
|
|
entry->cb = NULL;
|
|
memcpy(entry->address, address, 16);
|
|
entry->prefix_len = prefix_len;
|
|
entry->source = source;
|
|
entry->valid_lifetime = valid_lifetime;
|
|
entry->preferred_lifetime = preferred_lifetime;
|
|
entry->group_added = false;
|
|
#ifndef HAVE_IPV6_ND
|
|
skip_dad = true;
|
|
#endif
|
|
if (skip_dad || cur->dup_addr_detect_transmits == 0) {
|
|
entry->tentative = false;
|
|
if (addr_add_solicited_node_group(cur, entry->address)) {
|
|
entry->group_added = true;
|
|
}
|
|
// XXX not right? Want to do delay + MLD join, so don't special-case?
|
|
/* entry->cb isn't set yet, but global notifiers will want call */
|
|
addr_cb(cur, entry, ADDR_CALLBACK_DAD_COMPLETE);
|
|
} else {
|
|
entry->tentative = true;
|
|
// Initial timer is for the multicast join delay
|
|
entry->count = 0;
|
|
entry->state_timer = randLIB_get_random_in_range(1, 10); // MAX_RTR_SOLICITATION_DELAY (1s) in ticks
|
|
}
|
|
|
|
tr_info("%sAddress added to IF %d: %s", (entry->tentative ? "Tentative " : ""), cur->id, trace_ipv6(address));
|
|
|
|
ns_list_add_to_end(&cur->ip_addresses, entry);
|
|
|
|
return entry;
|
|
}
|
|
|
|
int_fast8_t addr_delete(protocol_interface_info_entry_t *cur, const uint8_t address[static 16])
|
|
{
|
|
ns_list_foreach(if_address_entry_t, e, &cur->ip_addresses) {
|
|
if (memcmp(e->address, address, 16) == 0) {
|
|
addr_delete_entry(cur, e);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void addr_delete_matching(protocol_interface_info_entry_t *cur, const uint8_t *prefix, uint8_t prefix_len, if_address_source_t source)
|
|
{
|
|
ns_list_foreach_safe(if_address_entry_t, e, &cur->ip_addresses) {
|
|
if ((source == ADDR_SOURCE_UNKNOWN || e->source == source) &&
|
|
bitsequal(e->address, prefix, prefix_len)) {
|
|
addr_delete_entry(cur, e);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void addr_set_non_preferred(protocol_interface_info_entry_t *cur, if_address_source_t source)
|
|
{
|
|
ns_list_foreach_safe(if_address_entry_t, e, &cur->ip_addresses) {
|
|
if (e->source == source) {
|
|
// Sets preferred lifetime to zero or deletes tentative addresses
|
|
addr_set_preferred_lifetime(cur, e, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void addr_duplicate_detected(struct protocol_interface_info_entry *interface, const uint8_t addr[static 16])
|
|
{
|
|
if_address_entry_t *entry = addr_get_entry(interface, addr);
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
|
|
tr_warn("DAD failed: %s", trace_ipv6(addr));
|
|
|
|
interface->dad_failures++;
|
|
addr_cb(interface, entry, ADDR_CALLBACK_DAD_FAILED);
|
|
|
|
/* Callback may have done something drastic like shut down the interface.
|
|
* Don't assume entry is still valid - remove it by IP address.
|
|
*/
|
|
addr_delete(interface, addr);
|
|
}
|
|
|
|
void addr_notification_register(if_address_notification_fn *fn)
|
|
{
|
|
ns_list_foreach(addr_notification_t, n, &addr_notifications) {
|
|
if (n->fn == fn) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
addr_notification_t *n = ns_dyn_mem_alloc(sizeof(addr_notification_t));
|
|
if (!n) {
|
|
tr_error("addr_notification_register mem");
|
|
return;
|
|
}
|
|
n->fn = fn;
|
|
ns_list_add_to_end(&addr_notifications, n);
|
|
}
|
|
|
|
void addr_cb(protocol_interface_info_entry_t *interface, if_address_entry_t *addr, if_address_callback_t reason)
|
|
{
|
|
ns_list_foreach(addr_notification_t, n, &addr_notifications) {
|
|
n->fn(interface, addr, reason);
|
|
}
|
|
if (addr->cb) {
|
|
addr->cb(interface, addr, reason);
|
|
}
|
|
}
|
|
|
|
void addr_set_valid_lifetime(protocol_interface_info_entry_t *interface, if_address_entry_t *addr, uint32_t valid_lifetime)
|
|
{
|
|
if (valid_lifetime != addr->valid_lifetime) {
|
|
addr->valid_lifetime = valid_lifetime;
|
|
addr_cb(interface, addr, ADDR_CALLBACK_REFRESHED);
|
|
}
|
|
}
|
|
|
|
void addr_set_preferred_lifetime(protocol_interface_info_entry_t *interface, if_address_entry_t *addr, uint32_t preferred_lifetime)
|
|
{
|
|
if (preferred_lifetime != addr->preferred_lifetime) {
|
|
addr->preferred_lifetime = preferred_lifetime;
|
|
if (preferred_lifetime == 0) {
|
|
// Entry is deleted if it is tentative
|
|
if (addr->tentative) {
|
|
addr_delete_entry(interface, addr);
|
|
} else {
|
|
addr_cb(interface, addr, ADDR_CALLBACK_DEPRECATED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void memswap(uint8_t *restrict a, uint8_t *restrict b, uint_fast8_t len)
|
|
{
|
|
while (len--) {
|
|
uint8_t t = *a;
|
|
*a++ = *b;
|
|
*b++ = t;
|
|
}
|
|
}
|
|
|
|
/* Optimised for quick discard of mismatching addresses (eg in a cache lookup):
|
|
* searches BACKWARDS, as last bytes are most likely to differ.
|
|
*/
|
|
bool addr_ipv6_equal(const uint8_t a[static 16], const uint8_t b[static 16])
|
|
{
|
|
for (int_fast8_t n = 15; n >= 0; n--) {
|
|
if (a[n] != b[n]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool addr_iid_matches_eui64(const uint8_t iid[static 8], const uint8_t eui64[static 8])
|
|
{
|
|
for (int_fast8_t n = 7; n >= 1; n--) {
|
|
if (iid[n] != eui64[n]) {
|
|
return false;
|
|
}
|
|
}
|
|
return iid[0] == (eui64[0] ^ 2);
|
|
}
|
|
|
|
bool addr_iid_matches_lowpan_short(const uint8_t iid[static 8], uint16_t short_addr)
|
|
{
|
|
if (iid[7] != (short_addr & 0xFF) ||
|
|
iid[6] != (short_addr >> 8)) {
|
|
return false;
|
|
}
|
|
|
|
for (int_fast8_t n = 5; n >= 0; n--) {
|
|
if (iid[n] != ADDR_SHORT_ADR_SUFFIC[n]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool addr_iid_reserved(const uint8_t iid[static 8])
|
|
{
|
|
static const uint8_t reserved_iana[5] = { 0x02, 0x00, 0x5e, 0xff, 0xfe };
|
|
static const uint8_t reserved_subnet_anycast[7] = { 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
|
|
|
if (memcmp(iid, ADDR_UNSPECIFIED + 8, 8) == 0) {
|
|
return true; // subnet-router anycast
|
|
}
|
|
|
|
if (memcmp(iid, reserved_iana, 5) == 0) {
|
|
return true;
|
|
}
|
|
|
|
if (memcmp(iid, reserved_subnet_anycast, 7) == 0 && iid[7] >= 0x80) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int_fast8_t addr_opaque_iid_key_set(const void *secret_key, uint8_t key_len)
|
|
{
|
|
/* Delete existing info */
|
|
if (addr_iid_secret_key) {
|
|
ns_dyn_mem_free((void *) addr_iid_secret_key);
|
|
addr_iid_secret_key = NULL;
|
|
addr_iid_secret_key_len = 0;
|
|
}
|
|
|
|
/* If disabling, that's it */
|
|
if (secret_key == NULL) {
|
|
return 0;
|
|
}
|
|
/* Attempt to copy new info */
|
|
uint8_t *copy = ns_dyn_mem_alloc(key_len);
|
|
if (!copy) {
|
|
return -1;
|
|
}
|
|
addr_iid_secret_key = memcpy(copy, secret_key, key_len);
|
|
addr_iid_secret_key_len = key_len;
|
|
return 0;
|
|
}
|
|
|
|
int_fast8_t addr_opaque_initial_iid_set(const void *iid)
|
|
{
|
|
/* Delete existing info */
|
|
if (addr_initial_iid) {
|
|
ns_dyn_mem_free((void *) addr_initial_iid);
|
|
addr_initial_iid = NULL;
|
|
}
|
|
if (!iid) {
|
|
return 0;
|
|
}
|
|
/* Attempt to copy new info */
|
|
uint8_t *copy = ns_dyn_mem_alloc(8);
|
|
if (!copy) {
|
|
return -1;
|
|
}
|
|
addr_initial_iid = memcpy(copy, iid, 8);
|
|
return 0;
|
|
}
|
|
|
|
bool addr_opaque_iid_key_is_set(void)
|
|
{
|
|
return addr_iid_secret_key != NULL;
|
|
}
|
|
|
|
/* RFC 7217 generation: addr must be prepopulated with 8-byte prefix, and secret key must be set */
|
|
void addr_generate_opaque_iid(protocol_interface_info_entry_t *cur, uint8_t addr[static 16])
|
|
{
|
|
opaque_retry:
|
|
|
|
if (addr_initial_iid && !cur->dad_failures) {
|
|
// This is test implementations use only normally should not need this.
|
|
memcpy(addr + 8,addr_initial_iid,8);
|
|
return;
|
|
}
|
|
{
|
|
// Limit scope to try to minimise stack, given the goto
|
|
ns_sha256_context ctx;
|
|
ns_sha256_init(&ctx);
|
|
ns_sha256_starts(&ctx);
|
|
ns_sha256_update(&ctx, addr, 8);
|
|
if (cur->interface_name) {
|
|
/* This isn't ideal - there's no guarantee each instance of a driver has a distinct name */
|
|
ns_sha256_update(&ctx, cur->interface_name, strlen(cur->interface_name));
|
|
} else {
|
|
ns_sha256_update(&ctx, &cur->id, sizeof cur->id);
|
|
}
|
|
ns_sha256_update(&ctx, &cur->dad_failures, sizeof cur->dad_failures);
|
|
ns_sha256_update(&ctx, addr_iid_secret_key, addr_iid_secret_key_len);
|
|
ns_sha256_finish_nbits(&ctx, addr + 8, 64);
|
|
ns_sha256_free(&ctx);
|
|
}
|
|
/* Note that we only check for reserved IIDs - as per RFC 7217,
|
|
* there's no restriction on U/G bits.
|
|
*/
|
|
if (addr_iid_reserved(addr + 8)) {
|
|
cur->dad_failures++;
|
|
goto opaque_retry;
|
|
}
|
|
}
|
|
|
|
/* Write a LoWPAN IPv6 address, based on a prefix and short address */
|
|
uint8_t *addr_ipv6_write_from_lowpan_short(uint8_t dst[static 16], const uint8_t prefix[static 8], uint16_t short_addr)
|
|
{
|
|
common_write_16_bit(short_addr, dst + 14);
|
|
memcpy(dst + 8, ADDR_SHORT_ADR_SUFFIC, 6);
|
|
return memcpy(dst, prefix, 8);
|
|
}
|
|
|
|
/* Turn an address (either MAC or IP) into a base IP address for context compression */
|
|
bool addr_iid_from_outer(uint8_t iid_out[static 8], const sockaddr_t *addr_in)
|
|
{
|
|
switch (addr_in->addr_type) {
|
|
case ADDR_802_15_4_LONG:
|
|
memcpy(iid_out, addr_in->address + 2, 8);
|
|
iid_out[0] ^= 2;
|
|
break;
|
|
case ADDR_BROADCAST:
|
|
case ADDR_802_15_4_SHORT:
|
|
memcpy(iid_out, ADDR_SHORT_ADR_SUFFIC, 6);
|
|
iid_out[6] = addr_in->address[2];
|
|
iid_out[7] = addr_in->address[3];
|
|
break;
|
|
case ADDR_IPV6:
|
|
memcpy(iid_out, addr_in->address + 8, 8);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int addr_interface_set_ll64(protocol_interface_info_entry_t *cur, if_address_callback_fn *cb)
|
|
{
|
|
int ret_val = -1;
|
|
if_address_entry_t *address_entry = NULL;
|
|
uint8_t temp_ll64[16];
|
|
memcpy(temp_ll64, ADDR_LINK_LOCAL_PREFIX, 8);
|
|
memcpy(temp_ll64 + 8, cur->iid_eui64, 8);
|
|
|
|
address_entry = addr_add(cur, temp_ll64, 64, ADDR_SOURCE_UNKNOWN, 0xffffffff, 0xffffffff, false);
|
|
if (address_entry) {
|
|
tr_debug("LL64 Register OK!");
|
|
ret_val = 0;
|
|
address_entry->cb = cb;
|
|
if (!address_entry->tentative) {
|
|
addr_cb(cur, address_entry, ADDR_CALLBACK_DAD_COMPLETE);
|
|
}
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
/* address_type 0 means "any" address - we return short by preference */
|
|
/* address_type 1 means long address - we ignore short addresses */
|
|
int8_t addr_interface_get_ll_address(protocol_interface_info_entry_t *cur, uint8_t *address_ptr, uint8_t address_type)
|
|
{
|
|
const uint8_t *short_addr = NULL;
|
|
const uint8_t *long_addr = NULL;
|
|
|
|
if (!cur) {
|
|
return -1;
|
|
}
|
|
|
|
ns_list_foreach(if_address_entry_t, e, &cur->ip_addresses) {
|
|
if (!e->tentative && addr_is_ipv6_link_local(e->address)) {
|
|
if (cur->nwk_id == IF_6LoWPAN && memcmp(e->address + 8, ADDR_SHORT_ADR_SUFFIC, 6) == 0) {
|
|
short_addr = e->address;
|
|
} else {
|
|
long_addr = e->address;
|
|
}
|
|
|
|
if (long_addr && short_addr) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (short_addr && address_type != 1) {
|
|
if (address_ptr) {
|
|
memcpy(address_ptr, short_addr, 16);
|
|
}
|
|
return 0;
|
|
} else if (long_addr) {
|
|
if (address_ptr) {
|
|
memcpy(address_ptr, long_addr, 16);
|
|
}
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
bool addr_interface_all_address_ready(protocol_interface_info_entry_t *cur)
|
|
{
|
|
if (!cur) {
|
|
return false;
|
|
}
|
|
|
|
ns_list_foreach(if_address_entry_t, e, &cur->ip_addresses) {
|
|
if (e->tentative) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int8_t addr_interface_gp_prefix_compare(protocol_interface_info_entry_t *cur, const uint8_t *prefix)
|
|
{
|
|
if (cur->global_address_available) {
|
|
ns_list_foreach(if_address_entry_t, e, &cur->ip_addresses) {
|
|
if (memcmp(e->address, prefix, 8) == 0) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int8_t addr_interface_address_compare(protocol_interface_info_entry_t *cur, const uint8_t *addr)
|
|
{
|
|
/* First check the specified interface */
|
|
if (addr_is_assigned_to_interface(cur, addr)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Then check other interfaces, enforcing scope zones */
|
|
uint_fast8_t scope = addr_ipv6_scope(addr, cur);
|
|
ns_list_foreach(protocol_interface_info_entry_t, other, &protocol_interface_info_list) {
|
|
if (other != cur &&
|
|
other->zone_index[scope] == cur->zone_index[scope] &&
|
|
addr_is_assigned_to_interface(other, addr)) {
|
|
// special handling for Thread - external global-scope ULA coming in,
|
|
// which would match, but we need to restrict if that ULA is mesh-local
|
|
// on the Thread side.
|
|
if (thread_info(other) && addr_ipv6_scope(addr, other) <= IPV6_SCOPE_REALM_LOCAL) {
|
|
continue;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int8_t addr_interface_select_source(protocol_interface_info_entry_t *cur, uint8_t *src_ptr, const uint8_t *dest, uint32_t addr_preferences)
|
|
{
|
|
int8_t ret_val = -1;
|
|
if (cur) {
|
|
const uint8_t *src = addr_select_source(cur, dest, addr_preferences);
|
|
if (src) {
|
|
memcpy(src_ptr, src, 16);
|
|
ret_val = 0;
|
|
}
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
// This last function must always be compiled with tracing enabled
|
|
#ifndef FEA_TRACE_SUPPORT
|
|
#define FEA_TRACE_SUPPORT 1
|
|
#include "mbed-trace/mbed_trace.h"
|
|
#endif
|
|
char* trace_sockaddr(const sockaddr_t* addr, bool panid_prefix)
|
|
{
|
|
uint8_t length = addr_len_from_type(addr->addr_type);
|
|
|
|
if (length == 0) {
|
|
return "<n/a>";
|
|
}
|
|
|
|
/* Awkward hack for 802.15.4 address types */
|
|
if (addr->addr_type == ADDR_802_15_4_SHORT ||
|
|
addr->addr_type == ADDR_802_15_4_LONG) {
|
|
length -= (panid_prefix) ? 0 : 2;
|
|
}
|
|
|
|
/* Start from index 0 (prints PAN ID if exists) */
|
|
return trace_array(&addr->address[0], length);
|
|
}
|