1020 lines
27 KiB
C
1020 lines
27 KiB
C
/** @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 */
|
|
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
|
|
}
|