mirror of https://github.com/ARMmbed/mbed-os.git
256 lines
9.6 KiB
C
256 lines
9.6 KiB
C
/*
|
|
* Copyright (c) 2015-2017, 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.
|
|
*/
|
|
|
|
#include "nsconfig.h"
|
|
#include "string.h"
|
|
#include "ns_types.h"
|
|
#include "ns_list.h"
|
|
#include "ns_trace.h"
|
|
#include "nsdynmemLIB.h"
|
|
#include "Core/include/socket.h"
|
|
#include "NWK_INTERFACE/Include/protocol.h"
|
|
#include "Common_Protocols/ipv6.h"
|
|
#include "Common_Protocols/icmpv6.h"
|
|
#include "Common_Protocols/icmpv6_prefix.h"
|
|
#include "Common_Protocols/icmpv6_radv.h"
|
|
#include "Common_Protocols/udp.h"
|
|
#include "6LoWPAN/ND/nd_router_object.h" // for gp_address_ functions - better place?
|
|
#include "ipv6_stack/ipv6_routing_table.h"
|
|
#include "ipv6_stack/protocol_ipv6.h"
|
|
#include "Common_Protocols/ipv6_resolution.h"
|
|
#include "Common_Protocols/tcp.h"
|
|
#include "Service_Libs/whiteboard/whiteboard.h"
|
|
#include "Service_Libs/etx/etx.h"
|
|
#include "platform/arm_hal_interrupt.h"
|
|
#include "common_functions.h"
|
|
|
|
#define TRACE_GROUP "ip6r"
|
|
|
|
#ifndef RESOLUTION_QUEUE_LIMIT
|
|
#define RESOLUTION_QUEUE_LIMIT 2
|
|
#endif
|
|
|
|
void ipv6_interface_resolve_send_ns(ipv6_neighbour_cache_t *cache, ipv6_neighbour_t *entry, bool unicast, uint_fast8_t seq)
|
|
{
|
|
protocol_interface_info_entry_t *cur_interface = NS_CONTAINER_OF(cache, protocol_interface_info_entry_t, ipv6_neighbour_cache);
|
|
|
|
if (cur_interface->if_ns_transmit) {
|
|
/* Thread uses DHCP Leasequery (!) instead of NS for address resolution */
|
|
/* We still allow unicast NUD probes using NS, although I expect them to be disabled */
|
|
if (cur_interface->if_ns_transmit(cur_interface, entry, unicast, seq)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_IPV6_ND
|
|
tr_debug("Sending %s NS for: %s",
|
|
(unicast ? "unicast" : "multicast"), trace_ipv6(entry->ip_address));
|
|
|
|
buffer_t *prompting_packet = ns_list_get_first(&entry->queue);
|
|
buffer_t *buf = icmpv6_build_ns(cur_interface, entry->ip_address,
|
|
prompting_packet ? prompting_packet->src_sa.address : NULL,
|
|
unicast, false, NULL);
|
|
protocol_push(buf);
|
|
#else
|
|
tr_error("No NS handler for interface %d", cur_interface->id);
|
|
#endif
|
|
}
|
|
|
|
/* Entry has already been removed from cache, and is about to be freed. Hence entry->queue can't change while we process it */
|
|
void ipv6_interface_resolution_failed(ipv6_neighbour_cache_t *cache, ipv6_neighbour_t *entry)
|
|
{
|
|
protocol_interface_info_entry_t *cur_interface = NS_CONTAINER_OF(cache, protocol_interface_info_entry_t, ipv6_neighbour_cache);
|
|
|
|
tr_warn("LL addr of %s not found", trace_ipv6(entry->ip_address));
|
|
ns_list_foreach_safe(buffer_t, buf, &entry->queue) {
|
|
ns_list_remove(&entry->queue, buf);
|
|
uint8_t code;
|
|
if (buf->options.ip_extflags & IPEXT_SRH_RPL) {
|
|
/* Note that as the border router we loopback SRH errors to ourselves if the first hop doesn't resolve */
|
|
code = ICMPV6_CODE_DST_UNREACH_SRC_RTE_HDR_ERR;
|
|
} else {
|
|
code = ICMPV6_CODE_DST_UNREACH_ADDR_UNREACH;
|
|
}
|
|
/* XXX note no special handling for our own socket transmissions,
|
|
* unlike original case. If we want this, we should do it in ICMP
|
|
* RX handling, so we get events for external errors.
|
|
*/
|
|
buf = socket_tx_buffer_event(buf, SOCKET_NO_ROUTE);
|
|
if (buf) {
|
|
buf = icmpv6_error(buf, cur_interface, ICMPV6_TYPE_ERROR_DESTINATION_UNREACH, code, 0);
|
|
protocol_push(buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Silly bit of interface glue - ipv6_routing_table.c doesn't know about interface structures,
|
|
* but it needs to be able to get from the interface id in the Routing Table and/or
|
|
* Destination Cache to the relevant Neighbour Cache
|
|
*/
|
|
ipv6_neighbour_cache_t *ipv6_neighbour_cache_by_interface_id(int8_t interface_id)
|
|
{
|
|
protocol_interface_info_entry_t *interface = protocol_stack_interface_info_get_by_id(interface_id);
|
|
|
|
return interface ? &interface->ipv6_neighbour_cache : NULL;
|
|
}
|
|
|
|
void ipv6_send_queued(ipv6_neighbour_t *entry)
|
|
{
|
|
ns_list_foreach_safe(buffer_t, buf, &entry->queue) {
|
|
ns_list_remove(&entry->queue, buf);
|
|
tr_debug("Destination solved");
|
|
protocol_push(buf);
|
|
}
|
|
}
|
|
|
|
static void ipv6_trigger_resolve_query(protocol_interface_info_entry_t *cur_interface, buffer_t *buf, ipv6_neighbour_t *n)
|
|
{
|
|
if (n->state != IP_NEIGHBOUR_NEW && n->state != IP_NEIGHBOUR_INCOMPLETE) {
|
|
tr_debug("ipv6_resolve_query");
|
|
buffer_free(buf);
|
|
return;
|
|
}
|
|
|
|
uint_fast16_t count = ns_list_count(&n->queue);
|
|
while (count >= RESOLUTION_QUEUE_LIMIT) {
|
|
buffer_t *b = ns_list_get_first(&n->queue);
|
|
ns_list_remove(&n->queue, b);
|
|
socket_tx_buffer_event_and_free(b, SOCKET_NO_ROUTE);
|
|
count--;
|
|
}
|
|
tr_debug("Queueing for: %s", trace_ipv6(n->ip_address));
|
|
ns_list_add_to_end(&n->queue, buf);
|
|
|
|
if (n->state == IP_NEIGHBOUR_NEW) {
|
|
/* Start NS timers, send first NS */
|
|
ipv6_neighbour_set_state(&cur_interface->ipv6_neighbour_cache, n, IP_NEIGHBOUR_INCOMPLETE);
|
|
ipv6_interface_resolve_send_ns(&cur_interface->ipv6_neighbour_cache, n, false, 0);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Given a buffer with IP next-hop address and outgoing interface, find the
|
|
* neighbour entry, and if complete, write the link-layer address into the buffer
|
|
* destination, and return the Neighbour Cache entry.
|
|
* If we have an incomplete Neighbour Cache entry, start address resolution
|
|
* and queue the buffer, returning NULL.
|
|
*/
|
|
ipv6_neighbour_t *ipv6_interface_resolve_new(protocol_interface_info_entry_t *cur, buffer_t *buf)
|
|
{
|
|
buffer_routing_info_t *route = ipv6_buffer_route(buf);
|
|
if (!route) {
|
|
tr_warn("XXX ipv6_interface_resolve no route!");
|
|
// Can this happen? How did it get to this interface in the first place?
|
|
// If it can happen, send ICMP Destination Unreachable
|
|
buffer_free(buf);
|
|
return NULL;
|
|
}
|
|
ipv6_neighbour_t *n = ipv6_neighbour_lookup_or_create(&cur->ipv6_neighbour_cache, route->route_info.next_hop_addr);
|
|
if (!n) {
|
|
// If it can happen, send ICMP Destination Unreachable
|
|
tr_warn("No heap for address resolve");
|
|
buffer_free(buf);
|
|
return NULL;
|
|
}
|
|
|
|
if (n->state == IP_NEIGHBOUR_NEW || n->state == IP_NEIGHBOUR_INCOMPLETE) {
|
|
addrtype_t ll_type;
|
|
const uint8_t *ll_addr;
|
|
|
|
if (cur->if_map_ip_to_link_addr &&
|
|
cur->if_map_ip_to_link_addr(cur, route->route_info.next_hop_addr, &ll_type, &ll_addr)) {
|
|
ipv6_neighbour_update_from_na(&cur->ipv6_neighbour_cache, n, NA_O, ll_type, ll_addr);
|
|
}
|
|
}
|
|
|
|
if (n->state == IP_NEIGHBOUR_NEW || n->state == IP_NEIGHBOUR_INCOMPLETE) {
|
|
ipv6_trigger_resolve_query(cur, buf, n);
|
|
return NULL;
|
|
}
|
|
|
|
buf->dst_sa.addr_type = n->ll_type;
|
|
memcpy(buf->dst_sa.address, n->ll_address, addr_len_from_type(n->ll_type));
|
|
|
|
/* Optimisation trick - if security bypass is set, this is presumably some
|
|
* sort of MLE-type link management packet. Not worth sending extra NS/NA
|
|
* noise for these.
|
|
*/
|
|
if (!(buf->options.ll_security_bypass_tx && addr_is_ipv6_link_local(route->route_info.next_hop_addr))) {
|
|
n = ipv6_neighbour_used(&cur->ipv6_neighbour_cache, n);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/* Attempt a mapping from current information (neighbour cache, hard mappings) */
|
|
bool ipv6_map_ip_to_ll(protocol_interface_info_entry_t *cur, ipv6_neighbour_t *n, const uint8_t ip_addr[16], addrtype_t *ll_type, const uint8_t **ll_addr_out)
|
|
{
|
|
if (!n) {
|
|
n = ipv6_neighbour_lookup(&cur->ipv6_neighbour_cache, ip_addr);
|
|
}
|
|
if (n && !(n->state == IP_NEIGHBOUR_NEW || n->state == IP_NEIGHBOUR_INCOMPLETE)) {
|
|
*ll_type = n->ll_type;
|
|
*ll_addr_out = n->ll_address;
|
|
return true;
|
|
}
|
|
|
|
if (cur->if_map_ip_to_link_addr &&
|
|
cur->if_map_ip_to_link_addr(cur, ip_addr, ll_type, ll_addr_out)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Attempt a mapping from current information (neighbour cache, hard mappings) */
|
|
bool ipv6_map_ll_to_ip_link_local(protocol_interface_info_entry_t *cur, addrtype_t ll_type, const uint8_t *ll_addr, uint8_t ip_addr_out[16])
|
|
{
|
|
if (cur->if_map_link_addr_to_ip &&
|
|
cur->if_map_link_addr_to_ip(cur, ll_type, ll_addr, ip_addr_out)) {
|
|
return true;
|
|
}
|
|
|
|
ns_list_foreach(ipv6_neighbour_t, n, &cur->ipv6_neighbour_cache.list) {
|
|
if (ipv6_neighbour_ll_addr_match(n, ll_type, ll_addr) && addr_is_ipv6_link_local(n->ip_address)) {
|
|
memcpy(ip_addr_out, n->ip_address, 16);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* To comply with ETX returns 0xFFFF when neighbor doesn't exist and 0 when neighbor is currently unknown. */
|
|
uint16_t ipv6_map_ip_to_ll_and_call_ll_addr_handler(protocol_interface_info_entry_t *cur, int8_t interface_id, ipv6_neighbour_t *n, const uint8_t ipaddr[16], ll_addr_handler_t *ll_addr_handler_ptr)
|
|
{
|
|
addrtype_t ll_type;
|
|
const uint8_t *ll_addr;
|
|
|
|
if (!cur) {
|
|
cur = protocol_stack_interface_info_get_by_id(interface_id);
|
|
if (!cur) {
|
|
return 0xFFFF;
|
|
}
|
|
}
|
|
|
|
if (!ipv6_map_ip_to_ll(cur, n, ipaddr, &ll_type, &ll_addr)) {
|
|
return 0;
|
|
}
|
|
|
|
return ll_addr_handler_ptr(cur->id, ll_type, ll_addr);
|
|
}
|