|  | /** @file | 
|  | * @brief IPv6 Fragment related functions | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * Copyright (c) 2018 Intel Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_DECLARE(net_ipv6, CONFIG_NET_IPV6_LOG_LEVEL); | 
|  |  | 
|  | #include <errno.h> | 
|  | #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/random/random.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 for various buffer allocations in this file. */ | 
|  | #define NET_BUF_TIMEOUT K_MSEC(50) | 
|  |  | 
|  | #if defined(CONFIG_NET_IPV6_FRAGMENT_TIMEOUT) | 
|  | #define IPV6_REASSEMBLY_TIMEOUT K_SECONDS(CONFIG_NET_IPV6_FRAGMENT_TIMEOUT) | 
|  | #else | 
|  | #define IPV6_REASSEMBLY_TIMEOUT K_SECONDS(5) | 
|  | #endif /* CONFIG_NET_IPV6_FRAGMENT_TIMEOUT */ | 
|  |  | 
|  | #define FRAG_BUF_WAIT K_MSEC(10) /* how long to max wait for a buffer */ | 
|  |  | 
|  | static void reassembly_timeout(struct k_work *work); | 
|  | static bool reassembly_init_done; | 
|  |  | 
|  | static struct net_ipv6_reassembly | 
|  | reassembly[CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT]; | 
|  |  | 
|  | int net_ipv6_find_last_ext_hdr(struct net_pkt *pkt, uint16_t *next_hdr_off, | 
|  | uint16_t *last_hdr_off) | 
|  | { | 
|  | NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv6_access, struct net_ipv6_hdr); | 
|  | struct net_ipv6_hdr *hdr; | 
|  | uint8_t next_nexthdr; | 
|  | uint8_t nexthdr; | 
|  |  | 
|  | if (!pkt || !pkt->frags || !next_hdr_off || !last_hdr_off) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | net_pkt_cursor_init(pkt); | 
|  |  | 
|  | hdr = (struct net_ipv6_hdr *)net_pkt_get_data(pkt, &ipv6_access); | 
|  | if (!hdr) { | 
|  | return -ENOBUFS; | 
|  | } | 
|  |  | 
|  | net_pkt_acknowledge_data(pkt, &ipv6_access); | 
|  |  | 
|  | nexthdr = hdr->nexthdr; | 
|  |  | 
|  | /* Initial values */ | 
|  | *next_hdr_off = offsetof(struct net_ipv6_hdr, nexthdr); | 
|  | *last_hdr_off = sizeof(struct net_ipv6_hdr); | 
|  |  | 
|  | while (!net_ipv6_is_nexthdr_upper_layer(nexthdr)) { | 
|  | if (net_pkt_read_u8(pkt, &next_nexthdr)) { | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | switch (nexthdr) { | 
|  | case NET_IPV6_NEXTHDR_HBHO: | 
|  | case NET_IPV6_NEXTHDR_DESTO: | 
|  | { | 
|  | uint8_t val = 0U; | 
|  | uint16_t length; | 
|  |  | 
|  | if (net_pkt_read_u8(pkt, &val)) { | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | length = val * 8U + 8 - 2; | 
|  |  | 
|  | if (net_pkt_skip(pkt, length)) { | 
|  | goto fail; | 
|  | } | 
|  | } | 
|  | break; | 
|  | case NET_IPV6_NEXTHDR_FRAG: | 
|  | if (net_pkt_skip(pkt, 7)) { | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | break; | 
|  | case NET_IPV6_NEXTHDR_NONE: | 
|  | goto out; | 
|  | default: | 
|  | /* TODO: Add more IPv6 extension headers to check */ | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | *next_hdr_off = *last_hdr_off; | 
|  | *last_hdr_off = net_pkt_get_current_offset(pkt); | 
|  |  | 
|  | nexthdr = next_nexthdr; | 
|  | } | 
|  | out: | 
|  | return 0; | 
|  | fail: | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static struct net_ipv6_reassembly *reassembly_get(uint32_t id, | 
|  | struct in6_addr *src, | 
|  | struct in6_addr *dst) | 
|  | { | 
|  | int i, avail = -1; | 
|  |  | 
|  | for (i = 0; i < CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT; i++) { | 
|  | if (k_work_delayable_remaining_get(&reassembly[i].timer) && | 
|  | reassembly[i].id == id && | 
|  | net_ipv6_addr_cmp(src, &reassembly[i].src) && | 
|  | net_ipv6_addr_cmp(dst, &reassembly[i].dst)) { | 
|  | return &reassembly[i]; | 
|  | } | 
|  |  | 
|  | if (k_work_delayable_remaining_get(&reassembly[i].timer)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (avail < 0) { | 
|  | avail = i; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (avail < 0) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | k_work_reschedule(&reassembly[avail].timer, IPV6_REASSEMBLY_TIMEOUT); | 
|  |  | 
|  | net_ipaddr_copy(&reassembly[avail].src, src); | 
|  | net_ipaddr_copy(&reassembly[avail].dst, dst); | 
|  |  | 
|  | reassembly[avail].id = id; | 
|  |  | 
|  | return &reassembly[avail]; | 
|  | } | 
|  |  | 
|  | static bool reassembly_cancel(uint32_t id, | 
|  | struct in6_addr *src, | 
|  | struct in6_addr *dst) | 
|  | { | 
|  | int i, j; | 
|  |  | 
|  | NET_DBG("Cancel 0x%x", id); | 
|  |  | 
|  | for (i = 0; i < CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT; i++) { | 
|  | int32_t remaining; | 
|  |  | 
|  | if (reassembly[i].id != id || | 
|  | !net_ipv6_addr_cmp(src, &reassembly[i].src) || | 
|  | !net_ipv6_addr_cmp(dst, &reassembly[i].dst)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | remaining = k_ticks_to_ms_ceil32( | 
|  | k_work_delayable_remaining_get(&reassembly[i].timer)); | 
|  | k_work_cancel_delayable(&reassembly[i].timer); | 
|  |  | 
|  | NET_DBG("IPv6 reassembly id 0x%x remaining %d ms", | 
|  | reassembly[i].id, remaining); | 
|  |  | 
|  | reassembly[i].id = 0U; | 
|  |  | 
|  | for (j = 0; j < CONFIG_NET_IPV6_FRAGMENT_MAX_PKT; j++) { | 
|  | if (!reassembly[i].pkt[j]) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | NET_DBG("[%d] IPv6 reassembly pkt %p %zd bytes data", | 
|  | j, reassembly[i].pkt[j], | 
|  | net_pkt_get_len(reassembly[i].pkt[j])); | 
|  |  | 
|  | net_pkt_unref(reassembly[i].pkt[j]); | 
|  | reassembly[i].pkt[j] = NULL; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static void reassembly_info(char *str, struct net_ipv6_reassembly *reass) | 
|  | { | 
|  | NET_DBG("%s id 0x%x src %s dst %s remain %d ms", str, reass->id, | 
|  | net_sprint_ipv6_addr(&reass->src), | 
|  | net_sprint_ipv6_addr(&reass->dst), | 
|  | k_ticks_to_ms_ceil32( | 
|  | k_work_delayable_remaining_get(&reass->timer))); | 
|  | } | 
|  |  | 
|  | static void reassembly_timeout(struct k_work *work) | 
|  | { | 
|  | struct k_work_delayable *dwork = k_work_delayable_from_work(work); | 
|  | struct net_ipv6_reassembly *reass = | 
|  | CONTAINER_OF(dwork, struct net_ipv6_reassembly, timer); | 
|  |  | 
|  | reassembly_info("Reassembly cancelled", reass); | 
|  |  | 
|  | /* Send a ICMPv6 Time Exceeded only if we received the first fragment (RFC 2460 Sec. 5) */ | 
|  | if (reass->pkt[0] && net_pkt_ipv6_fragment_offset(reass->pkt[0]) == 0) { | 
|  | net_icmpv6_send_error(reass->pkt[0], NET_ICMPV6_TIME_EXCEEDED, 1, 0); | 
|  | } | 
|  |  | 
|  | reassembly_cancel(reass->id, &reass->src, &reass->dst); | 
|  | } | 
|  |  | 
|  | static void reassemble_packet(struct net_ipv6_reassembly *reass) | 
|  | { | 
|  | NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv6_access, struct net_ipv6_hdr); | 
|  | NET_PKT_DATA_ACCESS_DEFINE(frag_access, struct net_ipv6_frag_hdr); | 
|  | union { | 
|  | struct net_ipv6_hdr *hdr; | 
|  | struct net_ipv6_frag_hdr *frag_hdr; | 
|  | } ipv6; | 
|  |  | 
|  | struct net_pkt *pkt; | 
|  | struct net_buf *last; | 
|  | uint8_t next_hdr; | 
|  | int i, len; | 
|  |  | 
|  | k_work_cancel_delayable(&reass->timer); | 
|  |  | 
|  | NET_ASSERT(reass->pkt[0]); | 
|  |  | 
|  | last = net_buf_frag_last(reass->pkt[0]->buffer); | 
|  |  | 
|  | /* We start from 2nd packet which is then appended to | 
|  | * the first one. | 
|  | */ | 
|  | for (i = 1; i < CONFIG_NET_IPV6_FRAGMENT_MAX_PKT; i++) { | 
|  | int removed_len; | 
|  |  | 
|  | pkt = reass->pkt[i]; | 
|  | if (!pkt) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | net_pkt_cursor_init(pkt); | 
|  |  | 
|  | /* Get rid of IPv6 and fragment header which are at | 
|  | * the beginning of the fragment. | 
|  | */ | 
|  | removed_len = net_pkt_ipv6_fragment_start(pkt) + | 
|  | sizeof(struct net_ipv6_frag_hdr); | 
|  |  | 
|  | NET_DBG("Removing %d bytes from start of pkt %p", | 
|  | removed_len, pkt->buffer); | 
|  |  | 
|  | if (net_pkt_pull(pkt, removed_len)) { | 
|  | NET_ERR("Failed to pull headers"); | 
|  | reassembly_cancel(reass->id, &reass->src, &reass->dst); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Attach the data to previous pkt */ | 
|  | last->frags = pkt->buffer; | 
|  | last = net_buf_frag_last(pkt->buffer); | 
|  |  | 
|  | pkt->buffer = NULL; | 
|  | reass->pkt[i] = NULL; | 
|  |  | 
|  | net_pkt_unref(pkt); | 
|  | } | 
|  |  | 
|  | pkt = reass->pkt[0]; | 
|  | reass->pkt[0] = NULL; | 
|  |  | 
|  | /* Next we need to strip away the fragment header from the first packet | 
|  | * and set the various pointers and values in packet. | 
|  | */ | 
|  | net_pkt_cursor_init(pkt); | 
|  |  | 
|  | if (net_pkt_skip(pkt, net_pkt_ipv6_fragment_start(pkt))) { | 
|  | NET_ERR("Failed to move to fragment header"); | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | ipv6.frag_hdr = (struct net_ipv6_frag_hdr *)net_pkt_get_data( | 
|  | pkt, &frag_access); | 
|  | if (!ipv6.frag_hdr) { | 
|  | NET_ERR("Failed to get fragment header"); | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | next_hdr = ipv6.frag_hdr->nexthdr; | 
|  |  | 
|  | if (net_pkt_pull(pkt, sizeof(struct net_ipv6_frag_hdr))) { | 
|  | NET_ERR("Failed to remove fragment header"); | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | /* This one updates the previous header's nexthdr value */ | 
|  | if (net_pkt_skip(pkt, net_pkt_ipv6_hdr_prev(pkt)) || | 
|  | net_pkt_write_u8(pkt, next_hdr)) { | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | net_pkt_cursor_init(pkt); | 
|  |  | 
|  | ipv6.hdr = (struct net_ipv6_hdr *)net_pkt_get_data(pkt, &ipv6_access); | 
|  | if (!ipv6.hdr) { | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | /* Fix the total length of the IPv6 packet. */ | 
|  | len = net_pkt_ipv6_ext_len(pkt); | 
|  | if (len > 0) { | 
|  | NET_DBG("Old pkt %p IPv6 ext len is %d bytes", pkt, len); | 
|  | net_pkt_set_ipv6_ext_len(pkt, | 
|  | len - sizeof(struct net_ipv6_frag_hdr)); | 
|  | } | 
|  |  | 
|  | len = net_pkt_get_len(pkt) - sizeof(struct net_ipv6_hdr); | 
|  |  | 
|  | ipv6.hdr->len = htons(len); | 
|  |  | 
|  | net_pkt_set_data(pkt, &ipv6_access); | 
|  | net_pkt_set_ip_reassembled(pkt, true); | 
|  |  | 
|  | NET_DBG("New pkt %p IPv6 len is %d bytes", pkt, | 
|  | len + NET_IPV6H_LEN); | 
|  |  | 
|  | /* We need to use the queue when feeding the packet back into the | 
|  | * IP stack as we might run out of stack if we call processing_data() | 
|  | * directly. As the packet does not contain link layer header, we | 
|  | * MUST NOT pass it to L2 so there will be a special check for that | 
|  | * in process_data() when handling the packet. | 
|  | */ | 
|  | if (net_recv_data(net_pkt_iface(pkt), pkt) >= 0) { | 
|  | return; | 
|  | } | 
|  | error: | 
|  | net_pkt_unref(pkt); | 
|  | } | 
|  |  | 
|  | void net_ipv6_frag_foreach(net_ipv6_frag_cb_t cb, void *user_data) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for (i = 0; reassembly_init_done && | 
|  | i < CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT; i++) { | 
|  | if (!k_work_delayable_remaining_get(&reassembly[i].timer)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | cb(&reassembly[i], user_data); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Verify that we have all the fragments received and in correct order. | 
|  | * Return: | 
|  | * - a negative value if the fragments are erroneous and must be dropped | 
|  | * - zero if we are expecting more fragments | 
|  | * - a positive value if we can proceed with the reassembly | 
|  | */ | 
|  | static int fragments_are_ready(struct net_ipv6_reassembly *reass) | 
|  | { | 
|  | unsigned int expected_offset = 0; | 
|  | bool more = true; | 
|  | int i; | 
|  |  | 
|  | /* Fragments can arrive in any order, for example in reverse order: | 
|  | *   1 -> Fragment3(M=0, offset=x2) | 
|  | *   2 -> Fragment2(M=1, offset=x1) | 
|  | *   3 -> Fragment1(M=1, offset=0) | 
|  | * We have to test several requirements before proceeding with the reassembly: | 
|  | * - We received the first fragment (Fragment Offset is 0) | 
|  | * - All intermediate fragments are contiguous | 
|  | * - The More bit of the last fragment is 0 | 
|  | */ | 
|  | for (i = 0; i < CONFIG_NET_IPV6_FRAGMENT_MAX_PKT; i++) { | 
|  | struct net_pkt *pkt = reass->pkt[i]; | 
|  | unsigned int offset; | 
|  | int payload_len; | 
|  |  | 
|  | if (!pkt) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | offset = net_pkt_ipv6_fragment_offset(pkt); | 
|  |  | 
|  | if (offset < expected_offset) { | 
|  | /* Overlapping or duplicated | 
|  | * According to RFC8200 we can drop it | 
|  | */ | 
|  | return -EBADMSG; | 
|  | } else if (offset != expected_offset) { | 
|  | /* Not contiguous, let's wait for fragments */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | payload_len = net_pkt_get_len(pkt) - net_pkt_ipv6_fragment_start(pkt); | 
|  | payload_len -= sizeof(struct net_ipv6_frag_hdr); | 
|  | if (payload_len < 0) { | 
|  | return -EBADMSG; | 
|  | } | 
|  |  | 
|  | expected_offset += payload_len; | 
|  | more = net_pkt_ipv6_fragment_more(pkt); | 
|  | } | 
|  |  | 
|  | if (more) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static int shift_packets(struct net_ipv6_reassembly *reass, int pos) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for (i = pos + 1; i < CONFIG_NET_IPV6_FRAGMENT_MAX_PKT; i++) { | 
|  | if (!reass->pkt[i]) { | 
|  | NET_DBG("Moving [%d] %p (offset 0x%x) to [%d]", | 
|  | pos, reass->pkt[pos], | 
|  | net_pkt_ipv6_fragment_offset(reass->pkt[pos]), | 
|  | pos + 1); | 
|  |  | 
|  | /* pkt[i] is free, so shift everything between | 
|  | * [pos] and [i - 1] by one element | 
|  | */ | 
|  | memmove(&reass->pkt[pos + 1], &reass->pkt[pos], | 
|  | sizeof(void *) * (i - pos)); | 
|  |  | 
|  | /* pkt[pos] is now free */ | 
|  | reass->pkt[pos] = NULL; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* We do not have free space left in the array */ | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | enum net_verdict net_ipv6_handle_fragment_hdr(struct net_pkt *pkt, | 
|  | struct net_ipv6_hdr *hdr, | 
|  | uint8_t nexthdr) | 
|  | { | 
|  | struct net_ipv6_reassembly *reass = NULL; | 
|  | uint16_t flag; | 
|  | bool found; | 
|  | uint8_t more; | 
|  | uint32_t id; | 
|  | int ret; | 
|  | int i; | 
|  |  | 
|  | if (!reassembly_init_done) { | 
|  | /* Static initializing does not work here because of the array | 
|  | * so we must do it at runtime. | 
|  | */ | 
|  | for (i = 0; i < CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT; i++) { | 
|  | k_work_init_delayable(&reassembly[i].timer, | 
|  | reassembly_timeout); | 
|  | } | 
|  |  | 
|  | reassembly_init_done = true; | 
|  | } | 
|  |  | 
|  | /* Each fragment has a fragment header, however since we already | 
|  | * read the nexthdr part of it, we are not going to use | 
|  | * net_pkt_get_data() and access the header directly: the cursor | 
|  | * being 1 byte too far, let's just read the next relevant pieces. | 
|  | */ | 
|  | if (net_pkt_skip(pkt, 1) || /* reserved */ | 
|  | net_pkt_read_be16(pkt, &flag) || | 
|  | net_pkt_read_be32(pkt, &id)) { | 
|  | goto drop; | 
|  | } | 
|  |  | 
|  | reass = reassembly_get(id, (struct in6_addr *)hdr->src, | 
|  | (struct in6_addr *)hdr->dst); | 
|  | if (!reass) { | 
|  | NET_DBG("Cannot get reassembly slot, dropping pkt %p", pkt); | 
|  | goto drop; | 
|  | } | 
|  |  | 
|  | more = flag & 0x01; | 
|  | net_pkt_set_ipv6_fragment_flags(pkt, flag); | 
|  |  | 
|  | if (more && net_pkt_get_len(pkt) % 8) { | 
|  | /* Fragment length is not multiple of 8, discard | 
|  | * the packet and send parameter problem error with the | 
|  | * offset of the "Payload Length" field in the IPv6 header. | 
|  | */ | 
|  | net_icmpv6_send_error(pkt, NET_ICMPV6_PARAM_PROBLEM, | 
|  | NET_ICMPV6_PARAM_PROB_HEADER, NET_IPV6H_LENGTH_OFFSET); | 
|  | goto drop; | 
|  | } | 
|  |  | 
|  | /* The fragments might come in wrong order so place them | 
|  | * in reassembly chain in correct order. | 
|  | */ | 
|  | for (i = 0, found = false; i < CONFIG_NET_IPV6_FRAGMENT_MAX_PKT; i++) { | 
|  | if (reass->pkt[i]) { | 
|  | if (net_pkt_ipv6_fragment_offset(reass->pkt[i]) < | 
|  | net_pkt_ipv6_fragment_offset(pkt)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* Make room for this fragment. If there is no room, | 
|  | * then it will discard the whole reassembly. | 
|  | */ | 
|  | if (shift_packets(reass, i)) { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | NET_DBG("Storing pkt %p to slot %d offset %d", | 
|  | pkt, i, net_pkt_ipv6_fragment_offset(pkt)); | 
|  | reass->pkt[i] = pkt; | 
|  | found = true; | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (!found) { | 
|  | /* We could not add this fragment into our saved fragment | 
|  | * list. We must discard the whole packet at this point. | 
|  | */ | 
|  | NET_DBG("No slots available for 0x%x", reass->id); | 
|  | net_pkt_unref(pkt); | 
|  | goto drop; | 
|  | } | 
|  |  | 
|  | ret = fragments_are_ready(reass); | 
|  | if (ret < 0) { | 
|  | NET_DBG("Reassembled IPv6 verify failed, dropping id %u", | 
|  | reass->id); | 
|  |  | 
|  | /* Let the caller release the already inserted pkt */ | 
|  | if (i < CONFIG_NET_IPV6_FRAGMENT_MAX_PKT) { | 
|  | reass->pkt[i] = NULL; | 
|  | } | 
|  |  | 
|  | net_pkt_unref(pkt); | 
|  | goto drop; | 
|  | } else if (ret == 0) { | 
|  | reassembly_info("Reassembly nth pkt", reass); | 
|  |  | 
|  | NET_DBG("More fragments to be received"); | 
|  | goto accept; | 
|  | } | 
|  |  | 
|  | reassembly_info("Reassembly last pkt", reass); | 
|  |  | 
|  | /* The last fragment received, reassemble the packet */ | 
|  | reassemble_packet(reass); | 
|  |  | 
|  | accept: | 
|  | return NET_OK; | 
|  |  | 
|  | drop: | 
|  | if (reass) { | 
|  | if (reassembly_cancel(reass->id, &reass->src, &reass->dst)) { | 
|  | return NET_OK; | 
|  | } | 
|  | } | 
|  |  | 
|  | return NET_DROP; | 
|  | } | 
|  |  | 
|  | #define BUF_ALLOC_TIMEOUT K_MSEC(100) | 
|  |  | 
|  | static int send_ipv6_fragment(struct net_pkt *pkt, | 
|  | uint16_t fit_len, | 
|  | uint16_t frag_offset, | 
|  | uint16_t next_hdr_off, | 
|  | uint8_t next_hdr, | 
|  | bool final) | 
|  | { | 
|  | NET_PKT_DATA_ACCESS_DEFINE(frag_access, struct net_ipv6_frag_hdr); | 
|  | uint8_t frag_pkt_next_hdr = NET_IPV6_NEXTHDR_HBHO; | 
|  | int ret = -ENOBUFS; | 
|  | struct net_ipv6_frag_hdr *frag_hdr; | 
|  | struct net_pkt *frag_pkt; | 
|  |  | 
|  | frag_pkt = net_pkt_alloc_with_buffer(net_pkt_iface(pkt), fit_len + | 
|  | net_pkt_ipv6_ext_len(pkt) + | 
|  | NET_IPV6_FRAGH_LEN, | 
|  | AF_INET6, 0, BUF_ALLOC_TIMEOUT); | 
|  | if (!frag_pkt) { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | net_pkt_cursor_init(pkt); | 
|  |  | 
|  | /* We copy original headers back to the fragment packet | 
|  | * Note that we insert the right next header to point to fragment header | 
|  | */ | 
|  | if (net_pkt_copy(frag_pkt, pkt, next_hdr_off) || | 
|  | net_pkt_write_u8(frag_pkt, NET_IPV6_NEXTHDR_FRAG) || | 
|  | net_pkt_skip(pkt, 1) || | 
|  | net_pkt_copy(frag_pkt, pkt, net_pkt_ip_hdr_len(pkt) + | 
|  | net_pkt_ipv6_ext_len(pkt) - next_hdr_off - 1)) { | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | if (!net_pkt_ipv6_ext_len(pkt)) { | 
|  | frag_pkt_next_hdr = NET_IPV6_NEXTHDR_FRAG; | 
|  | } | 
|  |  | 
|  | /* And we append the fragmentation header */ | 
|  | frag_hdr = (struct net_ipv6_frag_hdr *)net_pkt_get_data(frag_pkt, | 
|  | &frag_access); | 
|  | if (!frag_hdr) { | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | frag_hdr->nexthdr = next_hdr; | 
|  | frag_hdr->reserved = 0U; | 
|  | frag_hdr->id = net_pkt_ipv6_fragment_id(pkt); | 
|  | frag_hdr->offset = htons(((frag_offset / 8U) << 3) | !final); | 
|  |  | 
|  | net_pkt_set_chksum_done(frag_pkt, true); | 
|  |  | 
|  | if (net_pkt_set_data(frag_pkt, &frag_access)) { | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | net_pkt_set_ip_hdr_len(frag_pkt, net_pkt_ip_hdr_len(pkt)); | 
|  | net_pkt_set_ipv6_ext_len(frag_pkt, | 
|  | net_pkt_ipv6_ext_len(pkt) + | 
|  | sizeof(struct net_ipv6_frag_hdr)); | 
|  |  | 
|  | /* Finally we copy the payload part of this fragment from | 
|  | * the original packet | 
|  | */ | 
|  | if (net_pkt_skip(pkt, frag_offset) || | 
|  | net_pkt_copy(frag_pkt, pkt, fit_len)) { | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | net_pkt_cursor_init(frag_pkt); | 
|  |  | 
|  | if (net_ipv6_finalize(frag_pkt, frag_pkt_next_hdr) < 0) { | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | if (final) { | 
|  | net_pkt_set_context(frag_pkt, net_pkt_context(pkt)); | 
|  | } | 
|  |  | 
|  | /* If everything has been ok so far, we can send the packet. */ | 
|  | ret = net_send_data(frag_pkt); | 
|  | if (ret < 0) { | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | /* Let this packet to be sent and hopefully it will release | 
|  | * the memory that can be utilized for next sent IPv6 fragment. | 
|  | */ | 
|  | k_yield(); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | fail: | 
|  | NET_DBG("Cannot send fragment (%d)", ret); | 
|  | net_pkt_unref(frag_pkt); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int net_ipv6_send_fragmented_pkt(struct net_if *iface, struct net_pkt *pkt, | 
|  | uint16_t pkt_len) | 
|  | { | 
|  | uint16_t next_hdr_off; | 
|  | uint16_t last_hdr_off; | 
|  | uint16_t frag_offset; | 
|  | size_t length; | 
|  | uint8_t next_hdr; | 
|  | int fit_len; | 
|  | int ret; | 
|  |  | 
|  | net_pkt_set_ipv6_fragment_id(pkt, sys_rand32_get()); | 
|  |  | 
|  | ret = net_ipv6_find_last_ext_hdr(pkt, &next_hdr_off, &last_hdr_off); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | net_pkt_cursor_init(pkt); | 
|  |  | 
|  | if (net_pkt_skip(pkt, next_hdr_off) || | 
|  | net_pkt_read_u8(pkt, &next_hdr)) { | 
|  | return -ENOBUFS; | 
|  | } | 
|  |  | 
|  | /* The Maximum payload can fit into each packet after IPv6 header, | 
|  | * Extension headers and Fragmentation header. | 
|  | */ | 
|  | fit_len = NET_IPV6_MTU - NET_IPV6_FRAGH_LEN - | 
|  | (net_pkt_ip_hdr_len(pkt) + net_pkt_ipv6_ext_len(pkt)); | 
|  | if (fit_len <= 0) { | 
|  | /* Must be invalid extension headers length */ | 
|  | NET_DBG("No room for IPv6 payload MTU %d hdrs_len %d", | 
|  | NET_IPV6_MTU, NET_IPV6_FRAGH_LEN + | 
|  | net_pkt_ip_hdr_len(pkt) + net_pkt_ipv6_ext_len(pkt)); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | frag_offset = 0U; | 
|  |  | 
|  | /* Calculate the L4 checksum (if not done already) before the fragmentation. */ | 
|  | if (!net_pkt_is_chksum_done(pkt)) { | 
|  | net_pkt_cursor_init(pkt); | 
|  | net_pkt_skip(pkt, last_hdr_off); | 
|  |  | 
|  | switch (next_hdr) { | 
|  | case IPPROTO_ICMPV6: | 
|  | ret = net_icmpv6_finalize(pkt, true); | 
|  | break; | 
|  | case IPPROTO_TCP: | 
|  | ret = net_tcp_finalize(pkt, true); | 
|  | break; | 
|  | case IPPROTO_UDP: | 
|  | ret = net_udp_finalize(pkt, true); | 
|  | break; | 
|  | default: | 
|  | ret = 0; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  | } | 
|  |  | 
|  | length = net_pkt_get_len(pkt) - | 
|  | (net_pkt_ip_hdr_len(pkt) + net_pkt_ipv6_ext_len(pkt)); | 
|  | while (length) { | 
|  | bool final = false; | 
|  |  | 
|  | if (fit_len >= length) { | 
|  | final = true; | 
|  | fit_len = length; | 
|  | } | 
|  |  | 
|  | ret = send_ipv6_fragment(pkt, fit_len, frag_offset, | 
|  | next_hdr_off, next_hdr, final); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | length -= fit_len; | 
|  | frag_offset += fit_len; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } |