| /** @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 <logging/log.h> |
| LOG_MODULE_REGISTER(net_ipv6, CONFIG_NET_IPV6_LOG_LEVEL); |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <net/net_core.h> |
| #include <net/net_pkt.h> |
| #include <net/net_stats.h> |
| #include <net/net_context.h> |
| #include <net/net_mgmt.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" |
| |
| /* 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 |
| |
| /* IPv6 wildcard and loopback address defined by RFC2553 */ |
| const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; |
| const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; |
| |
| const struct in6_addr *net_ipv6_unspecified_address(void) |
| { |
| return &in6addr_any; |
| } |
| |
| 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; |
| |
| ipv6_hdr = (struct net_ipv6_hdr *)net_pkt_get_data(pkt, &ipv6_access); |
| if (!ipv6_hdr) { |
| return -ENOBUFS; |
| } |
| |
| ipv6_hdr->vtc = 0x60; |
| ipv6_hdr->tcflow = 0U; |
| ipv6_hdr->flow = 0U; |
| ipv6_hdr->len = 0U; |
| ipv6_hdr->nexthdr = 0U; |
| |
| /* User can tweak the default hop limit if needed */ |
| ipv6_hdr->hop_limit = net_pkt_ipv6_hop_limit(pkt); |
| if (ipv6_hdr->hop_limit == 0U) { |
| ipv6_hdr->hop_limit = |
| net_if_ipv6_get_hop_limit(net_pkt_iface(pkt)); |
| } |
| |
| net_ipaddr_copy(&ipv6_hdr->dst, dst); |
| net_ipaddr_copy(&ipv6_hdr->src, 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, u8_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); |
| } else if (IS_ENABLED(CONFIG_NET_TCP) && |
| next_header_proto == IPPROTO_TCP) { |
| return net_tcp_finalize(pkt); |
| } else if (next_header_proto == IPPROTO_ICMPV6) { |
| return net_icmpv6_finalize(pkt); |
| } |
| |
| return 0; |
| } |
| |
| static inline bool ipv6_drop_on_unknown_option(struct net_pkt *pkt, |
| struct net_ipv6_hdr *hdr, |
| u8_t opt_type, |
| u16_t length) |
| { |
| /* 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(&hdr->dst)) { |
| break; |
| } |
| |
| /* passthrough */ |
| case 0x80: |
| net_icmpv6_send_error(pkt, NET_ICMPV6_PARAM_PROBLEM, |
| NET_ICMPV6_PARAM_PROB_OPTION, |
| (u32_t)length); |
| break; |
| } |
| |
| return true; |
| } |
| |
| static inline int ipv6_handle_ext_hdr_options(struct net_pkt *pkt, |
| struct net_ipv6_hdr *hdr, |
| u16_t pkt_len) |
| { |
| u16_t exthdr_len = 0U; |
| u16_t length = 0U; |
| |
| if (net_pkt_read_u8(pkt, (u8_t *)&exthdr_len)) { |
| return -ENOBUFS; |
| } |
| |
| exthdr_len = exthdr_len * 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) { |
| u8_t opt_type, opt_len; |
| |
| /* Each extension option has type and 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: |
| length++; |
| break; |
| case NET_IPV6_EXT_HDR_OPT_PADN: |
| NET_DBG("PADN option"); |
| length += opt_len + 2; |
| |
| break; |
| default: |
| if (ipv6_drop_on_unknown_option(pkt, hdr, |
| opt_type, length)) { |
| 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, |
| u8_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_DBG("%s route to %s/%d iface %p", route ? "Add" : "Cannot add", |
| log_strdup(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, log_strdup(net_sprint_ipv6_addr(src)), |
| log_strdup(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, &hdr->dst, &route, |
| &nexthop); |
| } else { |
| found = net_route_get_info(net_pkt_iface(pkt), |
| &hdr->dst, &route, &nexthop); |
| } |
| |
| if (found) { |
| int ret; |
| |
| if (IS_ENABLED(CONFIG_NET_ROUTING) && |
| (net_ipv6_is_ll_addr(&hdr->src) || |
| net_ipv6_is_ll_addr(&hdr->dst))) { |
| /* RFC 4291 ch 2.5.6 */ |
| ipv6_no_route_info(pkt, &hdr->src, &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)) { |
| /* 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), &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, log_strdup(net_sprint_ipv6_addr(nexthop)), |
| net_pkt_iface(pkt), ret); |
| } else { |
| return NET_OK; |
| } |
| } else { |
| NET_DBG("No route to %s pkt %p dropped", |
| log_strdup(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 */ |
| |
| 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); |
| enum net_verdict verdict = NET_DROP; |
| int real_len = net_pkt_get_len(pkt); |
| u8_t ext_bitmap = 0U; |
| u16_t ext_len = 0U; |
| u8_t nexthdr, next_nexthdr; |
| union net_proto_header proto_hdr; |
| struct net_ipv6_hdr *hdr; |
| union net_ip_header ip; |
| int pkt_len; |
| |
| net_stats_update_ipv6_recv(net_pkt_iface(pkt)); |
| |
| 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, |
| log_strdup(net_sprint_ipv6_addr(&hdr->src)), |
| log_strdup(net_sprint_ipv6_addr(&hdr->dst))); |
| |
| if (net_ipv6_is_addr_mcast(&hdr->src) || |
| net_ipv6_is_addr_mcast_scope(&hdr->dst, 0)) { |
| NET_DBG("DROP: multicast packet"); |
| goto drop; |
| } |
| |
| if (!is_loopback) { |
| if (net_ipv6_is_addr_loopback(&hdr->dst) || |
| net_ipv6_is_addr_loopback(&hdr->src)) { |
| NET_DBG("DROP: ::1 packet"); |
| goto drop; |
| } |
| |
| if (net_ipv6_is_addr_mcast_iface(&hdr->dst) || |
| (net_ipv6_is_addr_mcast_group( |
| &hdr->dst, net_ipv6_unspecified_address()) && |
| (net_ipv6_is_addr_mcast_site(&hdr->dst) || |
| net_ipv6_is_addr_mcast_org(&hdr->dst)))) { |
| NET_DBG("DROP: invalid scope multicast packet"); |
| goto drop; |
| } |
| } |
| |
| /* 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); |
| |
| if (!net_ipv6_is_my_addr(&hdr->dst) && |
| !net_ipv6_is_my_maddr(&hdr->dst) && |
| !net_ipv6_is_addr_mcast(&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(&hdr->src) && |
| !net_ipv6_is_addr_mcast(&hdr->dst) && |
| !net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt), |
| &hdr->dst)) { |
| ipv6_no_route_info(pkt, &hdr->src, &hdr->dst); |
| goto drop; |
| } |
| |
| net_pkt_acknowledge_data(pkt, &ipv6_access); |
| |
| nexthdr = hdr->nexthdr; |
| while (!net_ipv6_is_nexthdr_upper_layer(nexthdr)) { |
| int exthdr_len; |
| |
| NET_DBG("IPv6 next header %d", nexthdr); |
| |
| if (net_pkt_read_u8(pkt, &next_nexthdr)) { |
| goto drop; |
| } |
| |
| switch (nexthdr) { |
| case NET_IPV6_NEXTHDR_HBHO: |
| if (ext_bitmap & NET_IPV6_EXT_HDR_BITMAP_HBHO) { |
| NET_ERR("DROP: multiple hop-by-hop"); |
| goto drop; |
| } |
| |
| /* HBH option needs to be the first one */ |
| if (nexthdr != hdr->nexthdr) { |
| goto bad_hdr; |
| } |
| |
| ext_bitmap |= NET_IPV6_EXT_HDR_BITMAP_HBHO; |
| |
| break; |
| |
| case NET_IPV6_NEXTHDR_DESTO: |
| if (ext_bitmap & NET_IPV6_EXT_HDR_BITMAP_DESTO2) { |
| /* DESTO option cannot appear more than twice */ |
| goto bad_hdr; |
| } |
| |
| if (ext_bitmap & NET_IPV6_EXT_HDR_BITMAP_DESTO1) { |
| ext_bitmap |= NET_IPV6_EXT_HDR_BITMAP_DESTO2; |
| } else { |
| ext_bitmap |= NET_IPV6_EXT_HDR_BITMAP_DESTO1; |
| } |
| |
| 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)); |
| return net_ipv6_handle_fragment_hdr(pkt, hdr, |
| nexthdr); |
| } |
| |
| goto bad_hdr; |
| |
| case 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; |
| |
| default: |
| goto bad_hdr; |
| } |
| |
| exthdr_len = ipv6_handle_ext_hdr_options(pkt, hdr, pkt_len); |
| if (exthdr_len < 0) { |
| goto drop; |
| } |
| |
| ext_len += exthdr_len; |
| nexthdr = next_nexthdr; |
| } |
| |
| net_pkt_set_ipv6_ext_len(pkt, ext_len); |
| net_pkt_set_family(pkt, PF_INET6); |
| |
| switch (nexthdr) { |
| 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 (verdict == NET_DROP) { |
| goto drop; |
| } else if (nexthdr == IPPROTO_ICMPV6) { |
| return verdict; |
| } |
| |
| ip.ipv6 = hdr; |
| |
| verdict = net_conn_input(pkt, &ip, nexthdr, &proto_hdr); |
| if (verdict != NET_DROP) { |
| return verdict; |
| } |
| |
| drop: |
| net_stats_update_ipv6_drop(net_pkt_iface(pkt)); |
| 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(net_pkt_iface(pkt)); |
| |
| return NET_DROP; |
| } |
| |
| void net_ipv6_init(void) |
| { |
| net_ipv6_nbr_init(); |
| |
| #if defined(CONFIG_NET_IPV6_MLD) |
| net_ipv6_mld_init(); |
| #endif |
| } |