| /** @file |
| * @brief IPv6 related functions |
| */ |
| |
| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /* By default this prints too much data, set the value to 1 to see |
| * neighbor cache contents. |
| */ |
| #define NET_DEBUG_NBR 0 |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(net_ipv6, CONFIG_NET_IPV6_LOG_LEVEL); |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| |
| #if defined(CONFIG_NET_IPV6_IID_STABLE) |
| #include <zephyr/random/random.h> |
| #include <mbedtls/md.h> |
| #endif /* CONFIG_NET_IPV6_IID_STABLE */ |
| |
| #include <zephyr/net/net_core.h> |
| #include <zephyr/net/net_pkt.h> |
| #include <zephyr/net/net_stats.h> |
| #include <zephyr/net/net_context.h> |
| #include <zephyr/net/net_mgmt.h> |
| #include <zephyr/net/virtual.h> |
| #include "net_private.h" |
| #include "connection.h" |
| #include "icmpv6.h" |
| #include "udp_internal.h" |
| #include "tcp_internal.h" |
| #include "ipv6.h" |
| #include "nbr.h" |
| #include "6lo.h" |
| #include "route.h" |
| #include "net_stats.h" |
| |
| BUILD_ASSERT(sizeof(struct in6_addr) == NET_IPV6_ADDR_SIZE); |
| |
| /* Timeout value to be used when allocating net buffer during various |
| * neighbor discovery procedures. |
| */ |
| #define ND_NET_BUF_TIMEOUT K_MSEC(100) |
| |
| /* Timeout for various buffer allocations in this file. */ |
| #define NET_BUF_TIMEOUT K_MSEC(50) |
| |
| /* Maximum reachable time value specified in RFC 4861 section |
| * 6.2.1. Router Configuration Variables, AdvReachableTime |
| */ |
| #define MAX_REACHABLE_TIME 3600000 |
| |
| int net_ipv6_create(struct net_pkt *pkt, |
| const struct in6_addr *src, |
| const struct in6_addr *dst) |
| { |
| NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv6_access, struct net_ipv6_hdr); |
| struct net_ipv6_hdr *ipv6_hdr; |
| uint8_t tc = 0; |
| |
| ipv6_hdr = (struct net_ipv6_hdr *)net_pkt_get_data(pkt, &ipv6_access); |
| if (!ipv6_hdr) { |
| return -ENOBUFS; |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_IP_DSCP_ECN)) { |
| net_ipv6_set_dscp(&tc, net_pkt_ip_dscp(pkt)); |
| net_ipv6_set_ecn(&tc, net_pkt_ip_ecn(pkt)); |
| } |
| |
| ipv6_hdr->vtc = 0x60 | ((tc >> 4) & 0x0F); |
| ipv6_hdr->tcflow = (tc << 4) & 0xF0; |
| ipv6_hdr->flow = 0U; |
| ipv6_hdr->len = 0U; |
| ipv6_hdr->nexthdr = 0U; |
| |
| /* Set the hop limit by default from net_pkt as that could |
| * be set for example when sending NS. If the limit is 0, |
| * then take the value from socket. |
| */ |
| ipv6_hdr->hop_limit = net_pkt_ipv6_hop_limit(pkt); |
| if (ipv6_hdr->hop_limit == 0U) { |
| if (net_ipv6_is_addr_mcast(dst)) { |
| if (net_pkt_context(pkt) != NULL) { |
| ipv6_hdr->hop_limit = |
| net_context_get_ipv6_mcast_hop_limit(net_pkt_context(pkt)); |
| } else { |
| ipv6_hdr->hop_limit = |
| net_if_ipv6_get_mcast_hop_limit(net_pkt_iface(pkt)); |
| } |
| } else { |
| if (net_pkt_context(pkt) != NULL) { |
| ipv6_hdr->hop_limit = |
| net_context_get_ipv6_hop_limit(net_pkt_context(pkt)); |
| } else { |
| ipv6_hdr->hop_limit = |
| net_if_ipv6_get_hop_limit(net_pkt_iface(pkt)); |
| } |
| } |
| } |
| |
| net_ipv6_addr_copy_raw(ipv6_hdr->dst, (uint8_t *)dst); |
| net_ipv6_addr_copy_raw(ipv6_hdr->src, (uint8_t *)src); |
| |
| net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr)); |
| net_pkt_set_ipv6_ext_len(pkt, 0); |
| |
| return net_pkt_set_data(pkt, &ipv6_access); |
| } |
| |
| int net_ipv6_finalize(struct net_pkt *pkt, uint8_t next_header_proto) |
| { |
| NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv6_access, struct net_ipv6_hdr); |
| struct net_ipv6_hdr *ipv6_hdr; |
| |
| net_pkt_set_overwrite(pkt, true); |
| |
| ipv6_hdr = (struct net_ipv6_hdr *)net_pkt_get_data(pkt, &ipv6_access); |
| if (!ipv6_hdr) { |
| return -ENOBUFS; |
| } |
| |
| ipv6_hdr->len = htons(net_pkt_get_len(pkt) - |
| sizeof(struct net_ipv6_hdr)); |
| |
| if (net_pkt_ipv6_next_hdr(pkt) != 255U) { |
| ipv6_hdr->nexthdr = net_pkt_ipv6_next_hdr(pkt); |
| } else { |
| ipv6_hdr->nexthdr = next_header_proto; |
| } |
| |
| net_pkt_set_data(pkt, &ipv6_access); |
| |
| if (net_pkt_ipv6_next_hdr(pkt) != 255U && |
| net_pkt_skip(pkt, net_pkt_ipv6_ext_len(pkt))) { |
| return -ENOBUFS; |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_UDP) && |
| next_header_proto == IPPROTO_UDP) { |
| return net_udp_finalize(pkt, false); |
| } else if (IS_ENABLED(CONFIG_NET_TCP) && |
| next_header_proto == IPPROTO_TCP) { |
| return net_tcp_finalize(pkt, false); |
| } else if (next_header_proto == IPPROTO_ICMPV6) { |
| return net_icmpv6_finalize(pkt, false); |
| } |
| |
| return 0; |
| } |
| |
| static inline bool ipv6_drop_on_unknown_option(struct net_pkt *pkt, |
| struct net_ipv6_hdr *hdr, |
| uint8_t opt_type, |
| uint16_t opt_type_offset) |
| { |
| /* RFC 2460 chapter 4.2 tells how to handle the unknown |
| * options by the two highest order bits of the option: |
| * |
| * 00: Skip over this option and continue processing the header. |
| * 01: Discard the packet. |
| * 10: Discard the packet and, regardless of whether or not the |
| * packet's Destination Address was a multicast address, |
| * send an ICMP Parameter Problem, Code 2, message to the packet's |
| * Source Address, pointing to the unrecognized Option Type. |
| * 11: Discard the packet and, only if the packet's Destination |
| * Address was not a multicast address, send an ICMP Parameter |
| * Problem, Code 2, message to the packet's Source Address, |
| * pointing to the unrecognized Option Type. |
| */ |
| NET_DBG("Unknown option %d (0x%02x) MSB %d - 0x%02x", |
| opt_type, opt_type, opt_type >> 6, opt_type & 0xc0); |
| |
| switch (opt_type & 0xc0) { |
| case 0x00: |
| return false; |
| case 0x40: |
| break; |
| case 0xc0: |
| if (net_ipv6_is_addr_mcast((struct in6_addr *)hdr->dst)) { |
| break; |
| } |
| |
| __fallthrough; |
| case 0x80: |
| net_icmpv6_send_error(pkt, NET_ICMPV6_PARAM_PROBLEM, |
| NET_ICMPV6_PARAM_PROB_OPTION, |
| (uint32_t)opt_type_offset); |
| break; |
| } |
| |
| return true; |
| } |
| |
| static inline int ipv6_handle_ext_hdr_options(struct net_pkt *pkt, |
| struct net_ipv6_hdr *hdr, |
| uint16_t pkt_len) |
| { |
| uint16_t exthdr_len = 0U; |
| uint16_t length = 0U; |
| |
| { |
| uint8_t val = 0U; |
| |
| if (net_pkt_read_u8(pkt, &val)) { |
| return -ENOBUFS; |
| } |
| exthdr_len = val * 8U + 8; |
| } |
| |
| if (exthdr_len > pkt_len) { |
| NET_DBG("Corrupted packet, extension header %d too long " |
| "(max %d bytes)", exthdr_len, pkt_len); |
| return -EINVAL; |
| } |
| |
| length += 2U; |
| |
| while (length < exthdr_len) { |
| uint16_t opt_type_offset; |
| uint8_t opt_type, opt_len; |
| |
| opt_type_offset = net_pkt_get_current_offset(pkt); |
| |
| /* Each extension option has type and length - except |
| * Pad1 which has only a type without any length |
| */ |
| if (net_pkt_read_u8(pkt, &opt_type)) { |
| return -ENOBUFS; |
| } |
| |
| if (opt_type != NET_IPV6_EXT_HDR_OPT_PAD1) { |
| if (net_pkt_read_u8(pkt, &opt_len)) { |
| return -ENOBUFS; |
| } |
| } |
| |
| switch (opt_type) { |
| case NET_IPV6_EXT_HDR_OPT_PAD1: |
| NET_DBG("PAD1 option"); |
| length++; |
| break; |
| case NET_IPV6_EXT_HDR_OPT_PADN: |
| NET_DBG("PADN option"); |
| length += opt_len + 2; |
| net_pkt_skip(pkt, opt_len); |
| break; |
| default: |
| /* Make sure that the option length is not too large. |
| * The former 1 + 1 is the length of extension type + |
| * length fields. |
| * The latter 1 + 1 is the length of the sub-option |
| * type and length fields. |
| */ |
| if (opt_len > (exthdr_len - (1 + 1 + 1 + 1))) { |
| return -EINVAL; |
| } |
| |
| if (ipv6_drop_on_unknown_option(pkt, hdr, |
| opt_type, opt_type_offset)) { |
| return -ENOTSUP; |
| } |
| |
| if (net_pkt_skip(pkt, opt_len)) { |
| return -ENOBUFS; |
| } |
| |
| length += opt_len + 2; |
| |
| break; |
| } |
| } |
| |
| return exthdr_len; |
| } |
| |
| #if defined(CONFIG_NET_ROUTE) |
| static struct net_route_entry *add_route(struct net_if *iface, |
| struct in6_addr *addr, |
| uint8_t prefix_len) |
| { |
| struct net_route_entry *route; |
| |
| route = net_route_lookup(iface, addr); |
| if (route) { |
| return route; |
| } |
| |
| route = net_route_add(iface, addr, prefix_len, addr, |
| NET_IPV6_ND_INFINITE_LIFETIME, |
| NET_ROUTE_PREFERENCE_LOW); |
| |
| NET_DBG("%s route to %s/%d iface %p", route ? "Add" : "Cannot add", |
| net_sprint_ipv6_addr(addr), prefix_len, iface); |
| |
| return route; |
| } |
| #endif /* CONFIG_NET_ROUTE */ |
| |
| static void ipv6_no_route_info(struct net_pkt *pkt, |
| struct in6_addr *src, |
| struct in6_addr *dst) |
| { |
| NET_DBG("Will not route pkt %p ll src %s to dst %s between interfaces", |
| pkt, net_sprint_ipv6_addr(src), |
| net_sprint_ipv6_addr(dst)); |
| } |
| |
| #if defined(CONFIG_NET_ROUTE) |
| static enum net_verdict ipv6_route_packet(struct net_pkt *pkt, |
| struct net_ipv6_hdr *hdr) |
| { |
| struct net_route_entry *route; |
| struct in6_addr *nexthop; |
| bool found; |
| |
| /* Check if the packet can be routed */ |
| if (IS_ENABLED(CONFIG_NET_ROUTING)) { |
| found = net_route_get_info(NULL, (struct in6_addr *)hdr->dst, |
| &route, &nexthop); |
| } else { |
| found = net_route_get_info(net_pkt_iface(pkt), |
| (struct in6_addr *)hdr->dst, |
| &route, &nexthop); |
| } |
| |
| if (found) { |
| int ret; |
| |
| if (IS_ENABLED(CONFIG_NET_ROUTING) && |
| (net_ipv6_is_ll_addr((struct in6_addr *)hdr->src) || |
| net_ipv6_is_ll_addr((struct in6_addr *)hdr->dst))) { |
| /* RFC 4291 ch 2.5.6 */ |
| ipv6_no_route_info(pkt, (struct in6_addr *)hdr->src, |
| (struct in6_addr *)hdr->dst); |
| goto drop; |
| } |
| |
| /* Used when detecting if the original link |
| * layer address length is changed or not. |
| */ |
| net_pkt_set_orig_iface(pkt, net_pkt_iface(pkt)); |
| |
| if (route) { |
| net_pkt_set_iface(pkt, route->iface); |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_ROUTING) && |
| net_pkt_orig_iface(pkt) != net_pkt_iface(pkt) && |
| !net_if_flag_is_set(net_pkt_orig_iface(pkt), NET_IF_IPV6_NO_ND)) { |
| /* If the route interface to destination is |
| * different than the original route, then add |
| * route to original source. |
| */ |
| NET_DBG("Route pkt %p from %p to %p", |
| pkt, net_pkt_orig_iface(pkt), |
| net_pkt_iface(pkt)); |
| |
| add_route(net_pkt_orig_iface(pkt), |
| (struct in6_addr *)hdr->src, 128); |
| } |
| |
| ret = net_route_packet(pkt, nexthop); |
| if (ret < 0) { |
| NET_DBG("Cannot re-route pkt %p via %s " |
| "at iface %p (%d)", |
| pkt, net_sprint_ipv6_addr(nexthop), |
| net_pkt_iface(pkt), ret); |
| } else { |
| return NET_OK; |
| } |
| } else { |
| struct net_if *iface = NULL; |
| int ret; |
| |
| if (net_if_ipv6_addr_onlink(&iface, (struct in6_addr *)hdr->dst)) { |
| ret = net_route_packet_if(pkt, iface); |
| if (ret < 0) { |
| NET_DBG("Cannot re-route pkt %p " |
| "at iface %p (%d)", |
| pkt, net_pkt_iface(pkt), ret); |
| } else { |
| return NET_OK; |
| } |
| } |
| |
| NET_DBG("No route to %s pkt %p dropped", |
| net_sprint_ipv6_addr(&hdr->dst), pkt); |
| } |
| |
| drop: |
| return NET_DROP; |
| } |
| #else |
| static inline enum net_verdict ipv6_route_packet(struct net_pkt *pkt, |
| struct net_ipv6_hdr *hdr) |
| { |
| ARG_UNUSED(pkt); |
| ARG_UNUSED(hdr); |
| |
| NET_DBG("DROP: Packet %p not for me", pkt); |
| |
| return NET_DROP; |
| } |
| |
| #endif /* CONFIG_NET_ROUTE */ |
| |
| |
| static enum net_verdict ipv6_forward_mcast_packet(struct net_pkt *pkt, |
| struct net_ipv6_hdr *hdr) |
| { |
| #if defined(CONFIG_NET_ROUTE_MCAST) |
| int routed; |
| |
| /* Continue processing without forwarding if: |
| * 1. routing loop could be created |
| * 2. the destination is of interface local scope |
| * 3. is from link local source |
| * 4. hop limit is or would become zero |
| */ |
| if (net_ipv6_is_addr_mcast((struct in6_addr *)hdr->src) || |
| net_ipv6_is_addr_mcast_iface((struct in6_addr *)hdr->dst) || |
| net_ipv6_is_ll_addr((struct in6_addr *)hdr->src) || hdr->hop_limit <= 1) { |
| return NET_CONTINUE; |
| } |
| |
| routed = net_route_mcast_forward_packet(pkt, hdr); |
| |
| if (routed < 0) { |
| return NET_DROP; |
| } |
| #endif /*CONFIG_NET_ROUTE_MCAST*/ |
| return NET_CONTINUE; |
| } |
| |
| static uint8_t extension_to_bitmap(uint8_t header, uint8_t ext_bitmap) |
| { |
| switch (header) { |
| case NET_IPV6_NEXTHDR_HBHO: |
| return NET_IPV6_EXT_HDR_BITMAP_HBHO; |
| case NET_IPV6_NEXTHDR_DESTO: |
| /* Destination header can appears twice */ |
| if (ext_bitmap & NET_IPV6_EXT_HDR_BITMAP_DESTO1) { |
| return NET_IPV6_EXT_HDR_BITMAP_DESTO2; |
| } |
| return NET_IPV6_EXT_HDR_BITMAP_DESTO1; |
| case NET_IPV6_NEXTHDR_ROUTING: |
| return NET_IPV6_EXT_HDR_BITMAP_ROUTING; |
| case NET_IPV6_NEXTHDR_FRAG: |
| return NET_IPV6_EXT_HDR_BITMAP_FRAG; |
| default: |
| return 0; |
| } |
| } |
| |
| static inline bool is_src_non_tentative_itself(struct in6_addr *src) |
| { |
| struct net_if_addr *ifaddr; |
| |
| ifaddr = net_if_ipv6_addr_lookup(src, NULL); |
| if (ifaddr != NULL && ifaddr->addr_state != NET_ADDR_TENTATIVE) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| enum net_verdict net_ipv6_input(struct net_pkt *pkt, bool is_loopback) |
| { |
| NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv6_access, struct net_ipv6_hdr); |
| NET_PKT_DATA_ACCESS_DEFINE(udp_access, struct net_udp_hdr); |
| NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct net_tcp_hdr); |
| struct net_if *pkt_iface = net_pkt_iface(pkt); |
| enum net_verdict verdict = NET_DROP; |
| int real_len = net_pkt_get_len(pkt); |
| uint8_t ext_bitmap = 0U; |
| uint16_t ext_len = 0U; |
| uint8_t current_hdr, nexthdr, prev_hdr_offset; |
| union net_proto_header proto_hdr; |
| struct net_ipv6_hdr *hdr; |
| struct net_if_mcast_addr *if_mcast_addr; |
| union net_ip_header ip; |
| int pkt_len; |
| |
| #if defined(CONFIG_NET_L2_IPIP) |
| struct net_pkt_cursor hdr_start; |
| |
| net_pkt_cursor_backup(pkt, &hdr_start); |
| #endif |
| |
| net_stats_update_ipv6_recv(pkt_iface); |
| |
| hdr = (struct net_ipv6_hdr *)net_pkt_get_data(pkt, &ipv6_access); |
| if (!hdr) { |
| NET_DBG("DROP: no buffer"); |
| goto drop; |
| } |
| |
| pkt_len = ntohs(hdr->len) + sizeof(struct net_ipv6_hdr); |
| if (real_len < pkt_len) { |
| NET_DBG("DROP: pkt len per hdr %d != pkt real len %d", |
| pkt_len, real_len); |
| goto drop; |
| } else if (real_len > pkt_len) { |
| net_pkt_update_length(pkt, pkt_len); |
| } |
| |
| NET_DBG("IPv6 packet len %d received from %s to %s", pkt_len, |
| net_sprint_ipv6_addr(&hdr->src), |
| net_sprint_ipv6_addr(&hdr->dst)); |
| |
| if (net_ipv6_is_addr_unspecified((struct in6_addr *)hdr->src)) { |
| /* If this is a possible DAD message, let it pass. Extra checks |
| * are done in duplicate address detection code to verify that |
| * the packet is ok. |
| */ |
| if (!(IS_ENABLED(CONFIG_NET_IPV6_DAD) && |
| net_ipv6_is_addr_solicited_node((struct in6_addr *)hdr->dst))) { |
| NET_DBG("DROP: src addr is %s", "unspecified"); |
| goto drop; |
| } |
| } |
| |
| if (net_ipv6_is_addr_mcast((struct in6_addr *)hdr->src) || |
| net_ipv6_is_addr_mcast_scope((struct in6_addr *)hdr->dst, 0)) { |
| NET_DBG("DROP: multicast packet"); |
| goto drop; |
| } |
| |
| if (!is_loopback) { |
| if (net_ipv6_is_addr_loopback((struct in6_addr *)hdr->dst) || |
| net_ipv6_is_addr_loopback((struct in6_addr *)hdr->src)) { |
| NET_DBG("DROP: ::1 packet"); |
| goto drop; |
| } |
| |
| if (net_ipv6_is_addr_mcast_iface((struct in6_addr *)hdr->dst) || |
| (net_ipv6_is_addr_mcast_group( |
| (struct in6_addr *)hdr->dst, |
| net_ipv6_unspecified_address()) && |
| (net_ipv6_is_addr_mcast_site((struct in6_addr *)hdr->dst) || |
| net_ipv6_is_addr_mcast_org((struct in6_addr *)hdr->dst)))) { |
| NET_DBG("DROP: invalid scope multicast packet"); |
| goto drop; |
| } |
| |
| /* We need to pass the packet through in case our address is |
| * tentative, as receiving a packet with a tentative address as |
| * source means that duplicate address has been detected. |
| * This check is done later on if routing features are enabled. |
| */ |
| if (!IS_ENABLED(CONFIG_NET_ROUTING) && !IS_ENABLED(CONFIG_NET_ROUTE_MCAST) && |
| is_src_non_tentative_itself((struct in6_addr *)hdr->src)) { |
| NET_DBG("DROP: src addr is %s", "mine"); |
| goto drop; |
| } |
| } |
| |
| /* Reconstruct TC field. */ |
| |
| if (IS_ENABLED(CONFIG_NET_IP_DSCP_ECN)) { |
| uint8_t tc = ((hdr->vtc << 4) & 0xF0) | ((hdr->tcflow >> 4) & 0x0F); |
| |
| net_pkt_set_ip_dscp(pkt, net_ipv6_get_dscp(tc)); |
| net_pkt_set_ip_ecn(pkt, net_ipv6_get_ecn(tc)); |
| } |
| |
| /* Check extension headers */ |
| net_pkt_set_ipv6_next_hdr(pkt, hdr->nexthdr); |
| net_pkt_set_ipv6_ext_len(pkt, 0); |
| net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr)); |
| net_pkt_set_ipv6_hop_limit(pkt, NET_IPV6_HDR(pkt)->hop_limit); |
| net_pkt_set_family(pkt, PF_INET6); |
| |
| if (!net_pkt_filter_ip_recv_ok(pkt)) { |
| /* drop the packet */ |
| NET_DBG("DROP: pkt filter"); |
| return NET_DROP; |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_ROUTE_MCAST) && |
| net_ipv6_is_addr_mcast((struct in6_addr *)hdr->dst) && !net_pkt_forwarding(pkt)) { |
| /* If the packet is a multicast packet and multicast routing |
| * is activated, we give the packet to the routing engine. |
| * |
| * But we only drop the packet if an error occurs, otherwise |
| * it might be eminent to respond on the packet on application |
| * layer. |
| */ |
| if (ipv6_forward_mcast_packet(pkt, hdr) == NET_DROP) { |
| NET_DBG("DROP: forward mcast"); |
| goto drop; |
| } |
| } |
| |
| if (!net_ipv6_is_addr_mcast((struct in6_addr *)hdr->dst)) { |
| if (!net_if_ipv6_addr_lookup_by_iface(pkt_iface, (struct in6_addr *)hdr->dst)) { |
| if (ipv6_route_packet(pkt, hdr) == NET_OK) { |
| return NET_OK; |
| } |
| |
| NET_DBG("DROP: no such address %s in iface %d", |
| net_sprint_ipv6_addr((struct in6_addr *)hdr->dst), |
| net_if_get_by_iface(pkt_iface)); |
| goto drop; |
| } |
| |
| /* If we receive a packet with ll source address fe80: and |
| * destination address is one of ours, and if the packet would |
| * cross interface boundary, then drop the packet. |
| * RFC 4291 ch 2.5.6 |
| */ |
| if (IS_ENABLED(CONFIG_NET_ROUTING) && |
| net_ipv6_is_ll_addr((struct in6_addr *)hdr->src) && |
| !net_if_ipv6_addr_lookup_by_iface( |
| pkt_iface, (struct in6_addr *)hdr->dst)) { |
| ipv6_no_route_info(pkt, (struct in6_addr *)hdr->src, |
| (struct in6_addr *)hdr->dst); |
| NET_DBG("DROP: cross interface boundary"); |
| goto drop; |
| } |
| } |
| |
| if ((IS_ENABLED(CONFIG_NET_ROUTING) || IS_ENABLED(CONFIG_NET_ROUTE_MCAST)) && |
| !is_loopback && is_src_non_tentative_itself((struct in6_addr *)hdr->src)) { |
| NET_DBG("DROP: src addr is %s", "mine"); |
| goto drop; |
| } |
| |
| if (net_ipv6_is_addr_mcast((struct in6_addr *)hdr->dst) && |
| !(net_ipv6_is_addr_mcast_iface((struct in6_addr *)hdr->dst) || |
| net_ipv6_is_addr_mcast_link_all_nodes((struct in6_addr *)hdr->dst))) { |
| /* If we receive a packet with a interface-local or |
| * link-local all-nodes multicast destination address we |
| * always have to pass it to the upper layer. |
| * |
| * For all other destination multicast addresses we have to |
| * check if one of the joined multicast groups on the |
| * originating interface of the packet matches. Otherwise the |
| * packet will be dropped. |
| * RFC4291 ch 2.7.1, ch 2.8 |
| */ |
| if_mcast_addr = net_if_ipv6_maddr_lookup( |
| (struct in6_addr *)hdr->dst, &pkt_iface); |
| |
| if (!if_mcast_addr || |
| !net_if_ipv6_maddr_is_joined(if_mcast_addr)) { |
| NET_DBG("DROP: packet for unjoined multicast address"); |
| goto drop; |
| } |
| } |
| |
| net_pkt_acknowledge_data(pkt, &ipv6_access); |
| |
| current_hdr = hdr->nexthdr; |
| ext_bitmap = extension_to_bitmap(current_hdr, ext_bitmap); |
| /* Offset of "nexthdr" in the IPv6 header */ |
| prev_hdr_offset = (uint8_t *)&hdr->nexthdr - (uint8_t *)hdr; |
| net_pkt_set_ipv6_hdr_prev(pkt, prev_hdr_offset); |
| |
| while (!net_ipv6_is_nexthdr_upper_layer(current_hdr)) { |
| int exthdr_len; |
| uint8_t ext_bit; |
| |
| NET_DBG("IPv6 next header %d", current_hdr); |
| |
| if (current_hdr == NET_IPV6_NEXTHDR_NONE) { |
| /* There is nothing after this header (see RFC 2460, |
| * ch 4.7), so we can drop the packet now. |
| * This is not an error case so do not update drop |
| * statistics. |
| */ |
| NET_DBG("DROP: none nexthdr"); |
| return NET_DROP; |
| } |
| |
| /* Offset of "nexthdr" in the Extension Header */ |
| prev_hdr_offset = net_pkt_get_current_offset(pkt); |
| |
| if (net_pkt_read_u8(pkt, &nexthdr)) { |
| NET_DBG("DROP: pkt invalid read"); |
| goto drop; |
| } |
| |
| /* Detect duplicated Extension headers */ |
| ext_bit = extension_to_bitmap(nexthdr, ext_bitmap); |
| if (ext_bit & ext_bitmap) { |
| goto bad_hdr; |
| } |
| ext_bitmap |= ext_bit; |
| |
| /* Make sure that nexthdr is valid, reject the Extension Header early otherwise. |
| * This is also important so that the "pointer" field in the ICMPv6 error |
| * message points to the "nexthdr" field. |
| */ |
| switch (nexthdr) { |
| case NET_IPV6_NEXTHDR_HBHO: |
| /* Hop-by-hop header can appear only once and must appear right after |
| * the IPv6 header. Consequently the "nexthdr" field of an Extension |
| * Header can never be an HBH option. |
| */ |
| goto bad_hdr; |
| |
| case NET_IPV6_NEXTHDR_DESTO: |
| case NET_IPV6_NEXTHDR_FRAG: |
| case NET_IPV6_NEXTHDR_NONE: |
| /* Valid values */ |
| break; |
| |
| default: |
| if (net_ipv6_is_nexthdr_upper_layer(nexthdr)) { |
| break; |
| } |
| goto bad_hdr; |
| } |
| |
| /* Process the current Extension Header */ |
| switch (current_hdr) { |
| case NET_IPV6_NEXTHDR_HBHO: |
| case NET_IPV6_NEXTHDR_DESTO: |
| /* Process options below */ |
| break; |
| |
| case NET_IPV6_NEXTHDR_FRAG: |
| if (IS_ENABLED(CONFIG_NET_IPV6_FRAGMENT)) { |
| net_pkt_set_ipv6_fragment_start( |
| pkt, |
| net_pkt_get_current_offset(pkt) - 1); |
| return net_ipv6_handle_fragment_hdr(pkt, hdr, |
| current_hdr); |
| } |
| |
| goto bad_hdr; |
| |
| default: |
| /* Unsupported */ |
| goto bad_hdr; |
| } |
| |
| exthdr_len = ipv6_handle_ext_hdr_options(pkt, hdr, pkt_len); |
| if (exthdr_len < 0) { |
| NET_DBG("DROP: extension hdr len (%d)", exthdr_len); |
| goto drop; |
| } |
| |
| ext_len += exthdr_len; |
| current_hdr = nexthdr; |
| /* Save the offset to "nexthdr" in case we need to overwrite it |
| * when processing a fragment header |
| */ |
| net_pkt_set_ipv6_hdr_prev(pkt, prev_hdr_offset); |
| } |
| |
| net_pkt_set_ipv6_ext_len(pkt, ext_len); |
| |
| switch (current_hdr) { |
| case IPPROTO_ICMPV6: |
| verdict = net_icmpv6_input(pkt, hdr); |
| break; |
| case IPPROTO_TCP: |
| proto_hdr.tcp = net_tcp_input(pkt, &tcp_access); |
| if (proto_hdr.tcp) { |
| verdict = NET_OK; |
| } |
| |
| NET_DBG("%s verdict %s", "TCP", net_verdict2str(verdict)); |
| break; |
| case IPPROTO_UDP: |
| proto_hdr.udp = net_udp_input(pkt, &udp_access); |
| if (proto_hdr.udp) { |
| verdict = NET_OK; |
| } |
| |
| NET_DBG("%s verdict %s", "UDP", net_verdict2str(verdict)); |
| break; |
| |
| #if defined(CONFIG_NET_L2_IPIP) |
| case IPPROTO_IPV6: |
| case IPPROTO_IPIP: { |
| struct sockaddr_in6 remote_addr = { 0 }; |
| struct net_if *tunnel_iface; |
| |
| remote_addr.sin6_family = AF_INET6; |
| net_ipv6_addr_copy_raw((uint8_t *)&remote_addr.sin6_addr, hdr->src); |
| |
| net_pkt_set_remote_address(pkt, (struct sockaddr *)&remote_addr, |
| sizeof(struct sockaddr_in6)); |
| |
| /* Get rid of the old IP header */ |
| net_pkt_cursor_restore(pkt, &hdr_start); |
| net_pkt_pull(pkt, net_pkt_ip_hdr_len(pkt) + |
| net_pkt_ipv6_ext_len(pkt)); |
| |
| tunnel_iface = net_ipip_get_virtual_interface(net_pkt_iface(pkt)); |
| if (tunnel_iface != NULL && net_if_l2(tunnel_iface)->recv != NULL) { |
| return net_if_l2(tunnel_iface)->recv(net_pkt_iface(pkt), pkt); |
| } |
| } |
| #endif |
| } |
| |
| if (verdict == NET_DROP) { |
| NET_DBG("DROP: because verdict"); |
| goto drop; |
| } else if (current_hdr == IPPROTO_ICMPV6) { |
| NET_DBG("%s verdict %s", "ICMPv6", net_verdict2str(verdict)); |
| return verdict; |
| } |
| |
| ip.ipv6 = hdr; |
| |
| verdict = net_conn_input(pkt, &ip, current_hdr, &proto_hdr); |
| |
| NET_DBG("%s verdict %s", "Connection", net_verdict2str(verdict)); |
| |
| if (verdict != NET_DROP) { |
| return verdict; |
| } |
| |
| drop: |
| net_stats_update_ipv6_drop(pkt_iface); |
| return NET_DROP; |
| |
| bad_hdr: |
| /* Send error message about parameter problem (RFC 2460) */ |
| net_icmpv6_send_error(pkt, NET_ICMPV6_PARAM_PROBLEM, |
| NET_ICMPV6_PARAM_PROB_NEXTHEADER, |
| net_pkt_get_current_offset(pkt) - 1); |
| |
| NET_DBG("DROP: Unknown/wrong nexthdr type"); |
| net_stats_update_ip_errors_protoerr(pkt_iface); |
| |
| return NET_DROP; |
| } |
| |
| #if defined(CONFIG_NET_IPV6_IID_STABLE) |
| static bool check_reserved(const uint8_t *buf, size_t len) |
| { |
| /* Subnet-Router Anycast (RFC 4291) */ |
| if (memcmp(buf, (uint8_t *)&(struct in6_addr)IN6ADDR_ANY_INIT, len) == 0) { |
| return true; |
| } |
| |
| /* Reserved Subnet Anycast Addresses (RFC 2526) |
| * FDFF:FFFF:FFFF:FF80 - FDFF:FFFF:FFFF:FFFF |
| */ |
| if (buf[0] == 0xFD && buf[1] == 0xFF && buf[2] == 0xFF && |
| buf[3] == 0xFF && buf[4] == 0xFF && buf[5] == 0xFF && |
| buf[6] == 0xFF && buf[7] >= 0x80) { |
| return true; |
| } |
| |
| return false; |
| } |
| #endif /* CONFIG_NET_IPV6_IID_STABLE */ |
| |
| static int gen_stable_iid(uint8_t if_index, |
| const struct in6_addr *prefix, |
| uint8_t *network_id, size_t network_id_len, |
| uint8_t dad_counter, |
| uint8_t *stable_iid, |
| size_t stable_iid_len) |
| { |
| #if defined(CONFIG_NET_IPV6_IID_STABLE) |
| const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); |
| mbedtls_md_context_t ctx; |
| uint8_t digest[32]; |
| int ret; |
| static bool once; |
| static uint8_t secret_key[16]; /* Min 128 bits, RFC 7217 ch 5 */ |
| struct { |
| struct in6_addr prefix; |
| uint8_t if_index; |
| uint8_t network_id[16]; |
| uint8_t dad_counter; |
| } buf = { |
| .dad_counter = dad_counter, |
| }; |
| |
| if (prefix == NULL) { |
| NET_ERR("IPv6 prefix must be set for generating a stable IID"); |
| return -EINVAL; |
| } |
| |
| memcpy(&buf.prefix, prefix, sizeof(struct in6_addr)); |
| |
| buf.if_index = if_index; |
| |
| if (network_id != NULL && network_id_len > 0) { |
| memcpy(buf.network_id, network_id, |
| MIN(network_id_len, sizeof(buf.network_id))); |
| } |
| |
| if (!once) { |
| sys_rand_get(&secret_key, sizeof(secret_key)); |
| once = true; |
| } |
| |
| mbedtls_md_init(&ctx); |
| mbedtls_md_setup(&ctx, md_info, true); |
| ret = mbedtls_md_hmac_starts(&ctx, secret_key, sizeof(secret_key)); |
| if (ret != 0) { |
| NET_DBG("Cannot %s hmac (%d)", "start", ret); |
| goto err; |
| } |
| |
| ret = mbedtls_md_hmac_update(&ctx, (uint8_t *)&buf, sizeof(buf)); |
| if (ret != 0) { |
| NET_DBG("Cannot %s hmac (%d)", "update", ret); |
| goto err; |
| } |
| |
| ret = mbedtls_md_hmac_finish(&ctx, digest); |
| if (ret != 0) { |
| NET_DBG("Cannot %s hmac (%d)", "finish", ret); |
| goto err; |
| } |
| |
| memcpy(stable_iid, digest, MIN(sizeof(digest), stable_iid_len)); |
| |
| /* Check reserved addresses, RFC 5453 ch 3 */ |
| if (unlikely(check_reserved(stable_iid, stable_iid_len))) { |
| LOG_HEXDUMP_DBG(stable_iid, stable_iid_len, |
| "Generated IID is reserved"); |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| err: |
| mbedtls_md_free(&ctx); |
| |
| return ret; |
| #else |
| return -ENOTSUP; |
| #endif |
| } |
| |
| int net_ipv6_addr_generate_iid(struct net_if *iface, |
| const struct in6_addr *prefix, |
| uint8_t *network_id, |
| size_t network_id_len, |
| uint8_t dad_counter, |
| struct in6_addr *addr, |
| struct net_linkaddr *lladdr) |
| { |
| struct in6_addr tmp_addr; |
| uint8_t if_index; |
| |
| if_index = (iface == NULL) ? net_if_get_by_iface(net_if_get_default()) |
| : net_if_get_by_iface(iface); |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6_IID_STABLE)) { |
| struct in6_addr tmp_prefix = { 0 }; |
| int ret; |
| |
| if (prefix == NULL) { |
| UNALIGNED_PUT(htonl(0xfe800000), &tmp_prefix.s6_addr32[0]); |
| } else { |
| UNALIGNED_PUT(prefix->s6_addr32[0], &tmp_prefix.s6_addr32[0]); |
| UNALIGNED_PUT(prefix->s6_addr32[1], &tmp_prefix.s6_addr32[1]); |
| } |
| |
| ret = gen_stable_iid(if_index, &tmp_prefix, network_id, network_id_len, |
| dad_counter, (uint8_t *)&tmp_addr + 8, |
| sizeof(tmp_addr) / 2); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| if (prefix == NULL) { |
| UNALIGNED_PUT(htonl(0xfe800000), &tmp_addr.s6_addr32[0]); |
| UNALIGNED_PUT(0, &tmp_addr.s6_addr32[1]); |
| } else { |
| UNALIGNED_PUT(prefix->s6_addr32[0], &tmp_addr.s6_addr32[0]); |
| UNALIGNED_PUT(prefix->s6_addr32[1], &tmp_addr.s6_addr32[1]); |
| } |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6_IID_EUI_64)) { |
| switch (lladdr->len) { |
| case 2: |
| /* The generated IPv6 shall not toggle the |
| * Universal/Local bit. RFC 6282 ch 3.2.2 |
| */ |
| if (lladdr->type == NET_LINK_IEEE802154) { |
| UNALIGNED_PUT(0, &tmp_addr.s6_addr32[2]); |
| tmp_addr.s6_addr[11] = 0xff; |
| tmp_addr.s6_addr[12] = 0xfe; |
| tmp_addr.s6_addr[13] = 0U; |
| tmp_addr.s6_addr[14] = lladdr->addr[0]; |
| tmp_addr.s6_addr[15] = lladdr->addr[1]; |
| } |
| |
| break; |
| case 6: |
| /* We do not toggle the Universal/Local bit |
| * in Bluetooth. See RFC 7668 ch 3.2.2 |
| */ |
| memcpy(&tmp_addr.s6_addr[8], lladdr->addr, 3); |
| tmp_addr.s6_addr[11] = 0xff; |
| tmp_addr.s6_addr[12] = 0xfe; |
| memcpy(&tmp_addr.s6_addr[13], lladdr->addr + 3, 3); |
| |
| if (lladdr->type == NET_LINK_ETHERNET) { |
| tmp_addr.s6_addr[8] ^= 0x02; |
| } |
| |
| break; |
| case 8: |
| memcpy(&tmp_addr.s6_addr[8], lladdr->addr, lladdr->len); |
| tmp_addr.s6_addr[8] ^= 0x02; |
| break; |
| } |
| } |
| |
| NET_DBG("%s IID for iface %d %s", |
| IS_ENABLED(CONFIG_NET_IPV6_IID_STABLE) ? "Stable" : "EUI-64", |
| if_index, net_sprint_ipv6_addr(&tmp_addr)); |
| |
| memcpy(addr, &tmp_addr, sizeof(*addr)); |
| return 0; |
| } |
| |
| void net_ipv6_init(void) |
| { |
| net_ipv6_nbr_init(); |
| |
| #if defined(CONFIG_NET_IPV6_MLD) |
| net_ipv6_mld_init(); |
| #endif |
| } |