/* * 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 */