| /**************************************************************************** |
| * 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 <nuttx/config.h> |
| #if defined(CONFIG_NET_IPv6) && defined (CONFIG_NET_IPFRAG) |
| |
| #include <sys/ioctl.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <debug.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <netinet/in.h> |
| #include <net/if.h> |
| |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/net/netconfig.h> |
| #include <nuttx/net/netdev.h> |
| #include <nuttx/net/netstats.h> |
| #include <nuttx/net/ip.h> |
| #include <nuttx/net/ipv6ext.h> |
| |
| #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: |
| * Populate 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 |
| * extension 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, ignore 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; |
| } |
| |
| /* Populate 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 */ |