mbed-os/features/nanostack/sal-stack-nanostack/source/Common_Protocols/ipv6.c

1443 lines
53 KiB
C

/*
* Copyright (c) 2013-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.
*/
#include "nsconfig.h"
#define _HAVE_IPV6
#ifdef _HAVE_IPV6
#include "ns_types.h"
#include "string.h"
#include "nsdynmemLIB.h"
#include "Core/include/socket.h"
#include "ns_trace.h"
#include "NWK_INTERFACE/Include/protocol.h"
#include "NWK_INTERFACE/Include/protocol_stats.h"
#include "Common_Protocols/ipv6.h"
#include "Common_Protocols/ipv6_fragmentation.h"
#include "ipv6_stack/protocol_ipv6.h"
#include "ipv6_stack/ipv6_routing_table.h"
#include "Common_Protocols/ip.h"
#include "Common_Protocols/icmpv6.h"
#include "Common_Protocols/ipv6_resolution.h"
#include "Common_Protocols/ipv6_flow.h"
#include "RPL/rpl_data.h"
#ifdef HAVE_MPL
#include "MPL/mpl.h"
#endif
#include "Service_Libs/nd_proxy/nd_proxy.h"
#include "common_functions.h"
#define TRACE_GROUP "ipv6"
static buffer_t *ipv6_consider_forwarding_multicast_packet(buffer_t *buf, protocol_interface_info_entry_t *cur, bool for_us);
static bool ipv6_packet_is_for_us(buffer_t *buf)
{
protocol_interface_info_entry_t *cur = buf->interface;
if (buf->dst_sa.addr_type != ADDR_IPV6) {
return false;
}
if (addr_is_ipv6_multicast(buf->dst_sa.address)) {
return addr_am_group_member_on_interface(cur, buf->dst_sa.address);
} else if (addr_is_ipv6_loopback(buf->dst_sa.address)) {
// We should have already dropped it if received externally
return true;
} else {
return addr_interface_address_compare(cur, buf->dst_sa.address) == 0;
}
}
static ipv6_exthdr_provider_fn_t *ipv6_exthdr_provider[ROUTE_MAX];
/* On entry:
* buf->route filled in with data from routing lookup, including source info
* buf->dst = final destination
* buf->src = source
* Other metadata available (eg rpl info from parsing in up)
* On exit:
* buf->dst modified to next hop if source routing
* Also possible to modify buf->route->next_hop_addr (eg RPL return to sender,
* for forwarding-error, except where would you get the parent addr?)
*
* Return negative if failed (will usually end up treated as "no route" error).
*/
static buffer_t *ipv6_get_exthdrs(buffer_t *buf, ipv6_exthdr_stage_t stage, int16_t *result)
{
if (ipv6_exthdr_provider[buf->route->route_info.source]) {
return ipv6_exthdr_provider[buf->route->route_info.source](buf, stage, result);
}
*result = 0;
return buf;
}
void ipv6_set_exthdr_provider(ipv6_route_src_t src, ipv6_exthdr_provider_fn_t *fn)
{
ipv6_exthdr_provider[src] = fn;
}
/* If next_if != NULL, this sends to next_hop on that interface */
buffer_routing_info_t *ipv6_buffer_route_to(buffer_t *buf, const uint8_t *next_hop, protocol_interface_info_entry_t *next_if)
{
buffer_routing_info_t *route;
if (buf->route) {
return buf->route;
}
buf->route = route = ns_dyn_mem_temporary_alloc(sizeof(buffer_routing_info_t));
if (!route) {
return NULL;
}
route->ip_dest = NULL;
route->ref_count = 1;
/* Realm-or-lower scope addresses must have interface specified */
bool interface_specific = addr_ipv6_scope(buf->dst_sa.address, buf->interface) <= IPV6_SCOPE_REALM_LOCAL;
protocol_interface_info_entry_t *cur = buf->interface;
if (cur == NULL && interface_specific) {
goto no_route;
}
ipv6_destination_t *dest_entry = ipv6_destination_lookup_or_create(buf->dst_sa.address, cur ? cur->id : -1);
if (!dest_entry) {
tr_err("ipv6_buffer_route no destination entry %s", trace_ipv6(buf->dst_sa.address));
goto no_route;
}
#ifdef HAVE_IPV6_ND
if (!next_hop && dest_entry->redirected) {
next_hop = dest_entry->redirect_addr;
next_if = protocol_stack_interface_info_get_by_id(dest_entry->interface_id);
}
#endif
if (next_hop && next_if) {
if (interface_specific && next_if != buf->interface) {
tr_err("Next hop interface mismatch %s%%%d vs %s%%%d", trace_ipv6(buf->dst_sa.address), buf->interface->id,
trace_ipv6(next_hop), next_if->id);
}
memcpy(route->route_info.next_hop_addr, next_hop, 16);
route->route_info.interface_id = next_if->id;
route->route_info.pmtu = 0xFFFF;
route->route_info.source = ROUTE_REDIRECT;
} else if (addr_is_ipv6_multicast(buf->dst_sa.address)) {
/*
* Multicast is handled specially - we always treat as on-link, so the
* only thing used from the routing table is the interface selection. This
* is what most network stacks do - basically means we direct on the
* interface leading to the default router in most cases. We don't support
* directing to a unicast next-hop, unless this was manually requested.
*/
if (!cur) {
/* Choose interface (only) from routing table */
ipv6_route_t *ip_route = ipv6_route_choose_next_hop(buf->dst_sa.address, -1, NULL);
if (!ip_route) {
tr_debug("No route for multicast %s", trace_ipv6(buf->dst_sa.address));
goto no_route;
}
route->route_info.interface_id = ip_route->info.interface_id;
} else {
route->route_info.interface_id = cur->id;
}
memcpy(route->route_info.next_hop_addr, next_hop ? next_hop : buf->dst_sa.address, 16);
route->route_info.pmtu = 0xFFFF;
route->route_info.source = ROUTE_MULTICAST;
} else { /* unicast, normal */
ipv6_route_predicate_fn_t *predicate = NULL;
#ifdef HAVE_RPL
if (buf->rpl_instance_known) {
if (!cur) {
goto no_route;
}
/* Limit the route search so we don't match other RPL instances */
predicate = rpl_data_get_route_predicate(cur->rpl_domain, buf);
}
#endif
ipv6_route_t *ip_route = ipv6_route_choose_next_hop(buf->dst_sa.address, interface_specific ? cur->id : -1, predicate);
if (!ip_route) {
tr_debug("XXX ipv6_buffer_route no route to %s!", trace_ipv6(buf->dst_sa.address));
goto no_route;
}
route->route_info = ip_route->info;
/* Above line copies all the main route info, but for on-link routes, we
* need to fill in next hop from the destination address.
*/
if (ip_route->on_link) {
memcpy(route->route_info.next_hop_addr, buf->dst_sa.address, 16);
}
}
protocol_interface_info_entry_t *outgoing_if = protocol_stack_interface_info_get_by_id(route->route_info.interface_id);
if (!outgoing_if) {
goto no_route; // Shouldn't happen - internal error
}
#ifdef HAVE_MPL
if (outgoing_if->mpl_seed && buf->options.mpl_permitted &&
addr_is_ipv6_multicast(buf->dst_sa.address) &&
addr_ipv6_multicast_scope(buf->dst_sa.address) >= IPV6_SCOPE_REALM_LOCAL) {
/* Special handling for MPL. Once we have decided we're sending to a
* multicast next hop for a greater-than-realm-local destination,
* if we're functioning as an MPL seed on that interface, we turn this
* into an MPL route. If the destination matches a domain, MPL extension
* header processing will add the necessary headers, else it will get
* tunnelled to All-MPL-Forwarders (ff03::fc).
*/
route->route_info.source = ROUTE_MPL;
}
#endif
#ifndef NO_IPV6_PMTUD
/* Update PMTU with first-hop link MTU (for initialisation, and may need to
* reduce an existing entry if route has changed) */
if (dest_entry->pmtu > outgoing_if->ipv6_neighbour_cache.link_mtu) {
dest_entry->pmtu = outgoing_if->ipv6_neighbour_cache.link_mtu;
}
/* Route can also limit PMTU */
if (dest_entry->pmtu > route->route_info.pmtu) {
dest_entry->pmtu = route->route_info.pmtu;
}
/* Buffer then gets this PMTU (overwriting what we wrote from the route) */
route->route_info.pmtu = dest_entry->pmtu;
#endif
dest_entry->interface_id = route->route_info.interface_id;
if (!addr_is_ipv6_multicast(dest_entry->destination)) {
dest_entry->last_neighbour = ipv6_neighbour_lookup_or_create(&outgoing_if->ipv6_neighbour_cache, route->route_info.next_hop_addr);
}
//tr_debug("%s->last_neighbour := %s", trace_ipv6(dest_entry->destination), trace_ipv6(route->route_info.next_hop_addr));
if (!cur || route->route_info.interface_id != cur->id) {
protocol_interface_info_entry_t *new_if = protocol_stack_interface_info_get_by_id(route->route_info.interface_id);
if (!new_if) {
goto no_route;
}
buf->interface = new_if;
cur = new_if;
}
if (buf->src_sa.addr_type == ADDR_NONE) {
const uint8_t *src = addr_select_source(cur, buf->dst_sa.address, 0);
if (!src) {
tr_debug("No source address");
goto no_route;
}
memcpy(buf->src_sa.address, src, 16);
buf->src_sa.addr_type = ADDR_IPV6;
}
return route;
no_route:
ns_dyn_mem_free(buf->route);
return buf->route = NULL;
}
buffer_routing_info_t *ipv6_buffer_route(buffer_t *buf)
{
return ipv6_buffer_route_to(buf, NULL, NULL);
}
/* Compute total IP header size requirements, before its construction
* src and dst must be originator and final destination, and ipv6_buffer_route()
* must have been called. This only counts the FIRST IP header and its extension headers -
* RPL tunnelling takes more thought.
* Doesn't account for fragmentation header.
*/
uint16_t ipv6_header_size_required(buffer_t *buf)
{
uint8_t hbh_options = 2; // dummy 2 for initial alignment
uint16_t ip_size = IPV6_HDRLEN;
if (buf->options.ip_extflags & IPEXT_HBH_ROUTER_ALERT) {
hbh_options += 4;
}
#if 0
/* If RPL source routing */
{
ip_size += srh_size;
}
/* else If RPL up/down and NOT tunnelling, ie dest is RPL */
{
hbh_options += 6;
}
/* If MPL */
{
hbh_options += mpl_option_size; // 4 or 6
}
#endif
if (hbh_options != 2) {
ip_size += (hbh_options + 7) & ~ 7;
}
return ip_size;
}
uint16_t ipv6_max_unfragmented_payload(buffer_t *buf, uint16_t mtu_limit)
{
uint16_t ip_size = ipv6_header_size_required(buf);
uint16_t pmtu = ipv6_mtu(buf);
uint16_t frag_size;
if (pmtu < IPV6_MIN_LINK_MTU) {
/* Small "PMTU" doesn't actually reduce our fragment size */
frag_size = IPV6_MIN_LINK_MTU;
} else {
frag_size = pmtu;
}
if (mtu_limit && frag_size > mtu_limit) {
frag_size = mtu_limit;
}
/* If this is true, then we're exceeding a sub-minimum PMTU, so need to
* include a fragment header, despite not actually fragmenting (RFC 2460, RFC 6415)*/
if (frag_size > pmtu) {
ip_size += 8;
}
return frag_size - ip_size;
}
#ifdef NO_IP_FRAGMENT_TX
#define ipv6_dontfrag(buf) true
#else
#define ipv6_dontfrag(buf) buf->options.ipv6_dontfrag
#endif
#ifdef NO_IPV6_PMTUD
#define ipv6_use_min_mtu(buf) 1
#else
#define ipv6_use_min_mtu(buf) buf->options.ipv6_use_min_mtu
#endif
/* Return the IPV6 MTU to use for a buffer to a specified final destination.
* Gives result of Path MTU discovery, unless this is deactivated by
* a socket option.
* Note that the MTU returned may be less than the IPv6 minimum if there has
* been such a "Packet Too Big" response (possibly due to IPv4<->IPv6
* translation). In this case, it's up to the caller whether they want to
* obey that MTU (presumably avoiding sub-IPv6 fragmentation at that link),
* or use the IPv6 minimum (reducing work on other links, but presumably
* necessitating sub-IPv6 fragmentation there).
*/
uint16_t ipv6_mtu(buffer_t *buf)
{
int8_t use_min_mtu = ipv6_use_min_mtu(buf);
if (use_min_mtu == -1) {
use_min_mtu = addr_is_ipv6_multicast(buf->dst_sa.address);
}
if (use_min_mtu) {
return IPV6_MIN_LINK_MTU;
}
bool dontfrag = ipv6_dontfrag(buf);
if (dontfrag) {
return buf->interface->ipv6_neighbour_cache.link_mtu;
} else {
return buf->route->route_info.pmtu;
}
}
static bool ipv6_fragmentation_needed(buffer_t *buf)
{
return buffer_data_length(buf) > ipv6_mtu(buf);
}
/* Input: IP payload. dst/src as source and final destination, type=NH, tclass set.
* Output: IP header added. With RPL HbH/SRH if necessary.
* Buffer source/destination = IP source/destination (matching contents)
*/
buffer_t *ipv6_down(buffer_t *buf)
{
uint8_t *ptr;
protocol_interface_info_entry_t *cur = 0;
buffer_routing_info_t *route = ipv6_buffer_route(buf);
/* Note ipv6_buffer_route can change interface */
if (!route) {
tr_warn("ipv6_down route fail");
drop:
socket_tx_buffer_event_and_free(buf, SOCKET_NO_ROUTE);
return NULL;
}
cur = buf->interface;
if (cur == NULL) {
tr_debug("ipv6_down Drop by Interface_PTR");
goto drop;
}
if (buf->dst_sa.addr_type != ADDR_IPV6) {
tr_debug("IP:Dest Not IPV6");
goto drop;
}
if (buf->src_sa.addr_type != ADDR_IPV6) {
tr_debug("IP:SRC Not IPV6");
goto drop;
}
/* Allow this to be decided at the last moment */
if (buf->options.hop_limit == 0) {
buf->options.hop_limit = cur->cur_hop_limit;
}
/* Choose a flow label if required (RFC 6437) */
if (buf->options.flow_label == IPV6_FLOW_UNSPECIFIED) {
buf->options.flow_label = ipv6_flow_auto_label ? IPV6_FLOW_AUTOGENERATE : 0;
}
if (buf->options.flow_label < 0) {
buf->options.flow_label = ipv6_flow_5tuple(buf->src_sa.address, buf->dst_sa.address, buf->options.type, buf->src_sa.port, buf->dst_sa.port);
}
/* Routing system can insert extension headers now */
/* If they want IP destination address changed (eg if inserting a routing
* header), they can set buf->route.ip_dest.
*/
int16_t exthdr_result;
buf = ipv6_get_exthdrs(buf, IPV6_EXTHDR_INSERT, &exthdr_result);
if (!buf) {
return NULL;
}
if (exthdr_result < 0) {
goto drop;
}
uint16_t payload_len = buffer_data_length(buf);
//tr_debug("IP Header Len: %02x", ip_header_len);
uint16_t ip_hdr_len = ipv6_header_size_required(buf);
if ((buf = buffer_headroom(buf, ip_hdr_len)) == NULL) {
return (buf);
}
ptr = buffer_data_reserve_header(buf, ip_hdr_len);
ptr = common_write_32_bit((UINT32_C(6) << 28) |
((uint32_t)buf->options.traffic_class << 20) |
(buf->options.flow_label & 0xfffff), ptr);
ptr = common_write_16_bit((ip_hdr_len - IPV6_HDRLEN) + payload_len, ptr);
/* Remember position of Next Header octet - we'll fill it later */
uint8_t *nh_ptr = ptr++;
*ptr++ = buf->options.hop_limit;
if (addr_is_ipv6_multicast(buf->src_sa.address)) {
tr_err("Illegal source %s", tr_ipv6(ptr));
goto drop;
}
// Copy the source address (IPv6)
memcpy(ptr, buf->src_sa.address, 16);
ptr += 16;
// Copy the destination address (IPv6), either modified by routing, or
// the original final destination.
memcpy(ptr, buf->route->ip_dest ? buf->route->ip_dest : buf->dst_sa.address, 16);
// Last-minute enforcement of a couple of rules on destination from RFC 4291
if (addr_is_ipv6_unspecified(ptr) ||
(addr_is_ipv6_multicast(ptr) && addr_ipv6_multicast_scope(ptr) == 0)) {
tr_err("Illegal destination %s", tr_ipv6(ptr));
goto drop;
}
ptr += 16;
bool add_hbh = false;
if (buf->options.ip_extflags & IPEXT_HBH_ROUTER_ALERT) {
add_hbh = true;
}
/* This HbH insertion would conflict with insertion from ipv6_get_exthdrs. */
/* Fortunately cases never overlap. */
if (add_hbh) {
*nh_ptr = IPV6_NH_HOP_BY_HOP;
nh_ptr = ptr;
// Come back to fill these in later
*ptr++ = IPV6_NH_NONE;
*ptr++ = 0;
if (buf->options.ip_extflags & IPEXT_HBH_ROUTER_ALERT) {
*ptr++ = IPV6_OPTION_ROUTER_ALERT;
*ptr++ = 2; // Length
ptr = common_write_16_bit(IPV6_ROUTER_ALERT_MLD, ptr);
}
#if 0
if (multicast) {
//tr_debug("TRIG Len; %02x", buf->trickle_data_len);
*ptr++ = IPV6_OPTION_MPL;
*ptr++ = buf->trickle_data_len;
memcpy(ptr, buf->trickle_data_field, buf->trickle_data_len);
ptr += buf->trickle_data_len;
if (buf->trickle_data_len == 2) {
*ptr++ = IPV6_OPTION_PADN;
*ptr++ = 0;
}
}
#endif
uint8_t alignment = (ptr - nh_ptr) & 7;
if (alignment) {
uint8_t pad = 8 - alignment;
if (pad == 1) {
*ptr++ = IPV6_OPTION_PAD1;
} else {
*ptr++ = IPV6_OPTION_PADN;
*ptr++ = (pad -= 2);
while (pad) {
*ptr = 0, pad--;
}
}
}
/* Go back and fill in the length byte */
nh_ptr[1] = ((ptr - nh_ptr) >> 3) - 1;
}
#if 0
if (src_route_len) {
*nh_ptr = IPV6_NH_ROUTING;
nh_ptr = ptr++;
*ptr++ = src_route_len;
ptr = gen_source_route_set(ptr);
}
#endif
// Fill in the previous Next Header field
*nh_ptr = buf->options.type;
buf->options.type = 0;
buf->options.code = 0;
buf->info = (buffer_info_t)(B_DIR_DOWN | B_FROM_IPV6 | B_TO_IPV6_FWD);
/* Divert to fragmentation if necessary */
if (ipv6_fragmentation_needed(buf)) {
if (ipv6_dontfrag(buf)) {
tr_debug("Packet too big");
goto drop;
} else {
return ipv6_frag_down(buf);
}
}
return buf;
}
/* Input: IP packet, either locally-generated, or received and okay to forward
* (XXX can currently distinguish with buf->ip_routed_up)
* Buffer source/destination = IP source/destination (matching contents)
* Output: Either go back for another IP header (tunnel entry)
* or determine routing information, and pass to transmission.
* Next hop IP address will be in buf->route->route_info.next_hop_addr.
* (Or loop back up if it's for us).
*/
buffer_t *ipv6_forwarding_down(buffer_t *buf)
{
/* If it's for us, loop it back up. It goes back into forwarding up, as
* we should process Destination headers etc...
* (Note that we could theoretically go forwarding up/down a few times in
* the event of a weird Routing Header)
*/
if (ipv6_packet_is_for_us(buf)) {
if (addr_is_ipv6_multicast(buf->dst_sa.address)) {
if (buf->options.multicast_loop) {
buffer_t *clone = buffer_clone(buf);
if (clone) {
clone->options.multicast_loop = true; // Flags that this is the loopback
buffer_socket_set(clone, NULL);
clone->info = (buffer_info_t)(B_DIR_UP | B_FROM_IPV6_FWD | B_TO_IPV6_FWD);
protocol_push(clone);
}
buf->options.multicast_loop = false; // Clear flag, to ensure only 1 clone (eg if tunnelling)
}
} else {
buffer_socket_set(buf, NULL);
buf->info = (buffer_info_t)(B_DIR_UP | B_FROM_IPV6_FWD | B_TO_IPV6_FWD);
return buf;
}
}
/* Note ipv6_buffer_route can change interface */
if (!ipv6_buffer_route(buf)) {
protocol_stats_update(STATS_IP_NO_ROUTE, 1);
tr_info("ipv6_forwarding route fail");
return icmpv6_error(buf, NULL, ICMPV6_TYPE_ERROR_DESTINATION_UNREACH, ICMPV6_CODE_DST_UNREACH_NO_ROUTE, 0);
}
/* Consider multicast forwarding /before/ calling routing code to modify
* extension headers - if that actually decides to tunnel it will
* overwrite the buffer's src_sa and dst_sa, when we want to consider
* forwarding the inner packet. This ordering works out for our only
* header-modifying multicast case of MPL:
* 1) We never want to forward packets with MPL headers, which means the
* outer packet in a tunnel gets ignored anyway.
* 2) This also means we don't have to worry that we're forwarding packets
* with the extension header not filled in yet.
* If we ever do have a multicast system where we are working with
* extension headers and forwarding those across interfaces, ipv6_get_exthdrs
* system will need a rework - probably split the "try MODIFY" call from the
* subsequent "give me tunnel parameters" part.
*/
if (!buf->ip_routed_up && addr_is_ipv6_multicast(buf->dst_sa.address)) {
buf = ipv6_consider_forwarding_multicast_packet(buf, buf->interface, true);
}
/* Allow routing code to update extension headers */
int16_t exthdr_result;
buf = ipv6_get_exthdrs(buf, IPV6_EXTHDR_MODIFY, &exthdr_result);
if (!buf) {
return NULL;
}
if (exthdr_result < 0) {
goto drop;
}
/* Routing code may say it needs to tunnel to add headers - loop back to IP layer if requested */
if (exthdr_result == IPV6_EXTHDR_MODIFY_TUNNEL) {
/* Avoid an infinite loop in the event of routing code failure - never
* let them ask for tunnelling more than once.
*/
if (buf->options.tunnelled) {
tr_error("Tunnel recursion");
goto drop;
}
buf->options.tunnelled = true;
buf->options.ip_extflags = 0;
/* Provide tunnel source, unless already set */
if (buf->src_sa.addr_type == ADDR_NONE) {
protocol_interface_info_entry_t *cur = buf->interface;
if (!cur) {
goto drop;
}
if (addr_interface_select_source(cur, buf->src_sa.address, buf->dst_sa.address, 0) < 0) {
tr_error("No tunnel source address");
goto drop;
}
buf->src_sa.addr_type = ADDR_IPV6;
}
/* Hop Limit copied from inner packet (maybe already decremented) */
/* ECN copied from inner packet (RFC 6040 normal mode) */
#ifdef RFC6040_COMPATIBILITY_MODE
buf->options.traffic_class &= ~ IP_TCLASS_ECN_MASK;
#endif
/* DSCP copied from inner packet */
buf->options.type = IPV6_NH_IPV6;
if (ipv6_flow_auto_label) {
/* Compute new flow label from inner src, dst, flow (RFC 6438) */
const uint8_t *iphdr = buffer_data_pointer(buf);
uint_fast24_t flow = common_read_24_bit(iphdr + IPV6_HDROFF_FLOW_LABEL) & 0xFFFFF;
buf->options.flow_label = ipv6_flow_2tuple_flow(iphdr + IPV6_HDROFF_SRC_ADDR, iphdr + IPV6_HDROFF_DST_ADDR, flow);
} else {
buf->options.flow_label = 0;
}
buf->info = (buffer_info_t)(B_DIR_DOWN | B_FROM_IPV6_FWD | B_TO_IPV6);
return buf;
}
buf->info = (buffer_info_t)(B_DIR_DOWN | B_FROM_IPV6 | B_TO_IPV6_TXRX);
return buf;
drop:
socket_tx_buffer_event_and_free(buf, SOCKET_NO_ROUTE);
return NULL;
}
#if defined HAVE_RPL || defined HAVE_MPL
/* Tunnel exit is only needed (and allowed!) for RPL and MPL */
#define IP_ECN__DROP -1
/*
* 4x4 array implementing the ECN combination rules from RFC 6040.
*
* Summary visualisation: Outer
* N01C
* +----
* N|NNN-
* Inner 0|001C
* 1|111C
* C|CCCC
*
* Each of the 16 entries [outer][inner], with justification:
*/
static const int8_t exit_ecn_combination[4][4] = {
// If the inner is Not-ECT, we mustn't propagate ECN markings onwards
// So we strip off any capability flags
[IP_ECN_NOT_ECT][IP_ECN_NOT_ECT] = IP_ECN_NOT_ECT,
[IP_ECN_ECT_0 ][IP_ECN_NOT_ECT] = IP_ECN_NOT_ECT,
[IP_ECN_ECT_1 ][IP_ECN_NOT_ECT] = IP_ECN_NOT_ECT,
// And if we see congestion experienced, we drop to indicate congestion
[IP_ECN_CE ][IP_ECN_NOT_ECT] = IP_ECN__DROP,
// If inner supports ECN, we set outgoing to most severe (C>1>0>N)
[IP_ECN_NOT_ECT][IP_ECN_ECT_0 ] = IP_ECN_ECT_0,
[IP_ECN_ECT_0 ][IP_ECN_ECT_0 ] = IP_ECN_ECT_0,
[IP_ECN_ECT_1 ][IP_ECN_ECT_0 ] = IP_ECN_ECT_1,
[IP_ECN_CE ][IP_ECN_ECT_0 ] = IP_ECN_CE,
[IP_ECN_NOT_ECT][IP_ECN_ECT_1 ] = IP_ECN_ECT_1,
[IP_ECN_ECT_0 ][IP_ECN_ECT_1 ] = IP_ECN_ECT_1,
[IP_ECN_ECT_1 ][IP_ECN_ECT_1 ] = IP_ECN_ECT_1,
[IP_ECN_CE ][IP_ECN_ECT_1 ] = IP_ECN_CE,
[IP_ECN_NOT_ECT][IP_ECN_CE ] = IP_ECN_CE,
[IP_ECN_ECT_0 ][IP_ECN_CE ] = IP_ECN_CE,
[IP_ECN_ECT_1 ][IP_ECN_CE ] = IP_ECN_CE,
[IP_ECN_CE ][IP_ECN_CE ] = IP_ECN_CE,
};
/* On entry, buf is original IPv6 packet, payload points to outer packet's
* protocol payload (ie the start of the inner IPv6 header.)
*/
static buffer_t *ipv6_tunnel_exit(buffer_t *buf, uint8_t *payload)
{
/* We're stripping the IP header - need the HBH header for future reference */
if ((buf->options.ip_extflags & IPEXT_HBH_RPL) && !rpl_data_remember_outer(buf)) {
goto drop;
}
buf->options.ip_extflags = 0;
buffer_data_pointer_set(buf, payload);
/*
* First 8 bytes of the IP header that we need to patch:
* . . . . .
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |Version| DSCP |ECN| Flow Label |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Payload Length | Next Header | Hop Limit |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
if (buffer_data_length(buf) >= IPV6_HDRLEN) {
/* RFC 6040 - combine ECN */
uint8_t inner_tclass = (payload[0] << 4) | (payload[1] >> 4);
uint8_t outer_ecn = buf->options.traffic_class & IP_TCLASS_ECN_MASK;
uint8_t inner_ecn = inner_tclass & IP_TCLASS_ECN_MASK;
uint8_t inner_dscp = inner_tclass & IP_TCLASS_DSCP_MASK;
int8_t outgoing_ecn = exit_ecn_combination[outer_ecn][inner_ecn];
if (outgoing_ecn == IP_ECN__DROP) {
goto drop;
}
/* RFC 2983 uniform model - copy DSCP from inner packet */
uint8_t outgoing_tclass = inner_dscp | outgoing_ecn;
/* Write the outgoing traffic-class field */
payload[0] = (payload[0] & 0xf0) | (outgoing_tclass >> 4);
payload[1] = (outgoing_tclass << 4) | (payload[1] & 0x0f);
/* We would like RFC 3443-style "uniform model" Hop Limit handling. As
* tunnel entry, we transfer the Hop Limit from the inner to the outer
* packet. On exit we transfer from outer back to inner (and outer must
* be lower than inner). Just in case another entry implementation didn't do
* this and set a big outer, we take the minimum of inner and outer.
*/
if (payload[IPV6_HDROFF_HOP_LIMIT] > buf->options.hop_limit) {
payload[IPV6_HDROFF_HOP_LIMIT] = buf->options.hop_limit;
}
}
buf->info = (buffer_info_t)(B_DIR_UP | B_TO_IPV6_FWD | B_FROM_IPV6_FWD);
return buf;
drop:
protocol_stats_update(STATS_IP_RX_DROP, 1);
return buffer_free(buf);
}
#endif /* HAVE_MPL || HAVE_RPL */
static buffer_t *ipv6_handle_options(buffer_t *buf, protocol_interface_info_entry_t *cur, uint8_t *ptr, uint8_t nh, uint16_t payload_length, uint16_t *hdrlen_out, const sockaddr_t *ll_src, bool pre_fragment)
{
(void) nh;
if (payload_length < 2) {
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_PARAMETER_PROBLEM, ICMPV6_CODE_PARAM_PRB_HDR_ERR, 4);
}
uint16_t hlen = (ptr[1] + 1) * 8;
if (payload_length < hlen) {
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_PARAMETER_PROBLEM, ICMPV6_CODE_PARAM_PRB_HDR_ERR, (ptr + 1) - buffer_data_pointer(buf));
}
if (pre_fragment) {
*hdrlen_out = hlen;
return buf;
}
uint8_t *opt = ptr + 2;
const uint8_t *const end = ptr + hlen;
while (opt < end) {
if (opt[0] == IPV6_OPTION_PAD1) {
opt++;
continue;
}
uint8_t opt_type = *opt++;
if (opt >= end) {
goto len_err;
}
uint8_t optlen = *opt++;
if (opt + optlen > end) {
goto len_err;
}
switch (opt_type) {
#ifdef HAVE_RPL
case IPV6_OPTION_RPL:
if (!cur->rpl_domain) {
goto drop;
}
if (optlen < 4) {
goto len_err;
}
if (!rpl_data_process_hbh(buf, cur, opt, ll_src)) {
goto drop;
}
break;
#endif
#ifdef HAVE_MPL
case IPV6_OPTION_MPL:
if (!mpl_hbh_len_check(opt, optlen)) {
goto len_err;
}
if (!mpl_process_hbh(buf, cur, opt)) {
goto drop;
}
break;
#endif
default:
opt_type &= IPV6_OPTION_ACTION_MASK;
if (opt_type == IPV6_OPTION_ACTION_SKIP) {
break;
}
if (opt_type == IPV6_OPTION_ACTION_ERROR ||
(opt_type == IPV6_OPTION_ACTION_ERROR_UNICAST && !addr_is_ipv6_multicast(buf->dst_sa.address))) {
return icmpv6_error(buf, NULL, ICMPV6_TYPE_ERROR_PARAMETER_PROBLEM, ICMPV6_CODE_PARAM_PRB_UNREC_IPV6_OPT, (opt - 2) - buffer_data_pointer(buf));
}
/* falling to */
drop:
protocol_stats_update(STATS_IP_RX_DROP, 1);
return buffer_free(buf);
len_err:
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_PARAMETER_PROBLEM, ICMPV6_CODE_PARAM_PRB_HDR_ERR, (opt - 1) - buffer_data_pointer(buf));
}
opt += optlen;
}
*hdrlen_out = hlen;
return buf;
}
static buffer_t *ipv6_handle_routing_header(buffer_t *buf, protocol_interface_info_entry_t *cur, uint8_t *ptr, uint16_t payload_length, uint16_t *hdrlen_out, bool *forward_out, bool pre_fragment)
{
if (buf->options.ll_security_bypass_rx) {
tr_warn("Routing header: Security check fail");
protocol_stats_update(STATS_IP_RX_DROP, 1);
return buffer_free(buf);
}
if (payload_length < 4) {
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_PARAMETER_PROBLEM, ICMPV6_CODE_PARAM_PRB_HDR_ERR, 4);
}
uint16_t hlen = (ptr[1] + 1) * 8;
if (payload_length < hlen) {
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_PARAMETER_PROBLEM, ICMPV6_CODE_PARAM_PRB_HDR_ERR, (ptr + 1) - buffer_data_pointer(buf));
}
if (pre_fragment) {
*hdrlen_out = hlen;
return buf;
}
uint8_t type = ptr[2];
uint8_t segs_left = ptr[3];
switch (type) {
#ifdef HAVE_RPL
case IPV6_ROUTING_TYPE_RPL:
return rpl_data_process_routing_header(buf, cur, ptr, hdrlen_out, forward_out);
#endif
default:
/* Unknown type: if segments left is 0, we ignore the header, else return an error */
if (segs_left == 0) {
*hdrlen_out = hlen;
} else {
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_PARAMETER_PROBLEM, ICMPV6_CODE_PARAM_PRB_HDR_ERR, (ptr + 2) - buffer_data_pointer(buf));
}
break;
}
return buf;
}
static buffer_t *ipv6_consider_forwarding_unicast_packet(buffer_t *buf, protocol_interface_info_entry_t *cur, const sockaddr_t *ll_src)
{
/* Security checks needed here before forwarding */
if (buf->options.ll_security_bypass_rx) {
tr_warn("IP Forward: Security check fail dst %s", trace_ipv6(buf->dst_sa.address));
protocol_stats_update(STATS_IP_RX_DROP, 1);
return buffer_free(buf);
}
if (cur->if_special_forwarding) {
bool bounce = false;
buf = cur->if_special_forwarding(cur, buf, ll_src, &bounce);
if (!buf || bounce) {
return buf;
}
}
if (!cur->ip_forwarding ||
addr_is_ipv6_loopback(buf->dst_sa.address) ||
addr_is_ipv6_unspecified(buf->src_sa.address)) {
protocol_stats_update(STATS_IP_RX_DROP, 1);
return buffer_free(buf);
}
/* Hop limit check and decrement */
if (buf->options.hop_limit <= 1) {
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_TIME_EXCEEDED, ICMPV6_CODE_TME_EXCD_HOP_LIM_EXCD, 0);
}
/* Routing rules may require us not to send back to our predecessor */
buffer_note_predecessor(buf, ll_src);
buf->ip_routed_up = true;
buffer_data_pointer(buf)[IPV6_HDROFF_HOP_LIMIT] = --buf->options.hop_limit;
/* Not for us, let's think about forwarding */
/* Note ipv6_buffer_route can change interface */
buffer_routing_info_t *routing = ipv6_buffer_route(buf);
if (!routing) {
protocol_stats_update(STATS_IP_NO_ROUTE, 1);
#ifdef HAVE_RPL
if (rpl_data_forwarding_error(buf)) {
buf->info = (buffer_info_t)(B_DIR_DOWN | B_FROM_IPV6_FWD | B_TO_IPV6_FWD);
return buf;
}
#endif
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_DESTINATION_UNREACH, ICMPV6_CODE_DST_UNREACH_NO_ROUTE, 0);
}
protocol_interface_info_entry_t *out_interface;
out_interface = buf->interface;
#ifdef HAVE_RPL
/* We must not let RPL-bearing packets out of or into a RPL domain */
if (buf->options.ip_extflags & (IPEXT_HBH_RPL | IPEXT_SRH_RPL)) {
if (out_interface->rpl_domain != cur->rpl_domain || !rpl_data_is_rpl_route(routing->route_info.source)) {
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_DESTINATION_UNREACH, ICMPV6_CODE_DST_UNREACH_ADM_PROHIB, 0);
}
}
#endif
/* If heading out a different interface, some extra scope checks for
* crossing a zone boundary (see RFC 4007).
*/
if (out_interface->id != cur->id) {
uint_fast8_t src_scope = addr_ipv6_scope(buf->src_sa.address, cur);
/* Check source scope (standard RFC 4007 test) */
if (out_interface->zone_index[src_scope] != cur->zone_index[src_scope]) {
buf->interface = cur;
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_DESTINATION_UNREACH, ICMPV6_CODE_DST_UNREACH_BEYOND_SCOPE, 0);
}
//#ifdef THREAD_SUPPORT
/* Handle a Thread-specific wrinkle - Thread's "Realm-local" address
* is really a ULA, so is global on other interfaces, and is treated
* as such for routing purposes. There's nothing in the routing system
* stopping a packet going into or out of Thread with a Realm-local
* address. The generic code above has handled the case:
*
* Thread->External src=RL ("Beyond scope of source address")
*
* Here we block the other cases:
*
* External->Thread src=RL ("Source address failed ingress/egress policy")
* Thread<->External dst=RL ("Communication with destination administratively prohibited")
*
* (NB if a real Realm-local address was defined, then we'd make routing
* treat it like link-local, and we'd never find ourselves routing
* to another interface, catching the first two cases, and the last
* would have been caught by "Beyond scope of source address").
*/
if (addr_ipv6_scope(buf->src_sa.address, out_interface) <= IPV6_SCOPE_REALM_LOCAL) {
buf->interface = cur;
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_DESTINATION_UNREACH, ICMPV6_CODE_DST_UNREACH_SRC_FAILED_POLICY, 0);
}
if (addr_ipv6_scope(buf->dst_sa.address, out_interface) <= IPV6_SCOPE_REALM_LOCAL ||
addr_ipv6_scope(buf->dst_sa.address, cur) <= IPV6_SCOPE_REALM_LOCAL) {
buf->interface = cur;
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_DESTINATION_UNREACH, ICMPV6_CODE_DST_UNREACH_ADM_PROHIB, 0);
}
//#endif
}
/* route_info.pmtu will cover limits from both the interface and the
* route. As a bonus, it will also cover any PMTUD we happen to have done
* to that destination ourselves, as well as mop up any tunnelling issues.
*/
if (routing->route_info.pmtu < buffer_data_length(buf)) {
buf->interface = cur;
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_PACKET_TOO_BIG, 0, routing->route_info.pmtu);
}
/* Pass to "forwarding down" */
buf->info = (buffer_info_t)(B_DIR_DOWN | B_FROM_IPV6_FWD | B_TO_IPV6_FWD);
return buf;
}
void ipv6_transmit_multicast_on_interface(buffer_t *buf, protocol_interface_info_entry_t *cur)
{
/* Mess with routing to get this to go out the correct interface */
buf->interface = cur;
if (!ipv6_buffer_route_to(buf, buf->dst_sa.address, cur)) {
buffer_free(buf);
return;
}
/* Send straight to IP transmission? Or to forwarding down? */
buf->info = (buffer_info_t)(B_DIR_DOWN | B_FROM_IPV6_FWD | B_TO_IPV6_FWD);
protocol_push(buf);
}
#ifdef MULTICAST_FORWARDING
static void ipv6_forward_multicast_onto_interface(buffer_t *buf, protocol_interface_info_entry_t *cur)
{
if (!buf) {
return;
}
if (buf->ip_routed_up) {
buffer_data_pointer(buf)[IPV6_HDROFF_HOP_LIMIT] = --buf->options.hop_limit;
}
buf->ip_routed_up = true;
// Make sure we receive a copy if we are a member on this interface
buf->options.multicast_loop = true;
// Setting direction to DOWN indicates to special forwarding that hop limit is appropriate for outgoing interface
buf->info = (buffer_info_t)(B_DIR_DOWN | B_FROM_IPV6_FWD | B_TO_IPV6_FWD);
if (cur->if_special_multicast_forwarding) {
cur->if_special_multicast_forwarding(cur, buf);
}
ipv6_transmit_multicast_on_interface(buf, cur);
}
#endif
/* Traditional multicast forwarding - from one interface to other(s).
* Used for example for ff05::1 across the border router. This is limited to
* scop values 4 and up, as we assume realms don't cross interfaces.
*
* This is distinct from MPL, which runs its own forwarding strategy. That is
* used for scop 3 (realm). [It could run at higher scopes, except the MPL
* code currently only transmits on 1 interface - rather than repeating on
* multiple].
*
* The two can be mixed, eg by encapsulating an ff05::1 packet in an MPL
* packet to ff03::fc. That would be spread by the MPL forwarding logic, and
* the border router would de-encapsulate and forward to Ethernet via this.
*/
/* "For us" tells us that we need to return the buffer (or a copy) - if false, we don't */
/* This controls copying logic. */
static buffer_t *ipv6_consider_forwarding_multicast_packet(buffer_t *buf, protocol_interface_info_entry_t *cur, bool for_us)
{
/* Security checks needed here before forwarding */
if (buf->options.ll_security_bypass_rx) {
goto no_forward;
}
/* Locally-sourced packets are forwarded on the way down. Don't do it again if we loop back for applications */
if ((buf->info & B_DIR_MASK) == B_DIR_UP && buf->options.multicast_loop) {
goto no_forward;
}
/* Hop limit check */
if (buf->options.hop_limit <= 1) {
goto no_forward;
}
if (!cur->ip_multicast_forwarding ||
addr_is_ipv6_unspecified(buf->src_sa.address)) {
goto no_forward;
}
if (cur->if_special_multicast_forwarding) {
cur->if_special_multicast_forwarding(cur, buf);
}
#ifdef HAVE_MPL
/* MPL does its own thing - we do not perform any "native" forwarding */
if (buf->options.ip_extflags & IPEXT_HBH_MPL) {
goto no_forward;
}
#endif
#ifdef MULTICAST_FORWARDING
uint_fast8_t group_scope = addr_ipv6_multicast_scope(buf->dst_sa.address);
uint_fast8_t src_scope = addr_ipv6_scope(buf->src_sa.address, cur);
/* Look at reverse path - check our route to the source address */
ipv6_route_t *route = ipv6_route_choose_next_hop(buf->src_sa.address, cur->id, NULL);
/* Only forward if it came from the interface leading to the source address */
if (!route || route->info.interface_id != cur->id) {
goto no_forward;
}
/* Mess around to minimise copies - initially no interface needs a packet */
protocol_interface_info_entry_t *fwd_interface = NULL;
uint16_t ptb_mtu = 0xFFFF;
ns_list_foreach(protocol_interface_info_entry_t, interface, &protocol_interface_info_list) {
if (interface != cur &&
interface->ip_multicast_forwarding &&
interface->zone_index[group_scope] == cur->zone_index[group_scope] &&
interface->zone_index[src_scope] == cur->zone_index[src_scope] &&
(group_scope >= interface->ip_mcast_fwd_for_scope ||
addr_multicast_fwd_check(interface, buf->dst_sa.address))) {
/* This interface seems to want a packet. Couple more checks first */
if (buffer_data_length(buf) > interface->ipv6_neighbour_cache.link_mtu) {
if (interface->ipv6_neighbour_cache.link_mtu < ptb_mtu) {
ptb_mtu = interface->ipv6_neighbour_cache.link_mtu;
}
continue;
}
/* If we already have a previous interface to forward to, give them a clone now */
if (fwd_interface) {
ipv6_forward_multicast_onto_interface(buffer_clone(buf), fwd_interface);
}
/* And remember this interface */
fwd_interface = interface;
}
}
/* We may need to report "packet too big" */
if (ptb_mtu != 0xFFFF && cur->icmp_tokens) {
if (for_us || fwd_interface) {
/* Someone else still needs a packet - clone for the error */
buffer_t *clone = buffer_clone(buf);
if (clone) {
protocol_push(icmpv6_error(clone, cur, ICMPV6_TYPE_ERROR_PACKET_TOO_BIG, 0, ptb_mtu));
}
} else {
/* Noone else needs a packet - consume for the error */
protocol_push(icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_PACKET_TOO_BIG, 0, ptb_mtu));
return NULL;
}
}
if (fwd_interface) {
/* We have 1 remaining interface to forward onto - clone or not depending on whether we need it */
if (for_us) {
ipv6_forward_multicast_onto_interface(buffer_clone(buf), fwd_interface);
return buf;
} else {
ipv6_forward_multicast_onto_interface(buf, fwd_interface);
return NULL;
}
}
#endif
no_forward:
/* Base functionality - if it's for us, return it, else bin it */
if (for_us) {
return buf;
} else {
return buffer_free(buf);
}
}
buffer_t *ipv6_forwarding_up(buffer_t *buf)
{
uint8_t *ptr = buffer_data_pointer(buf);
uint16_t len = buffer_data_length(buf);
uint8_t *nh_ptr;
protocol_interface_info_entry_t *cur;
bool intercept = false;
/* When processing a reassembled packet, we don't reprocess headers from before the fragment header */
uint16_t frag_offset;
cur = buf->interface;
// Make sure that this is a v6 header (just in case...)
if (!cur || len < IPV6_HDRLEN || (*ptr >> 4) != 6) {
goto drop;
}
if (buf->options.ip_extflags & IPEXT_FRAGMENT) {
// Remember the flags for headers we parsed before the fragment header;
// we won't re-parse them, and expect the metadata (from the first fragment)
// to survive reassembly.
frag_offset = buf->offset;
buf->options.ip_extflags &= ~ IPEXT_FRAGMENT;
tr_debug("Processing fragment from %d", frag_offset);
} else {
// Clear all info - extension header parsers will set
frag_offset = 0;
buf->options.ip_extflags = 0;
}
buf->options.traffic_class = (ptr[0] << 4) | (ptr[1] >> 4);
// Just skip Flow Label for now
ptr += 4;
uint16_t payload_length = common_read_16_bit(ptr);
ptr += 2;
// "Parameter problem" needs this pointer to Next Header field
nh_ptr = ptr++;
// May as well note the outermost NH field now; will update later
// if we go to an upper layer.
buf->options.type = *nh_ptr;
buf->options.code = 0;
// Read the Hop Limit
buf->options.hop_limit = *ptr++;
// Remember the link-layer address for "special forwarding" check
sockaddr_t ll_src = buf->src_sa;
sockaddr_t ll_dst = buf->dst_sa;
// Get the Source Address
memcpy(buf->src_sa.address, ptr, 16);
buf->src_sa.addr_type = ADDR_IPV6;
ptr += 16;
// Get the Destination Address
memcpy(buf->dst_sa.address, ptr, 16);
buf->dst_sa.addr_type = ADDR_IPV6;
ptr += 16;
/* XXX I'm using ip_routed_up as a "received from outside this node" check
* Not sure if that was original intent... We do want to accept it if it came
* from inside this node.
*/
if (addr_is_ipv6_multicast(buf->src_sa.address) ||
(buf->ip_routed_up && (addr_is_ipv6_loopback(buf->dst_sa.address) ||
addr_is_ipv6_loopback(buf->src_sa.address)))
) {
goto drop;
}
if (addr_is_ipv6_multicast(buf->dst_sa.address)) {
/* RFC 4291 says we must drop multicast packets with scope 0 */
if (addr_ipv6_multicast_scope(buf->dst_sa.address) == 0) {
goto drop;
}
} else {
/* RFC 1122 and 1812 say we SHOULD silently discard packets with unicast IP
* address but link-layer multicast or broadcast. And we MUST NOT forward
* them. So catch them here.
*/
if (buf->options.ll_multicast_rx || buf->options.ll_broadcast_rx) {
goto drop;
}
}
/* If security bypass is set, we only allow link-local traffic (unicast
* or multicast with link-local scope), as per ZigBee IP
*/
if (buf->options.ll_security_bypass_rx) {
if (addr_ipv6_scope(buf->dst_sa.address, cur) != IPV6_SCOPE_LINK_LOCAL) {
goto drop;
}
}
if (IPV6_HDRLEN + payload_length > buffer_data_length(buf)) {
// Return "Parameter problem", pointing at Payload Length
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_PARAMETER_PROBLEM, ICMPV6_CODE_PARAM_PRB_HDR_ERR, 4);
}
/* Trim buffer if it's longer than payload length stated in IP header */
if (IPV6_HDRLEN + payload_length < buffer_data_length(buf)) {
buffer_data_length_set(buf, IPV6_HDRLEN + payload_length);
}
/* Handle any hop-by-hop options first, before checking destination */
if (*nh_ptr == IPV6_NH_HOP_BY_HOP) {
uint16_t hdrlen = 0;
if (payload_length == 0) {
// RFC 2675 - return "Parameter problem", pointing at Payload Length
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_PARAMETER_PROBLEM, ICMPV6_CODE_PARAM_PRB_HDR_ERR, 4);
}
buf = ipv6_handle_options(buf, cur, ptr, IPV6_NH_HOP_BY_HOP, payload_length, &hdrlen, &ll_src, ptr - buffer_data_pointer(buf) < frag_offset);
if (hdrlen == 0) {
/* Something went wrong - it will have freed buf or turned it into an ICMP error */
return buf;
}
nh_ptr = ptr;
ptr += hdrlen;
payload_length -= hdrlen;
}
if (buf->options.ll_not_ours_rx) {
/* Wasn't addressed to us, but interface sent it up. It must now tell us
* what to do with the packet. Options:
* 1) Drop it (it frees)
* 2) Bounce it (turns buffer into response, sets bounce true)
* 3) Accept or forward as normal based on IP destination (returns buffer, clearing ll_not_ours_rx)
* 4) Treat it as for us regardless of IP destination (returns buffer, leaving ll_not_ours_rx set)
*/
bool bounce = false;
buf = cur->if_snoop(cur, buf, &ll_dst, &ll_src, &bounce);
if (!buf || bounce) {
return buf;
}
intercept = buf->options.ll_not_ours_rx;
}
if (*nh_ptr == IPV6_NH_ICMPV6 && payload_length >= 4 && ptr[0] == ICMPV6_TYPE_INFO_NS) {
/* Treat as ours, let NS reply */
intercept = true;
}
#ifdef HAVE_MPL
/* We don't reprocess if this is a reassembly - each fragment is its own MPL
* Data Message, and we already processed them.
*/
if ((buf->options.ip_extflags & IPEXT_HBH_MPL) && !frag_offset) {
if (!mpl_forwarder_process_message(buf, NULL, false)) {
/* False return means "duplicate" or other reason not to process */
return buffer_free(buf);
}
}
#endif
bool for_us = intercept || ipv6_packet_is_for_us(buf);
if (addr_is_ipv6_multicast(buf->dst_sa.address)) {
/* Multicast forwarding is told whether we're interested. It may
* clone or take ownership of the buffer, depending on for_us. If not
* forwarding or for us, it will bin.
*/
buf = ipv6_consider_forwarding_multicast_packet(buf, cur, for_us);
if (!buf) {
return NULL;
}
} else { /* unicast */
if (!for_us) {
return ipv6_consider_forwarding_unicast_packet(buf, cur, &ll_src);
}
}
/* Its destination is us (or we're intercepting) - start munching headers */
/* Guess this should ultimately be the "CIPV6" up layer */
for (;;) {
uint16_t hdrlen = 0;
if (payload_length == 0 && *nh_ptr != IPV6_NH_NONE) {
// RFC 2675 - return "Parameter problem", pointing at Payload Length
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_PARAMETER_PROBLEM, ICMPV6_CODE_PARAM_PRB_HDR_ERR, 4);
}
switch (*nh_ptr) {
case IPV6_NH_NONE:
return buffer_free(buf);
case IPV6_NH_DEST_OPT:
buf = ipv6_handle_options(buf, cur, ptr, IPV6_NH_DEST_OPT, payload_length, &hdrlen, &ll_src, ptr - buffer_data_pointer(buf) < frag_offset);
nh_ptr = ptr;
break;
#ifndef NO_IP_FRAGMENT_RX
case IPV6_NH_FRAGMENT:
return ipv6_frag_up(buf, ptr, nh_ptr, payload_length);
#endif
case IPV6_NH_ROUTING: {
bool forward = false;
buf = ipv6_handle_routing_header(buf, cur, ptr, payload_length, &hdrlen, &forward, ptr - buffer_data_pointer(buf) < frag_offset);
if (forward) {
/* Note that forwarding will cope with looping back if next address is
* actually ours. We do want to always treat it as forwarding, as we do
* want hop counts decremented, etc.
*/
return ipv6_consider_forwarding_unicast_packet(buf, cur, &ll_src);
}
nh_ptr = ptr;
break;
}
case IPV6_NH_UDP:
buf->info = (buffer_info_t)(B_DIR_UP | B_TO_UDP | B_FROM_IPV6_FWD);
/* UDP may want to generate ICMP "port unreachable", so we leave the
* IP headers unconsumed, setting offset to point to the UDP header
*/
buf->options.type = IPV6_NH_UDP;
buf->offset = ptr - buffer_data_pointer(buf);
return buf;
#ifndef NO_TCP
case IPV6_NH_TCP:
buf->info = (buffer_info_t)(B_DIR_UP | B_TO_TCP | B_FROM_IPV6_FWD);
goto upper_layer;
#endif
case IPV6_NH_ICMPV6:
buf->info = (buffer_info_t)(B_DIR_UP | B_TO_ICMP | B_FROM_IPV6_FWD);
goto upper_layer;
#if defined HAVE_RPL || defined HAVE_MPL
case IPV6_NH_IPV6:
/* Tunnel support is only used for RPL or MPL. Only permit tunnel exit if there was
* a RPL or MPL HbH option header, or RPL SRH header. Gives security, as
* long as border router doesn't forward such packets into RPL/MPL domain.
*/
if (!(buf->options.ip_extflags & (IPEXT_HBH_RPL | IPEXT_SRH_RPL | IPEXT_HBH_MPL))) {
goto bad_nh;
}
buffer_note_predecessor(buf, &ll_src);
buf->options.type = *nh_ptr;
return ipv6_tunnel_exit(buf, ptr);
#endif
default: {
if (buf->options.ll_security_bypass_rx) {
goto bad_nh;
}
buffer_socket_set(buf, socket_lookup_ipv6(*nh_ptr, &buf->dst_sa, &buf->src_sa, true));
if (!buf->socket) {
goto bad_nh;
}
buf->info = (buffer_info_t)(B_DIR_UP | B_TO_APP | B_FROM_IPV6_FWD);
goto upper_layer;
}
bad_nh:
return icmpv6_error(buf, cur, ICMPV6_TYPE_ERROR_PARAMETER_PROBLEM, ICMPV6_CODE_PARAM_PRB_UNREC_NEXT_HDR, nh_ptr - buffer_data_pointer(buf));
}
if (hdrlen == 0) {
/* Something went wrong in an extension header - it will have freed buf or turned it into an ICMP error */
return buf;
}
ptr += hdrlen;
payload_length -= hdrlen;
}
upper_layer:
buf->options.type = *nh_ptr;
buffer_data_pointer_set(buf, ptr);
return buf;
drop:
protocol_stats_update(STATS_IP_RX_DROP, 1);
return buffer_free(buf);
}
#endif /* _HAVE_IPV6 */