mbed-os/features/nanostack/sal-stack-nanostack/source/Core/address.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);
}