/** @file * @brief IPv6 Fragment related functions */ /* * Copyright (c) 2018 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_DECLARE(net_ipv6, CONFIG_NET_IPV6_LOG_LEVEL); #include #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 "rpl.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, u16_t *next_hdr_idx, u16_t *last_hdr_idx) { struct net_buf *next_hdr_frag; struct net_buf *last_hdr_frag; struct net_buf *frag; u16_t pkt_offset; u16_t offset; u16_t length; u8_t next_hdr; u8_t next; if (!pkt || !pkt->frags || !next_hdr_idx || !last_hdr_idx) { return -EINVAL; } next = NET_IPV6_HDR(pkt)->nexthdr; /* Initial value if no extension fragments are found */ *next_hdr_idx = 6U; *last_hdr_idx = sizeof(struct net_ipv6_hdr); /* First check the simplest case where there is no extension headers * in the packet. There cannot be any extensions after the normal or * typical IP protocols */ if (next == IPPROTO_ICMPV6 || next == IPPROTO_UDP || next == IPPROTO_TCP || next == NET_IPV6_NEXTHDR_NONE) { return 0; } frag = pkt->frags; offset = *last_hdr_idx; *next_hdr_idx = *last_hdr_idx; next_hdr_frag = last_hdr_frag = frag; while (frag) { frag = net_frag_read_u8(frag, offset, &offset, &next_hdr); if (!frag) { goto fail; } switch (next) { case NET_IPV6_NEXTHDR_FRAG: frag = net_frag_skip(frag, offset, &offset, 7); if (!frag) { goto fail; } break; case NET_IPV6_NEXTHDR_HBHO: case NET_IPV6_NEXTHDR_DESTO: length = 0U; frag = net_frag_read_u8(frag, offset, &offset, (u8_t *)&length); if (!frag) { goto fail; } length = length * 8 + 8; frag = net_frag_skip(frag, offset, &offset, length - 2); if (!frag) { goto fail; } break; case NET_IPV6_NEXTHDR_NONE: case IPPROTO_ICMPV6: case IPPROTO_UDP: case IPPROTO_TCP: goto out; default: /* TODO: Add more IPv6 extension headers to check */ goto fail; } *next_hdr_idx = *last_hdr_idx; next_hdr_frag = last_hdr_frag; *last_hdr_idx = offset; last_hdr_frag = frag; next = next_hdr; } fail: return -EINVAL; out: /* Current next_hdr_idx offset is based on respective fragment, but we * need to calculate next_hdr_idx offset based on whole packet. */ pkt_offset = 0U; frag = pkt->frags; while (frag) { if (next_hdr_frag == frag) { *next_hdr_idx += pkt_offset; break; } pkt_offset += frag->len; frag = frag->frags; } /* Current last_hdr_idx offset is based on respective fragment, but we * need to calculate last_hdr_idx offset based on whole packet. */ pkt_offset = 0U; frag = pkt->frags; while (frag) { if (last_hdr_frag == frag) { *last_hdr_idx += pkt_offset; break; } pkt_offset += frag->len; frag = frag->frags; } return 0; } static struct net_ipv6_reassembly *reassembly_get(u32_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_delayed_work_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_delayed_work_remaining_get(&reassembly[i].timer)) { continue; } if (avail < 0) { avail = i; } } if (avail < 0) { return NULL; } k_delayed_work_submit(&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(u32_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++) { s32_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_delayed_work_remaining_get(&reassembly[i].timer); if (remaining) { k_delayed_work_cancel(&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 < NET_IPV6_FRAGMENTS_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) { int i, len; for (i = 0, len = 0; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) { if (reass->pkt[i]) { len += net_pkt_get_len(reass->pkt[i]); } } NET_DBG("%s id 0x%x src %s dst %s remain %d ms len %d", str, reass->id, log_strdup(net_sprint_ipv6_addr(&reass->src)), log_strdup(net_sprint_ipv6_addr(&reass->dst)), k_delayed_work_remaining_get(&reass->timer), len); } static void reassembly_timeout(struct k_work *work) { struct net_ipv6_reassembly *reass = CONTAINER_OF(work, struct net_ipv6_reassembly, timer); reassembly_info("Reassembly cancelled", reass); reassembly_cancel(reass->id, &reass->src, &reass->dst); } static void reassemble_packet(struct net_ipv6_reassembly *reass) { struct net_pkt *pkt; struct net_buf *last; struct net_buf *frag; u8_t next_hdr; int i, len, ret; u16_t pos; k_delayed_work_cancel(&reass->timer); NET_ASSERT(reass->pkt[0]); last = net_buf_frag_last(reass->pkt[0]->frags); /* We start from 2nd packet which is then appended to * the first one. */ for (i = 1; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) { int removed_len; int ret; pkt = reass->pkt[i]; /* 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->frags); ret = net_pkt_pull(pkt, 0, removed_len); if (ret) { NET_ERR("Failed to pull headers"); NET_ASSERT(ret != 0); } /* Attach the data to previous pkt */ last->frags = pkt->frags; last = net_buf_frag_last(pkt->frags); pkt->frags = 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. */ frag = net_frag_read_u8(pkt->frags, net_pkt_ipv6_fragment_start(pkt), &pos, &next_hdr); if (!frag && pos == 0xFFFF) { NET_ERR("Failed to read next header"); NET_ASSERT(frag); } ret = net_pkt_pull(pkt, net_pkt_ipv6_fragment_start(pkt), sizeof(struct net_ipv6_frag_hdr)); if (ret) { NET_ERR("Failed to pull fragmentation header"); NET_ASSERT(ret); } /* This one updates the previous header's nexthdr value */ if (!net_pkt_write_u8_timeout(pkt, pkt->frags, net_pkt_ipv6_hdr_prev(pkt), &pos, next_hdr, NET_BUF_TIMEOUT)) { net_pkt_unref(pkt); return; } if (!net_pkt_compact(pkt)) { NET_ERR("Cannot compact reassembly packet %p", pkt); net_pkt_unref(pkt); return; } /* 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); NET_IPV6_HDR(pkt)->len = htons(len); NET_DBG("New pkt %p IPv6 len is %d bytes", pkt, 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. */ ret = net_recv_data(net_pkt_iface(pkt), pkt); if (ret < 0) { 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_delayed_work_remaining_get(&reassembly[i].timer)) { continue; } cb(&reassembly[i], user_data); } } /* Verify that we have all the fragments received and in correct order. */ static bool fragment_verify(struct net_ipv6_reassembly *reass) { u16_t offset; int i, prev_len; prev_len = net_pkt_get_len(reass->pkt[0]); offset = net_pkt_ipv6_fragment_offset(reass->pkt[0]); NET_DBG("pkt %p offset %u", reass->pkt[0], offset); if (offset != 0) { return false; } for (i = 1; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) { offset = net_pkt_ipv6_fragment_offset(reass->pkt[i]); NET_DBG("pkt %p offset %u prev_len %d", reass->pkt[i], offset, prev_len); if (prev_len < offset) { /* Something wrong with the offset value */ return false; } prev_len = net_pkt_get_len(reass->pkt[i]); } return true; } static int shift_packets(struct net_ipv6_reassembly *reass, int pos) { int i; for (i = pos + 1; i < NET_IPV6_FRAGMENTS_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]), i); /* Do we have enough space in packet array to make * the move? */ if (((i - pos) + 1) > (NET_IPV6_FRAGMENTS_MAX_PKT - i)) { return -ENOMEM; } memmove(&reass->pkt[i], &reass->pkt[pos], sizeof(void *) * (i - pos)); return 0; } } return -EINVAL; } enum net_verdict net_ipv6_handle_fragment_hdr(struct net_pkt *pkt, struct net_buf *frag, int total_len, u16_t buf_offset, u16_t *loc, u8_t nexthdr) { struct net_ipv6_reassembly *reass = NULL; u32_t id; u16_t offset; u16_t flag; u8_t more; bool found; 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_delayed_work_init(&reassembly[i].timer, reassembly_timeout); } reassembly_init_done = true; } /* Each fragment has a fragment header. */ frag = net_frag_skip(frag, buf_offset, loc, 1); /* reserved */ frag = net_frag_read_be16(frag, *loc, loc, &flag); frag = net_frag_read_be32(frag, *loc, loc, &id); if (!frag && *loc == 0xffff) { goto drop; } reass = reassembly_get(id, &NET_IPV6_HDR(pkt)->src, &NET_IPV6_HDR(pkt)->dst); if (!reass) { NET_DBG("Cannot get reassembly slot, dropping pkt %p", pkt); goto drop; } offset = flag & 0xfff8; more = flag & 0x01; net_pkt_set_ipv6_fragment_offset(pkt, offset); if (!reass->pkt[0]) { NET_DBG("Storing pkt %p to slot %d offset 0x%x", pkt, 0, offset); reass->pkt[0] = pkt; reassembly_info("Reassembly 1st pkt", reass); /* Wait for more fragments to receive. */ goto accept; } /* The fragments might come in wrong order so place them * in reassembly chain in correct order. */ for (i = 0, found = false; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) { if (!reass->pkt[i]) { NET_DBG("Storing pkt %p to slot %d offset 0x%x", pkt, i, offset); reass->pkt[i] = pkt; found = true; break; } if (net_pkt_ipv6_fragment_offset(reass->pkt[i]) < offset) { continue; } /* Make room for this fragment, if there is no room, then * discard the whole reassembly. */ if (shift_packets(reass, i)) { break; } NET_DBG("Storing %p (offset 0x%x) to [%d]", pkt, offset, i); 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; } if (more) { if (net_pkt_get_len(pkt) % 8) { /* Fragment length is not multiple of 8, discard * the packet and send parameter problem error. */ net_icmpv6_send_error(pkt, NET_ICMPV6_PARAM_PROBLEM, NET_ICMPV6_PARAM_PROB_OPTION, 0); goto drop; } reassembly_info("Reassembly nth pkt", reass); NET_DBG("More fragments to be received"); goto accept; } reassembly_info("Reassembly last pkt", reass); if (!fragment_verify(reass)) { NET_DBG("Reassembled IPv6 verify failed, dropping id %u", reass->id); /* Let the caller release the already inserted pkt */ if (i < NET_IPV6_FRAGMENTS_MAX_PKT) { reass->pkt[i] = NULL; } net_pkt_unref(pkt); goto drop; } /* 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_if *iface, struct net_pkt *pkt, struct net_buf **rest, u16_t ipv6_hdrs_len, u16_t fit_len, u16_t frag_offset, u8_t next_hdr, u16_t next_hdr_idx, u8_t last_hdr, u16_t last_hdr_idx, u16_t frag_count) { struct net_pkt *ipv6 = NULL; bool final; struct net_ipv6_frag_hdr hdr; struct net_buf *frag; struct net_buf *temp; u16_t pos; bool res; int ret; ipv6 = net_pkt_clone(pkt, BUF_ALLOC_TIMEOUT); if (!ipv6) { NET_DBG("Cannot clone %p", ipv6); return -ENOMEM; } /* And we need to update the last header in the IPv6 packet to point to * fragment header. */ temp = net_pkt_write_u8_timeout(ipv6, ipv6->frags, next_hdr_idx, &pos, NET_IPV6_NEXTHDR_FRAG, BUF_ALLOC_TIMEOUT); if (!temp) { if (pos == 0xffff) { ret = -EINVAL; } else { ret = -ENOMEM; } goto fail; } /* Update the extension length metadata so that upper layer checksum * will be calculated properly by net_ipv6_finalize(). */ net_pkt_set_ipv6_ext_len(ipv6, net_pkt_ipv6_ext_len(pkt) + sizeof(struct net_ipv6_frag_hdr)); frag = *rest; if (fit_len < net_buf_frags_len(*rest)) { ret = net_pkt_split(pkt, frag, fit_len, rest, FRAG_BUF_WAIT); if (ret < 0) { net_buf_unref(frag); goto fail; } } else { *rest = NULL; } final = false; /* *rest == NULL means no more data to send */ if (!*rest) { final = true; } /* Append the Fragmentation Header */ hdr.nexthdr = next_hdr; hdr.reserved = 0; hdr.id = net_pkt_ipv6_fragment_id(pkt); hdr.offset = htons(((frag_offset / 8) << 3) | !final); res = net_pkt_append_all(ipv6, sizeof(struct net_ipv6_frag_hdr), (u8_t *)&hdr, FRAG_BUF_WAIT); if (!res) { net_buf_unref(frag); ret = EINVAL; goto fail; } /* Attach the first part of split payload to end of the packet. And * "rest" of the packet will be sent in next iteration. */ temp = ipv6->frags; while (1) { if (!temp->frags) { temp->frags = frag; break; } temp = temp->frags; } res = net_pkt_compact(ipv6); if (!res) { ret = -EINVAL; goto fail; } /* Note that we must not calculate possible UDP/TCP/ICMPv6 checksum * as that is already calculated in the non-fragmented packet. */ ret = net_ipv6_finalize(ipv6, NET_IPV6_NEXTHDR_FRAG); if (ret < 0) { NET_DBG("Cannot create IPv6 packet (%d)", ret); goto fail; } /* If everything has been ok so far, we can send the packet. * Note that we cannot send this re-constructed packet directly * as the link layer headers will not be properly set (because * we recreated the packet). So pass this packet back to TX * so that the pkt is going back to L2 for setup. */ ret = net_send_data(ipv6); if (ret < 0) { NET_DBG("Cannot send fragment (%d)", ret); 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: if (ipv6) { net_pkt_unref(ipv6); } return ret; } int net_ipv6_send_fragmented_pkt(struct net_if *iface, struct net_pkt *pkt, u16_t pkt_len) { struct net_buf *rest = NULL; struct net_pkt *clone; struct net_buf *temp; u16_t next_hdr_idx; u16_t last_hdr_idx; u16_t ipv6_hdrs_len; u16_t frag_offset; u16_t frag_count; u16_t pos; u8_t next_hdr; u8_t last_hdr; int fit_len; int ret = -EINVAL; /* We cannot touch original pkt because it might be used for * some other purposes, like TCP resend etc. So we need to copy * the large pkt here and do the fragmenting with the clone. */ clone = net_pkt_clone(pkt, BUF_ALLOC_TIMEOUT); if (!clone) { NET_DBG("Cannot clone %p", pkt); return -ENOMEM; } pkt = clone; net_pkt_set_ipv6_fragment_id(pkt, sys_rand32_get()); ret = net_ipv6_find_last_ext_hdr(pkt, &next_hdr_idx, &last_hdr_idx); if (ret < 0) { goto fail; } temp = net_frag_read_u8(pkt->frags, next_hdr_idx, &pos, &next_hdr); if (!temp && pos == 0xffff) { ret = -EINVAL; goto fail; } temp = net_frag_read_u8(pkt->frags, last_hdr_idx, &pos, &last_hdr); if (!temp && pos == 0xffff) { ret = -EINVAL; goto fail; } ipv6_hdrs_len = net_pkt_ip_hdr_len(pkt) + net_pkt_ipv6_ext_len(pkt); ret = net_pkt_split(pkt, pkt->frags, ipv6_hdrs_len, &rest, FRAG_BUF_WAIT); if (ret < 0 || ipv6_hdrs_len != net_pkt_get_len(pkt)) { NET_DBG("Cannot split packet (%d)", ret); goto fail; } frag_count = 0U; frag_offset = 0U; /* The Maximum payload can fit into each packet after IPv6 header, * Extenstion headers and Fragmentation header. */ fit_len = NET_IPV6_MTU - NET_IPV6_FRAGH_LEN - ipv6_hdrs_len; 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 + ipv6_hdrs_len); ret = -EINVAL; goto fail; } while (rest) { ret = send_ipv6_fragment(iface, pkt, &rest, ipv6_hdrs_len, fit_len, frag_offset, next_hdr, next_hdr_idx, last_hdr, last_hdr_idx, frag_count); if (ret < 0) { goto fail; } frag_count++; frag_offset += fit_len; } net_pkt_unref(pkt); return 0; fail: net_pkt_unref(pkt); if (rest) { net_buf_unref(rest); } return ret; }