/** @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 LOG_MODULE_REGISTER(net_ipv6, CONFIG_NET_IPV6_LOG_LEVEL); #include #include #if defined(CONFIG_NET_IPV6_IID_STABLE) #include #include #endif /* CONFIG_NET_IPV6_IID_STABLE */ #include #include #include #include #include #include #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 */ 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) { 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; } 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); 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. */ 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)) { 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) { 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; } break; case IPPROTO_UDP: proto_hdr.udp = net_udp_input(pkt, &udp_access); if (proto_hdr.udp) { verdict = NET_OK; } 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) { goto drop; } else if (current_hdr == IPPROTO_ICMPV6) { return verdict; } ip.ipv6 = hdr; verdict = net_conn_input(pkt, &ip, current_hdr, &proto_hdr); 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 }