blob: 0d4214614dc9ffb71240c2c2d0f6257f1e69d92a [file] [log] [blame]
/** @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
}