/**************************************************************************** * net/ipfrag/ipv6_frag.c * * SPDX-License-Identifier: Apache-2.0 * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #if defined(CONFIG_NET_IPv6) && defined (CONFIG_NET_IPFRAG) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "netdev/netdev.h" #include "inet/inet.h" #include "ipfrag.h" /**************************************************************************** * Private Data ****************************************************************************/ /* The increasing number used for the IP ID field of IPv6 Fragment Header. */ static uint32_t g_ipv6id; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int32_t ipv6_fragin_getinfo(FAR struct iob_s *iob, FAR struct ip_fraglink_s *fraglink); static uint32_t ipv6_fragin_reassemble(FAR struct ip_fragsnode_s *node); static inline void ipv6_fragout_buildipv6header(FAR struct ipv6_hdr_s *ref, FAR struct ipv6_hdr_s *ipv6, uint16_t hdrlen, uint16_t datalen, uint16_t nxthdroff, uint16_t nxtprot); static inline void ipv6_fragout_buildipv6fragheader(FAR struct ipv6_fragment_extension_s *frag, uint8_t nxthdr, uint16_t ipoff, uint32_t ipid); static uint16_t ipv6_fragout_getunfraginfo(FAR struct iob_s *iob, uint16_t *hdroff, uint16_t *hdrtype); /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: ipv6_fragin_getinfo * * Description: * Polulate fragment information from the input ipv6 packet data. * * Input Parameters: * iob - An IPv6 fragment * fraglink - node of the lower-level linked list, it maintains information * of one fragment * * Returned Value: * OK - Got fragment information. * EINVAL - The input ipv6 packet is not a fragment. * ****************************************************************************/ static int32_t ipv6_fragin_getinfo(FAR struct iob_s *iob, FAR struct ip_fraglink_s *fraglink) { FAR struct ipv6_hdr_s *ipv6 = (FAR struct ipv6_hdr_s *) (iob->io_data + iob->io_offset); FAR struct ipv6_extension_s *exthdr; FAR uint8_t *payload; uint16_t paylen; uint8_t nxthdr; paylen = ((uint16_t)ipv6->len[0] << 8) + (uint16_t)ipv6->len[1]; payload = (FAR uint8_t *)(ipv6 + 1); exthdr = (FAR struct ipv6_extension_s *)payload; nxthdr = ipv6->proto; while (nxthdr != NEXT_FRAGMENT_EH && ipv6_exthdr(nxthdr)) { uint16_t extlen; exthdr = (FAR struct ipv6_extension_s *)payload; extlen = EXTHDR_LEN((unsigned int)exthdr->len); payload += extlen; paylen -= extlen; nxthdr = exthdr->nxthdr; }; if (nxthdr == NEXT_FRAGMENT_EH) { FAR struct ipv6_fragment_extension_s *fraghdr; fraghdr = (FAR struct ipv6_fragment_extension_s *)exthdr; /* Cut the size of fragment header, notice fragment header don't has a * length filed. */ paylen -= EXTHDR_FRAG_LEN; fraglink->flink = NULL; fraglink->fragsnode = NULL; fraglink->isipv4 = FALSE; fraglink->fragoff = (fraghdr->msoffset << 8) + fraghdr->lsoffset; fraglink->morefrags = fraglink->fragoff & 0x1; fraglink->fragoff &= 0xfff8; fraglink->fraglen = paylen; fraglink->ipid = NTOHL( ((uint32_t)(*(FAR uint16_t *)(&fraghdr->id[0])) << 16) + (uint32_t)(*(FAR uint16_t *)(&fraghdr->id[2]))); fraglink->frag = iob; return OK; } else { return -EINVAL; } } /**************************************************************************** * Name: ipv6_fragin_reassemble * * Description: * Reassemble all ipv6 fragments to build an IP frame. * * Input Parameters: * node - node of the upper-level linked list, it maintains * information about all fragments belonging to an IP datagram * * Returned Value: * The length of the reassembled IP frame * ****************************************************************************/ static uint32_t ipv6_fragin_reassemble(FAR struct ip_fragsnode_s *node) { FAR struct iob_s *head = NULL; FAR struct ipv6_hdr_s *ipv6; FAR struct ip_fraglink_s *fraglink; /* Loop to walk through the fragment list and reassemble those fragments, * the fraglink list was ordered by fragment offset value */ fraglink = node->frags; node->frags = NULL; while (fraglink != NULL) { FAR uint8_t *payload; uint8_t nxthdr; FAR struct iob_s *iob; FAR struct ip_fraglink_s *linknext; FAR struct ipv6_extension_s *exthdr; FAR struct ipv6_fragment_extension_s *fraghdr; iob = fraglink->frag; ipv6 = (FAR struct ipv6_hdr_s *)(iob->io_data + iob->io_offset); payload = (FAR uint8_t *)(ipv6 + 1); exthdr = (FAR struct ipv6_extension_s *)payload; nxthdr = ipv6->proto; /* Find fragment header and the front header which is close to the * fragment header */ while (nxthdr != NEXT_FRAGMENT_EH && ipv6_exthdr(nxthdr)) { uint16_t extlen; exthdr = (FAR struct ipv6_extension_s *)payload; extlen = EXTHDR_LEN((unsigned int)exthdr->len); payload += extlen; nxthdr = exthdr->nxthdr; }; fraghdr = (FAR struct ipv6_fragment_extension_s *)payload; /* Skip fragment header, notice fragment header don't has a length * filed */ payload += EXTHDR_FRAG_LEN; if (fraglink->fragoff == 0) { /* This is the zero fragment, Set the front header's next header * filed to the next header value of the fragment header */ if (ipv6->proto == NEXT_FRAGMENT_EH) { ipv6->proto = fraghdr->nxthdr; } else { exthdr->nxthdr = fraghdr->nxthdr; } /* Remove fragment header and fix up the data length */ memmove(fraghdr, payload, iob->io_len - (payload - (iob->io_data + iob->io_offset))); iob->io_len -= EXTHDR_FRAG_LEN; iob->io_pktlen -= EXTHDR_FRAG_LEN; /* Remember the head iob */ head = iob; } else { uint16_t new_off; /* Fix up the value of offset and length for this none zero * fragment */ new_off = payload - iob->io_data; iob->io_len -= new_off - iob->io_offset; iob->io_pktlen -= new_off - iob->io_offset; iob->io_offset = new_off; /* Concatenate this iob to the reassembly chain */ iob_concat(head, iob); } linknext = fraglink->flink; kmm_free(fraglink); fraglink = linknext; } /* Remember the reassembled outgoing IP frame */ node->outgoframe = head; /* Adjust the length value in the IP Header */ ipv6 = (FAR struct ipv6_hdr_s *)(head->io_data + head->io_offset); ipv6->len[0] = (head->io_pktlen - IPv6_HDRLEN) >> 8; ipv6->len[1] = (head->io_pktlen - IPv6_HDRLEN) & 0xff; return head->io_pktlen; } /**************************************************************************** * Name: ipv6_fragout_buildipv6header * * Description: * Build IPv6 header for an IPv6 fragment. * * Input Parameters: * ref - The reference IPv6 Header * ipv6 - The pointer of the newly generated IPv6 Header * hdrlen - Including the length of IPv6 basic header and all * extention headers * datalen - The data length follows the IPv6 basic header * nxthdroff - The offset of 'next header' to be updated * nxtprot - The value of 'next header' to be updated * * Returned Value: * None * ****************************************************************************/ static inline void ipv6_fragout_buildipv6header(FAR struct ipv6_hdr_s *ref, FAR struct ipv6_hdr_s *ipv6, uint16_t hdrlen, uint16_t datalen, uint16_t nxthdroff, uint16_t nxtprot) { if (ref != ipv6) { /* Copy unfragmentable header data from reference header */ memcpy(ipv6, ref, hdrlen); } /* Update length filed */ ipv6->len[0] = datalen >> 8; ipv6->len[1] = datalen & 0xff; /* If extension headers exist, update the Next Header field in the * last extension header of the unfragmentable part; Otherwise update * the Next Header field of the basic IPv6 header. */ *((uint8_t *)ipv6 + nxthdroff) = nxtprot; } /**************************************************************************** * Name: ipv6_fragout_buildipv6fragheader * * Description: * Build IPv6 fragment extension header for an IPv6 fragment. * * Input Parameters: * frag - The pointer of the newly generated IPv6 fragment Header * nxthdr - The first header type in the fragmentable part * ipoff - Fragment offset * ipid - The value of IPv6 IP ID * * Returned Value: * None * ****************************************************************************/ static inline void ipv6_fragout_buildipv6fragheader(FAR struct ipv6_fragment_extension_s *frag, uint8_t nxthdr, uint16_t ipoff, uint32_t ipid) { frag->nxthdr = nxthdr; frag->reserved = 0; frag->msoffset = ipoff >> 8; frag->lsoffset = ipoff & 0xff; *(FAR uint16_t *)&frag->id[0] = HTONL(ipid) & 0xffff; *(FAR uint16_t *)&frag->id[2] = HTONL(ipid) >> 16; } /**************************************************************************** * Name: ipv6_fragout_getunfraginfo * * Description: * Get the length of Unfragmentable Part of the original ipv6 packet, * remember the offset and value of nextheader in the last extension * header of the unfragmentable part. * Refer to rfc2460, section-4.1, section-4.5 * * Input Parameters: * iob - Outgoing data waiting for fragment * hdroff - The offset of the last next header position in the * unfragmentable part * hdrtype - The first header type in the fragmentable part * * Returned Value: * Unfragmentable Part length * ****************************************************************************/ static uint16_t ipv6_fragout_getunfraginfo(FAR struct iob_s *iob, uint16_t *hdroff, uint16_t *hdrtype) { uint32_t iter = 0; bool destopt = false; uint16_t delta = sizeof(struct ipv6_hdr_s); uint16_t unfraglen = delta; uint8_t nxthdr; FAR struct ipv6_hdr_s *ipv6; FAR struct ipv6_extension_s *exthdr; FAR uint8_t *payload; ipv6 = (FAR struct ipv6_hdr_s *)(iob->io_data + iob->io_offset); payload = (FAR uint8_t *)(ipv6 + 1); exthdr = (FAR struct ipv6_extension_s *)payload; nxthdr = ipv6->proto; *hdroff = offsetof(struct ipv6_hdr_s, proto); *hdrtype = ipv6->proto; /* Traverse up to three extension headers, if the Destination Options * Header appears repeatedly, ingore the secondary one and end the search. * refer to rfc2460, section-4.1 */ while (ipv6_exthdr(nxthdr) && iter++ < 3) { uint16_t extlen; exthdr = (FAR struct ipv6_extension_s *)payload; extlen = EXTHDR_LEN((unsigned int)exthdr->len); switch (nxthdr) { case NEXT_DESTOPT_EH: if (!destopt) { destopt = true; } else { /* This is the secondary Destination Options Header, * end the search */ goto done; } case NEXT_ROUTING_EH: case NEXT_HOPBYBOT_EH: unfraglen = delta + extlen; *hdroff = delta; *hdrtype = exthdr->nxthdr; } payload += extlen; delta += extlen; nxthdr = exthdr->nxthdr; } done: return unfraglen; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: ipv6_fragin * * Description: * Handling incoming IPv6 fragment input, the input data * (dev->d_iob) can be an I/O buffer chain * * Input Parameters: * dev - The NIC device that the fragmented data comes from * * Returned Value: * ENOMEM - No memory * OK - The input fragment is processed as expected * ****************************************************************************/ int32_t ipv6_fragin(FAR struct net_driver_s *dev) { FAR struct ip_fragsnode_s *node = NULL; FAR struct ip_fraglink_s *fraginfo = NULL; bool restartwdog; if (dev->d_len != dev->d_iob->io_pktlen) { nerr("ERROR: Parameters error.\n"); return -EINVAL; } fraginfo = kmm_malloc(sizeof(struct ip_fraglink_s)); if (fraginfo == NULL) { nerr("ERROR: Failed to allocate buffer.\n"); return -ENOMEM; } /* Polulate fragment information from input packet data */ ipv6_fragin_getinfo(dev->d_iob, fraginfo); nxmutex_lock(&g_ipfrag_lock); /* Need to restart reassembly worker if the original linked list is empty */ restartwdog = ip_fragin_enqueue(dev, fraginfo); node = fraginfo->fragsnode; if (node->verifyflag & IP_FRAGVERIFY_RECVDALLFRAGS) { /* Well, all fragments of an IP frame have been received, remove * node from link list first, then reassemble and dispatch to the * stack. */ ip_frag_remnode(node); /* All fragments belonging to one IP frame have been separated * from the fragment processing module, unlocks mutex as soon * as possible */ nxmutex_unlock(&g_ipfrag_lock); /* Reassemble fragments to one IP frame and set the resulting * IP frame to dev->d_iob */ ipv6_fragin_reassemble(node); netdev_iob_replace(dev, node->outgoframe); /* Free the memory of node */ kmm_free(node); return ipv6_input(dev); } nxmutex_unlock(&g_ipfrag_lock); if (restartwdog) { /* Restart the work queue for fragment processing */ ip_frag_startwdog(); } return OK; } /**************************************************************************** * Name: ipv6_fragout * * Description: * Execute the ipv6 fragment function. After this work is done, all * fragments are maintained by dev->d_fragout. In order to reduce the * cyclomatic complexity and facilitate maintenance, fragmentation is * performed in two steps: * 1. Reconstruct I/O Buffer according to MTU, which will reserve * the space for the L3 header; * 2. Fill the L3 header into the reserved space. * * Input Parameters: * dev - The NIC device * mtu - The MTU of current NIC * * Returned Value: * 0 if success or a negative value if fail. * * Assumptions: * Data length(dev->d_iob->io_pktlen) is grater than the MTU of * current NIC * ****************************************************************************/ int32_t ipv6_fragout(FAR struct net_driver_s *dev, uint16_t mtu) { uint16_t unfraglen; uint16_t offset = 0; uint32_t ipid; uint32_t iter; uint32_t nfrags; uint16_t hdroff; uint16_t hdrtype; FAR struct iob_s *frag; FAR struct ipv6_hdr_s *ref = NULL; FAR struct ipv6_fragment_extension_s *fraghdr; struct iob_queue_s fragq = { NULL, NULL }; /* Get the length of Unfragmentable Part of the original ipv6 packet, * Get the offset and value of nextheader filed in the last extension * header of the unfragmentable part. */ unfraglen = ipv6_fragout_getunfraginfo(dev->d_iob, &hdroff, &hdrtype); /* Reconstruct I/O Buffer according to MTU, which will reserve * the space for the L3 header */ nfrags = ip_fragout_slice(dev->d_iob, PF_INET6, mtu, unfraglen, &fragq); ASSERT(nfrags > 1); netdev_iob_clear(dev); ipid = ++g_ipv6id; /* Fill the L3 header into the reserved space */ for (iter = 0; iter < nfrags; iter++) { frag = iob_remove_queue(&fragq); if (iter == 0) { ref = (FAR struct ipv6_hdr_s *)(frag->io_data + frag->io_offset); /* Update the IPv6 header for the zero fragment */ ipv6_fragout_buildipv6header(ref, ref, unfraglen, frag->io_pktlen - IPv6_HDRLEN, hdroff, NEXT_FRAGMENT_EH); /* Build the fragment header for the zero fragment */ fraghdr = (FAR struct ipv6_fragment_extension_s *) (frag->io_data + frag->io_offset + unfraglen); ipv6_fragout_buildipv6fragheader(fraghdr, hdrtype, FRAGHDR_FRAG_MOREFRAGS, ipid); } else { uint16_t ipoff = offset - iter * (unfraglen + EXTHDR_FRAG_LEN); if (iter < nfrags - 1) { ipoff |= FRAGHDR_FRAG_MOREFRAGS; } /* Refer to the zero fragment ipv6 header to construct the ipv6 * header of non-zero fragment */ ipv6_fragout_buildipv6header(ref, (FAR struct ipv6_hdr_s *)(frag->io_data + frag->io_offset), unfraglen, frag->io_pktlen - IPv6_HDRLEN, hdroff, NEXT_FRAGMENT_EH); /* Build extension fragment header for non-zero fragment */ fraghdr = (FAR struct ipv6_fragment_extension_s *) (frag->io_data + frag->io_offset + unfraglen); ipv6_fragout_buildipv6fragheader(fraghdr, hdrtype, ipoff, ipid); } /* Enqueue this fragment to dev->d_fragout */ if (iob_tryadd_queue(frag, &dev->d_fragout) < 0) { goto fail; } offset += frag->io_pktlen; } #ifdef CONFIG_NET_STATISTICS g_netstats.ipv6.sent += nfrags - 1; #endif netdev_txnotify_dev(dev); return OK; fail: netdev_iob_release(dev); iob_free_chain(frag); iob_free_queue(&fragq); iob_free_queue(&dev->d_fragout); --g_ipv6id; return -ENOMEM; } #endif /* CONFIG_NET_IPv6 && CONFIG_NET_IPFRAG */