/**
 * Reassembles incoming IPv6 fragments into an IPv6 datagram.
 *
 * @param p points to the IPv6 Fragment Header
 * @param len the length of the payload (after Fragment Header)
 * @return NULL if reassembly is incomplete, pbuf pointing to
 *         IPv6 Header if reassembly is complete
 */
struct pbuf *
ip6_reass(struct pbuf *p)
{
  struct ip6_reassdata *ipr, **pipr;
  struct ip6_reass_helper *iprh, *iprh_tmp;
  struct ip6_reass_helper **pnext;
  struct ip6_frag_hdr * frag_hdr;
  size_t unfrag_len;
  u16_t offset, len, start, end, validlen;
  u8_t clen;

  IP6_FRAG_STATS_INC(ip6_frag.recv);

  frag_hdr = (struct ip6_frag_hdr *) p->payload;

  clen = pbuf_clen(p);

  offset = ntohs(frag_hdr->_fragment_offset);

  /* Calculate fragment length from IPv6 payload length.
   * Adjust for headers before Fragment Header.
   * And finally adjust by Fragment Header length. */
  len = ntohs(ip6_current_header()->_plen);
  len -= ((u8_t*)p->payload - (u8_t*)ip6_current_header()) - IP6_HLEN;
  len -= IP6_FRAG_HLEN;

  start = (offset & IP6_FRAG_OFFSET_MASK);
  end = start + len;


  /* Look for the datagram the fragment belongs to in the current datagram queue,
   * remembering the previous in the queue for later dequeueing. */
  for (ipr = reassdatagrams; ipr != NULL; ipr = ipr->next) {
    /* Check if the incoming fragment matches the one currently present
       in the reassembly buffer. If so, we proceed with copying the
       fragment into the buffer. */
    if ((frag_hdr->_identification == ipr->identification) &&
        ip6_addr_cmp(ip6_current_src_addr(), &(ipr->iphdr.src)) &&
        ip6_addr_cmp(ip6_current_dest_addr(), &(ipr->iphdr.dest))) {
      IP6_FRAG_STATS_INC(ip6_frag.cachehit);
      break;
    }
  }

  if (ipr == NULL) {
  /* Enqueue a new datagram into the datagram queue */
    ipr = (struct ip6_reassdata *)memp_malloc(MEMP_IP6_REASSDATA);
    if (ipr == NULL) {
#if IP_REASS_FREE_OLDEST
      /* Make room and try again. */
      ip6_reass_remove_oldest_datagram(ipr, clen);
      ipr = (struct ip6_reassdata *)memp_malloc(MEMP_IP6_REASSDATA);
      if (ipr == NULL)
#endif /* IP_REASS_FREE_OLDEST */
      {
        IP6_FRAG_STATS_INC(ip6_frag.memerr);
        IP6_FRAG_STATS_INC(ip6_frag.drop);
        goto nullreturn;
      }
    }

    memset(ipr, 0, sizeof(struct ip6_reassdata));
    ipr->timer = IP_REASS_MAXAGE;

    /* enqueue the new structure to the front of the list */
    ipr->next = reassdatagrams;
    reassdatagrams = ipr;

    /* Use the current IPv6 header for src/dest address reference.
     * Eventually, we will replace it when we get the first fragment
     * (it might be this one, in any case, it is done later). */
    SMEMCPY(&ipr->iphdr, ip6_current_header(), IP6_HLEN);
    if (start == 0) {
      ipr->iphdr0 = (struct ip6_hdr *)ip6_current_header();
    }

    /* copy the fragmented packet id. */
    ipr->identification = frag_hdr->_identification;
  }

  /* If this is the last fragment, save total packet length. */
  if ((offset & IP6_FRAG_MORE_FLAG) == 0) {
#if IP_REASS_CHECK_OVERLAP
    if (ipr->datagram_len != 0) {
      IP6_FRAG_STATS_INC(ip6_frag.proterr);
      IP6_FRAG_STATS_INC(ip6_frag.drop);
      goto nullreturn;
    }
#endif /* IP_REASS_CHECK_OVERLAP */
    ipr->datagram_len = end;
  }

  /* find the place to insert this pbuf */
  validlen = 0;
  for (pnext = &ipr->iprh; *pnext != NULL; pnext = &(*pnext)->next) {
    iprh_tmp = *pnext;

    if (start < iprh_tmp->start) {
      /* the new pbuf should be inserted before this */
#if IP_REASS_CHECK_OVERLAP
      if (end > iprh_tmp->start) {
        /* fragment overlaps with following, throw away */
        IP6_FRAG_STATS_INC(ip6_frag.proterr);
        IP6_FRAG_STATS_INC(ip6_frag.drop);
        goto nullreturn;
      }
#endif /* IP_REASS_CHECK_OVERLAP */
      break;
    }
    else if (start == iprh_tmp->start) {
      /* received the same datagram twice: no need to keep the datagram */
      IP6_FRAG_STATS_INC(ip6_frag.drop);
      goto nullreturn;
    }
#if IP_REASS_CHECK_OVERLAP
    else if (start < iprh_tmp->end) {
      /* overlap: no need to keep the new datagram */
      IP6_FRAG_STATS_INC(ip6_frag.proterr);
      IP6_FRAG_STATS_INC(ip6_frag.drop);
      goto nullreturn;
    }
#endif /* IP_REASS_CHECK_OVERLAP */
    else {
      /* Check if the fragments received so far have no gaps. */
      if (validlen == iprh_tmp->start) {
        validlen = iprh_tmp->end;
      }
      else {
        validlen = 0;
      }
    }
  }

  /* Check if we are allowed to enqueue more datagrams. */
  if ((ip6_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS) {
#if IP_REASS_FREE_OLDEST
    ip6_reass_remove_oldest_datagram(ipr, clen);
    if ((ip6_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS)
#endif /* IP_REASS_FREE_OLDEST */
    {
      /* @todo: send ICMPv6 time exceeded here? */
      /* drop this pbuf */
      IP6_FRAG_STATS_INC(ip6_frag.memerr);
      IP6_FRAG_STATS_INC(ip6_frag.drop);
      goto nullreturn;
    }
  }

  if (start == 0 && ipr->iphdr0 == NULL) {
    /*
     * We've got the fragment with offset 0 out of order, remember its
     * IPv6 header location (in the hidden part of the current pbuf)
     * and update the copy in ip6_reassdata::iphdr.  We don't need to
     * copy complete header since src and dest are the same as in the
     * first fragment we received.
     */
    ipr->iphdr0 = (struct ip6_hdr *)ip6_current_header();
    SMEMCPY(&ipr->iphdr, ip6_current_header(),
            IP6_HLEN - 2 * sizeof(ip_addr_p_t));
  }

  /* Overwrite IPv6 Header with our own helper struct (aligned). */
  iprh = (struct ip6_reass_helper *)
    (((uintptr_t)(u8_t *)ip6_current_header() + sizeof(void *) - 1)
     & ~(sizeof(void *) - 1));
  iprh->p = p;
  iprh->start = start;
  iprh->end = end;

  /* insert it into the list */
  iprh->next = *pnext;
  *pnext = iprh;

  /* Track the current number of pbufs current 'in-flight', in order to limit
  the number of fragments that may be enqueued at any one time */
  ip6_reass_pbufcount += clen;

  if (ipr->datagram_len == 0) {
    /* We still don't have the last fragment. */
    return NULL;
  }

  if (validlen == start) {
    validlen = end;
  }
  else {
    /* There are gaps before this fragment. */
    return NULL;
  }

  if (validlen != 0) {
    /*
     * We know we have all the data up to the end of this fragment and
     * we know the total length.  Check if the reassembly is complete.
     */
    for (iprh_tmp = iprh->next; iprh_tmp != NULL; iprh_tmp = iprh_tmp->next) {
      if (validlen == iprh_tmp->start) {
        validlen = iprh_tmp->end;
      }
      else {
        validlen = 0;
        break;
      }
    }

    if (validlen != ipr->datagram_len) {
      /* the datagram is not yet reassembled completely */
      return NULL;
    }
  }

  /*
   * All fragments have been received.  Reassemble original datagram
   * and return it to ip6_input() to be processed instead of the final
   * fragment that completed the reassembly.
   */

  /* chain together the pbufs contained within the ip6_reassdata list. */
  p = NULL;
  for (iprh = ipr->iprh; iprh != NULL; iprh = iprh->next) {
    if (p == NULL) {
      p = iprh->p;
    }
    else {
      /* hide the fragment header for every succeeding fragment */
      pbuf_header(iprh->p, -IP6_FRAG_HLEN);
      pbuf_cat(p, iprh->p);
    }
  }

  /* Adjust datagram length by adding preceding header lengths. */
  unfrag_len = (u8_t *)p->payload - (u8_t *)ipr->iphdr0;
# ifndef VBOX
  ipr->datagram_len += unfrag_len - IP6_HLEN + IP6_FRAG_HLEN;
# else
  LWIP_ASSERT("overflow", (s16_t)unfrag_len == (ssize_t)unfrag_len); /* s16_t because of pbuf_header call */
  ipr->datagram_len += (u16_t)(unfrag_len - IP6_HLEN + IP6_FRAG_HLEN);
# endif

  /* Set payload length in ip header. */
  ipr->iphdr._plen = htons(ipr->datagram_len);

  /* restore IPv6 header (overwritten with ip6_reass_helper) */
  SMEMCPY(ipr->iphdr0, &ipr->iphdr, IP6_HLEN);

  /* Mark as "single fragment" packet (see caller). */
  frag_hdr = (struct ip6_frag_hdr *) p->payload;
  frag_hdr->_fragment_offset = 0;

  /* Unlink from the reassdatagrams list */
  for (pipr = &reassdatagrams; *pipr != NULL; pipr = &(*pipr)->next) {
    if (*pipr == ipr) {
      (*pipr) = ipr->next;
      break;
    }
  }
  memp_free(MEMP_IP6_REASSDATA, ipr);

  /* adjust the number of pbufs currently queued for reassembly. */
  ip6_reass_pbufcount -= pbuf_clen(p);

  /* Move pbuf back to IPv6 header. */
# ifndef VBOX
  if (pbuf_header(p, unfrag_len) != 0) {
# else
  if (pbuf_header(p, (s16_t)unfrag_len) != 0) {
# endif
    LWIP_ASSERT("ip6_reass: moving p->payload to ip6 header failed\n", 0);
    goto nullreturn;
  }

  /* Return the pbuf chain */
  return p;

nullreturn:
  pbuf_free(p);
  return NULL;
}

#endif /* LWIP_IPV6 && LWIP_IPV6_REASS */

#if LWIP_IPV6 && LWIP_IPV6_FRAG

/** Allocate a new struct pbuf_custom_ref */
static struct pbuf_custom_ref*
ip6_frag_alloc_pbuf_custom_ref(void)
{
  return (struct pbuf_custom_ref*)memp_malloc(MEMP_FRAG_PBUF);
}

/** Free a struct pbuf_custom_ref */
static void
ip6_frag_free_pbuf_custom_ref(struct pbuf_custom_ref* p)
{
  LWIP_ASSERT("p != NULL", p != NULL);
  memp_free(MEMP_FRAG_PBUF, p);
}
Beispiel #2
0
/**
 * Reassembles incoming IPv6 fragments into an IPv6 datagram.
 *
 * @param p points to the IPv6 Fragment Header
 * @return NULL if reassembly is incomplete, pbuf pointing to
 *         IPv6 Header if reassembly is complete
 */
struct pbuf *
ip6_reass(struct pbuf *p)
{
  struct ip6_reassdata *ipr, *ipr_prev;
  struct ip6_reass_helper *iprh, *iprh_tmp, *iprh_prev=NULL;
  struct ip6_frag_hdr *frag_hdr;
  u16_t offset, len;
  u16_t clen;
  u8_t valid = 1;
  struct pbuf *q;

  IP6_FRAG_STATS_INC(ip6_frag.recv);

  if ((const void*)ip6_current_header() != ((u8_t*)p->payload) - IP6_HLEN) {
    /* ip6_frag_hdr must be in the first pbuf, not chained */
    IP6_FRAG_STATS_INC(ip6_frag.proterr);
    IP6_FRAG_STATS_INC(ip6_frag.drop);
    goto nullreturn;
  }

  frag_hdr = (struct ip6_frag_hdr *) p->payload;

  clen = pbuf_clen(p);

  offset = lwip_ntohs(frag_hdr->_fragment_offset);

  /* Calculate fragment length from IPv6 payload length.
   * Adjust for headers before Fragment Header.
   * And finally adjust by Fragment Header length. */
  len = lwip_ntohs(ip6_current_header()->_plen);
  len -= (u16_t)(((u8_t*)p->payload - (const u8_t*)ip6_current_header()) - IP6_HLEN);
  len -= IP6_FRAG_HLEN;

  /* Look for the datagram the fragment belongs to in the current datagram queue,
   * remembering the previous in the queue for later dequeueing. */
  for (ipr = reassdatagrams, ipr_prev = NULL; ipr != NULL; ipr = ipr->next) {
    /* Check if the incoming fragment matches the one currently present
       in the reassembly buffer. If so, we proceed with copying the
       fragment into the buffer. */
    if ((frag_hdr->_identification == ipr->identification) &&
        ip6_addr_cmp(ip6_current_src_addr(), &(IPV6_FRAG_HDRREF(ipr->iphdr)->src)) &&
        ip6_addr_cmp(ip6_current_dest_addr(), &(IPV6_FRAG_HDRREF(ipr->iphdr)->dest))) {
      IP6_FRAG_STATS_INC(ip6_frag.cachehit);
      break;
    }
    ipr_prev = ipr;
  }

  if (ipr == NULL) {
  /* Enqueue a new datagram into the datagram queue */
    ipr = (struct ip6_reassdata *)memp_malloc(MEMP_IP6_REASSDATA);
    if (ipr == NULL) {
#if IP_REASS_FREE_OLDEST
      /* Make room and try again. */
      ip6_reass_remove_oldest_datagram(ipr, clen);
      ipr = (struct ip6_reassdata *)memp_malloc(MEMP_IP6_REASSDATA);
      if (ipr != NULL) {
        /* re-search ipr_prev since it might have been removed */
        for (ipr_prev = reassdatagrams; ipr_prev != NULL; ipr_prev = ipr_prev->next) {
          if (ipr_prev->next == ipr) {
            break;
          }
        }
      } else
#endif /* IP_REASS_FREE_OLDEST */
      {
        IP6_FRAG_STATS_INC(ip6_frag.memerr);
        IP6_FRAG_STATS_INC(ip6_frag.drop);
        goto nullreturn;
      }
    }

    memset(ipr, 0, sizeof(struct ip6_reassdata));
    ipr->timer = IP_REASS_MAXAGE;

    /* enqueue the new structure to the front of the list */
    ipr->next = reassdatagrams;
    reassdatagrams = ipr;

    /* Use the current IPv6 header for src/dest address reference.
     * Eventually, we will replace it when we get the first fragment
     * (it might be this one, in any case, it is done later). */
#if IPV6_FRAG_COPYHEADER
    MEMCPY(&ipr->iphdr, ip6_current_header(), IP6_HLEN);
#else /* IPV6_FRAG_COPYHEADER */
    /* need to use the none-const pointer here: */
    ipr->iphdr = ip_data.current_ip6_header;
#endif /* IPV6_FRAG_COPYHEADER */

    /* copy the fragmented packet id. */
    ipr->identification = frag_hdr->_identification;

    /* copy the nexth field */
    ipr->nexth = frag_hdr->_nexth;
  }

  /* Check if we are allowed to enqueue more datagrams. */
  if ((ip6_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS) {
#if IP_REASS_FREE_OLDEST
    ip6_reass_remove_oldest_datagram(ipr, clen);
    if ((ip6_reass_pbufcount + clen) <= IP_REASS_MAX_PBUFS) {
      /* re-search ipr_prev since it might have been removed */
      for (ipr_prev = reassdatagrams; ipr_prev != NULL; ipr_prev = ipr_prev->next) {
        if (ipr_prev->next == ipr) {
          break;
        }
      }
    } else
#endif /* IP_REASS_FREE_OLDEST */
    {
      /* @todo: send ICMPv6 time exceeded here? */
      /* drop this pbuf */
      IP6_FRAG_STATS_INC(ip6_frag.memerr);
      IP6_FRAG_STATS_INC(ip6_frag.drop);
      goto nullreturn;
    }
  }

  /* Overwrite Fragment Header with our own helper struct. */
#if IPV6_FRAG_COPYHEADER
  if (IPV6_FRAG_REQROOM > 0) {
    /* Make room for struct ip6_reass_helper (only required if sizeof(void*) > 4).
       This cannot fail since we already checked when receiving this fragment. */
    u8_t hdrerr = pbuf_header_force(p, IPV6_FRAG_REQROOM);
    LWIP_UNUSED_ARG(hdrerr); /* in case of LWIP_NOASSERT */
    LWIP_ASSERT("no room for struct ip6_reass_helper", hdrerr == 0);
  }
#else /* IPV6_FRAG_COPYHEADER */
  LWIP_ASSERT("sizeof(struct ip6_reass_helper) <= IP6_FRAG_HLEN, set IPV6_FRAG_COPYHEADER to 1",
    sizeof(struct ip6_reass_helper) <= IP6_FRAG_HLEN);
#endif /* IPV6_FRAG_COPYHEADER */
  iprh = (struct ip6_reass_helper *)p->payload;
  iprh->next_pbuf = NULL;
  iprh->start = (offset & IP6_FRAG_OFFSET_MASK);
  iprh->end = (offset & IP6_FRAG_OFFSET_MASK) + len;

  /* find the right place to insert this pbuf */
  /* Iterate through until we either get to the end of the list (append),
   * or we find on with a larger offset (insert). */
  for (q = ipr->p; q != NULL;) {
    iprh_tmp = (struct ip6_reass_helper*)q->payload;
    if (iprh->start < iprh_tmp->start) {
#if IP_REASS_CHECK_OVERLAP
      if (iprh->end > iprh_tmp->start) {
        /* fragment overlaps with following, throw away */
        IP6_FRAG_STATS_INC(ip6_frag.proterr);
        IP6_FRAG_STATS_INC(ip6_frag.drop);
        goto nullreturn;
      }
      if (iprh_prev != NULL) {
        if (iprh->start < iprh_prev->end) {
          /* fragment overlaps with previous, throw away */
          IP6_FRAG_STATS_INC(ip6_frag.proterr);
          IP6_FRAG_STATS_INC(ip6_frag.drop);
          goto nullreturn;
        }
      }
#endif /* IP_REASS_CHECK_OVERLAP */
      /* the new pbuf should be inserted before this */
      iprh->next_pbuf = q;
      if (iprh_prev != NULL) {
        /* not the fragment with the lowest offset */
        iprh_prev->next_pbuf = p;
      } else {
        /* fragment with the lowest offset */
        ipr->p = p;
      }
      break;
    } else if (iprh->start == iprh_tmp->start) {
      /* received the same datagram twice: no need to keep the datagram */
      IP6_FRAG_STATS_INC(ip6_frag.drop);
      goto nullreturn;
#if IP_REASS_CHECK_OVERLAP
    } else if (iprh->start < iprh_tmp->end) {
      /* overlap: no need to keep the new datagram */
      IP6_FRAG_STATS_INC(ip6_frag.proterr);
      IP6_FRAG_STATS_INC(ip6_frag.drop);
      goto nullreturn;
#endif /* IP_REASS_CHECK_OVERLAP */
    } else {
      /* Check if the fragments received so far have no gaps. */
      if (iprh_prev != NULL) {
        if (iprh_prev->end != iprh_tmp->start) {
          /* There is a fragment missing between the current
           * and the previous fragment */
          valid = 0;
        }
      }
    }
    q = iprh_tmp->next_pbuf;
    iprh_prev = iprh_tmp;
  }

  /* If q is NULL, then we made it to the end of the list. Determine what to do now */
  if (q == NULL) {
    if (iprh_prev != NULL) {
      /* this is (for now), the fragment with the highest offset:
       * chain it to the last fragment */
#if IP_REASS_CHECK_OVERLAP
      LWIP_ASSERT("check fragments don't overlap", iprh_prev->end <= iprh->start);
#endif /* IP_REASS_CHECK_OVERLAP */
      iprh_prev->next_pbuf = p;
      if (iprh_prev->end != iprh->start) {
        valid = 0;
      }
    } else {
#if IP_REASS_CHECK_OVERLAP
      LWIP_ASSERT("no previous fragment, this must be the first fragment!",
        ipr->p == NULL);
#endif /* IP_REASS_CHECK_OVERLAP */
      /* this is the first fragment we ever received for this ip datagram */
      ipr->p = p;
    }
  }

  /* Track the current number of pbufs current 'in-flight', in order to limit
  the number of fragments that may be enqueued at any one time */
  ip6_reass_pbufcount += clen;

  /* Remember IPv6 header if this is the first fragment. */
  if (iprh->start == 0) {
#if IPV6_FRAG_COPYHEADER
    if (iprh->next_pbuf != NULL) {
      MEMCPY(&ipr->iphdr, ip6_current_header(), IP6_HLEN);
    }
#else /* IPV6_FRAG_COPYHEADER */
    /* need to use the none-const pointer here: */
    ipr->iphdr = ip_data.current_ip6_header;
#endif /* IPV6_FRAG_COPYHEADER */
  }

  /* If this is the last fragment, calculate total packet length. */
  if ((offset & IP6_FRAG_MORE_FLAG) == 0) {
    ipr->datagram_len = iprh->end;
  }

  /* Additional validity tests: we have received first and last fragment. */
  iprh_tmp = (struct ip6_reass_helper*)ipr->p->payload;
  if (iprh_tmp->start != 0) {
    valid = 0;
  }
  if (ipr->datagram_len == 0) {
    valid = 0;
  }

  /* Final validity test: no gaps between current and last fragment. */
  iprh_prev = iprh;
  q = iprh->next_pbuf;
  while ((q != NULL) && valid) {
    iprh = (struct ip6_reass_helper*)q->payload;
    if (iprh_prev->end != iprh->start) {
      valid = 0;
      break;
    }
    iprh_prev = iprh;
    q = iprh->next_pbuf;
  }

  if (valid) {
    /* All fragments have been received */
    struct ip6_hdr* iphdr_ptr;

    /* chain together the pbufs contained within the ip6_reassdata list. */
    iprh = (struct ip6_reass_helper*) ipr->p->payload;
    while (iprh != NULL) {
      struct pbuf* next_pbuf = iprh->next_pbuf;
      if (next_pbuf != NULL) {
        /* Save next helper struct (will be hidden in next step). */
        iprh_tmp = (struct ip6_reass_helper*)next_pbuf->payload;

        /* hide the fragment header for every succeeding fragment */
        pbuf_header(next_pbuf, -IP6_FRAG_HLEN);
#if IPV6_FRAG_COPYHEADER
        if (IPV6_FRAG_REQROOM > 0) {
          /* hide the extra bytes borrowed from ip6_hdr for struct ip6_reass_helper */
          u8_t hdrerr = pbuf_header(next_pbuf, -(s16_t)(IPV6_FRAG_REQROOM));
          LWIP_UNUSED_ARG(hdrerr); /* in case of LWIP_NOASSERT */
          LWIP_ASSERT("no room for struct ip6_reass_helper", hdrerr == 0);
        }
#endif
        pbuf_cat(ipr->p, next_pbuf);
      }
      else {
        iprh_tmp = NULL;
      }

      iprh = iprh_tmp;
    }

#if IPV6_FRAG_COPYHEADER
    if (IPV6_FRAG_REQROOM > 0) {
      /* get back room for struct ip6_reass_helper (only required if sizeof(void*) > 4) */
      u8_t hdrerr = pbuf_header(ipr->p, -(s16_t)(IPV6_FRAG_REQROOM));
      LWIP_UNUSED_ARG(hdrerr); /* in case of LWIP_NOASSERT */
      LWIP_ASSERT("no room for struct ip6_reass_helper", hdrerr == 0);
    }
    iphdr_ptr = (struct ip6_hdr*)((u8_t*)ipr->p->payload - IP6_HLEN);
    MEMCPY(iphdr_ptr, &ipr->iphdr, IP6_HLEN);
#else
    iphdr_ptr = ipr->iphdr;
#endif

    /* Adjust datagram length by adding header lengths. */
    ipr->datagram_len += (u16_t)(((u8_t*)ipr->p->payload - (u8_t*)iphdr_ptr)
                         + IP6_FRAG_HLEN
                         - IP6_HLEN);

    /* Set payload length in ip header. */
    iphdr_ptr->_plen = lwip_htons(ipr->datagram_len);

    /* Get the first pbuf. */
    p = ipr->p;

    /* Restore Fragment Header in first pbuf. Mark as "single fragment"
     * packet. Restore nexth. */
    frag_hdr = (struct ip6_frag_hdr *) p->payload;
    frag_hdr->_nexth = ipr->nexth;
    frag_hdr->reserved = 0;
    frag_hdr->_fragment_offset = 0;
    frag_hdr->_identification = 0;

    /* release the sources allocate for the fragment queue entry */
    if (reassdatagrams == ipr) {
      /* it was the first in the list */
      reassdatagrams = ipr->next;
    } else {
      /* it wasn't the first, so it must have a valid 'prev' */
      LWIP_ASSERT("sanity check linked list", ipr_prev != NULL);
      ipr_prev->next = ipr->next;
    }
    memp_free(MEMP_IP6_REASSDATA, ipr);

    /* adjust the number of pbufs currently queued for reassembly. */
    ip6_reass_pbufcount -= pbuf_clen(p);

    /* Move pbuf back to IPv6 header.
       This cannot fail since we already checked when receiving this fragment. */
    if (pbuf_header_force(p, (s16_t)((u8_t*)p->payload - (u8_t*)iphdr_ptr))) {
      LWIP_ASSERT("ip6_reass: moving p->payload to ip6 header failed\n", 0);
      pbuf_free(p);
      return NULL;
    }

    /* Return the pbuf chain */
    return p;
  }
  /* the datagram is not (yet?) reassembled completely */
  return NULL;

nullreturn:
  pbuf_free(p);
  return NULL;
}
Beispiel #3
0
/**
 * Reassembles incoming IPv6 fragments into an IPv6 datagram.
 *
 * @param p points to the IPv6 Fragment Header
 * @return NULL if reassembly is incomplete, pbuf pointing to
 *         IPv6 Header if reassembly is complete
 */
struct pbuf *
ip6_reass(struct pbuf *p)
{
  struct ip6_reassdata *ipr, *ipr_prev;
  struct ip6_reass_helper *iprh, *iprh_tmp, *iprh_prev=NULL;
  struct ip6_frag_hdr *frag_hdr;
  u16_t offset, len, start, end;
  ptrdiff_t hdrdiff;
  u16_t clen;
  u8_t valid = 1;
  struct pbuf *q, *next_pbuf;

  IP6_FRAG_STATS_INC(ip6_frag.recv);

  /* ip6_frag_hdr must be in the first pbuf, not chained. Checked by caller. */
  LWIP_ASSERT("IPv6 fragment header does not fit in first pbuf",
    p->len >= sizeof(struct ip6_frag_hdr));

  frag_hdr = (struct ip6_frag_hdr *) p->payload;

  clen = pbuf_clen(p);

  offset = lwip_ntohs(frag_hdr->_fragment_offset);

  /* Calculate fragment length from IPv6 payload length.
   * Adjust for headers before Fragment Header.
   * And finally adjust by Fragment Header length. */
  len = lwip_ntohs(ip6_current_header()->_plen);
  hdrdiff = (u8_t*)p->payload - (const u8_t*)ip6_current_header();
  LWIP_ASSERT("not a valid pbuf (ip6_input check missing?)", hdrdiff <= 0xFFFF);
  LWIP_ASSERT("not a valid pbuf (ip6_input check missing?)", hdrdiff >= IP6_HLEN);
  hdrdiff -= IP6_HLEN;
  hdrdiff += IP6_FRAG_HLEN;
  if (hdrdiff > len) {
    IP6_FRAG_STATS_INC(ip6_frag.proterr);
    goto nullreturn;
  }
  len = (u16_t)(len - hdrdiff);
  start = (offset & IP6_FRAG_OFFSET_MASK);
  if (start > (0xFFFF - len)) {
    /* u16_t overflow, cannot handle this */
    IP6_FRAG_STATS_INC(ip6_frag.proterr);
    goto nullreturn;
  }

  /* Look for the datagram the fragment belongs to in the current datagram queue,
   * remembering the previous in the queue for later dequeueing. */
  for (ipr = reassdatagrams, ipr_prev = NULL; ipr != NULL; ipr = ipr->next) {
    /* Check if the incoming fragment matches the one currently present
       in the reassembly buffer. If so, we proceed with copying the
       fragment into the buffer. */
    if ((frag_hdr->_identification == ipr->identification) &&
        ip6_addr_cmp_packed(ip6_current_src_addr(), &(IPV6_FRAG_SRC(ipr)), ipr->src_zone) &&
        ip6_addr_cmp_packed(ip6_current_dest_addr(), &(IPV6_FRAG_DEST(ipr)), ipr->dest_zone)) {
      IP6_FRAG_STATS_INC(ip6_frag.cachehit);
      break;
    }
    ipr_prev = ipr;
  }

  if (ipr == NULL) {
  /* Enqueue a new datagram into the datagram queue */
    ipr = (struct ip6_reassdata *)memp_malloc(MEMP_IP6_REASSDATA);
    if (ipr == NULL) {
#if IP_REASS_FREE_OLDEST
      /* Make room and try again. */
      ip6_reass_remove_oldest_datagram(ipr, clen);
      ipr = (struct ip6_reassdata *)memp_malloc(MEMP_IP6_REASSDATA);
      if (ipr != NULL) {
        /* re-search ipr_prev since it might have been removed */
        for (ipr_prev = reassdatagrams; ipr_prev != NULL; ipr_prev = ipr_prev->next) {
          if (ipr_prev->next == ipr) {
            break;
          }
        }
      } else
#endif /* IP_REASS_FREE_OLDEST */
      {
        IP6_FRAG_STATS_INC(ip6_frag.memerr);
        goto nullreturn;
      }
    }

    memset(ipr, 0, sizeof(struct ip6_reassdata));
    ipr->timer = IPV6_REASS_MAXAGE;

    /* enqueue the new structure to the front of the list */
    ipr->next = reassdatagrams;
    reassdatagrams = ipr;

    /* Use the current IPv6 header for src/dest address reference.
     * Eventually, we will replace it when we get the first fragment
     * (it might be this one, in any case, it is done later). */
    /* need to use the none-const pointer here: */
    ipr->iphdr = ip_data.current_ip6_header;
#if IPV6_FRAG_COPYHEADER
    MEMCPY(&ipr->src, &ip6_current_header()->src, sizeof(ipr->src));
    MEMCPY(&ipr->dest, &ip6_current_header()->dest, sizeof(ipr->dest));
#endif /* IPV6_FRAG_COPYHEADER */
#if LWIP_IPV6_SCOPES
    /* Also store the address zone information.
     * @todo It is possible that due to netif destruction and recreation, the
     * stored zones end up resolving to a different interface. In that case, we
     * risk sending a "time exceeded" ICMP response over the wrong link.
     * Ideally, netif destruction would clean up matching pending reassembly
     * structures, but custom zone mappings would make that non-trivial. */
    ipr->src_zone = ip6_addr_zone(ip6_current_src_addr());
    ipr->dest_zone = ip6_addr_zone(ip6_current_dest_addr());
#endif /* LWIP_IPV6_SCOPES */
    /* copy the fragmented packet id. */
    ipr->identification = frag_hdr->_identification;

    /* copy the nexth field */
    ipr->nexth = frag_hdr->_nexth;
  }

  /* Check if we are allowed to enqueue more datagrams. */
  if ((ip6_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS) {
#if IP_REASS_FREE_OLDEST
    ip6_reass_remove_oldest_datagram(ipr, clen);
    if ((ip6_reass_pbufcount + clen) <= IP_REASS_MAX_PBUFS) {
      /* re-search ipr_prev since it might have been removed */
      for (ipr_prev = reassdatagrams; ipr_prev != NULL; ipr_prev = ipr_prev->next) {
        if (ipr_prev->next == ipr) {
          break;
        }
      }
    } else
#endif /* IP_REASS_FREE_OLDEST */
    {
      /* @todo: send ICMPv6 time exceeded here? */
      /* drop this pbuf */
      IP6_FRAG_STATS_INC(ip6_frag.memerr);
      goto nullreturn;
    }
  }

  /* Overwrite Fragment Header with our own helper struct. */
#if IPV6_FRAG_COPYHEADER
  if (IPV6_FRAG_REQROOM > 0) {
    /* Make room for struct ip6_reass_helper (only required if sizeof(void*) > 4).
       This cannot fail since we already checked when receiving this fragment. */
    u8_t hdrerr = pbuf_header_force(p, IPV6_FRAG_REQROOM);
    LWIP_UNUSED_ARG(hdrerr); /* in case of LWIP_NOASSERT */
    LWIP_ASSERT("no room for struct ip6_reass_helper", hdrerr == 0);
  }
#else /* IPV6_FRAG_COPYHEADER */
  LWIP_ASSERT("sizeof(struct ip6_reass_helper) <= IP6_FRAG_HLEN, set IPV6_FRAG_COPYHEADER to 1",
    sizeof(struct ip6_reass_helper) <= IP6_FRAG_HLEN);
#endif /* IPV6_FRAG_COPYHEADER */

  /* Prepare the pointer to the helper structure, and its initial values.
   * Do not yet write to the structure itself, as we still have to make a
   * backup of the original data, and we should not do that until we know for
   * sure that we are going to add this packet to the list. */
  iprh = (struct ip6_reass_helper *)p->payload;
  next_pbuf = NULL;
  end = (u16_t)(start + len);

  /* find the right place to insert this pbuf */
  /* Iterate through until we either get to the end of the list (append),
   * or we find on with a larger offset (insert). */
  for (q = ipr->p; q != NULL;) {
    iprh_tmp = (struct ip6_reass_helper*)q->payload;
    if (start < iprh_tmp->start) {
#if IP_REASS_CHECK_OVERLAP
      if (end > iprh_tmp->start) {
        /* fragment overlaps with following, throw away */
        IP6_FRAG_STATS_INC(ip6_frag.proterr);
        goto nullreturn;
      }
      if (iprh_prev != NULL) {
        if (start < iprh_prev->end) {
          /* fragment overlaps with previous, throw away */
          IP6_FRAG_STATS_INC(ip6_frag.proterr);
          goto nullreturn;
        }
      }
#endif /* IP_REASS_CHECK_OVERLAP */
      /* the new pbuf should be inserted before this */
      next_pbuf = q;
      if (iprh_prev != NULL) {
        /* not the fragment with the lowest offset */
        iprh_prev->next_pbuf = p;
      } else {
        /* fragment with the lowest offset */
        ipr->p = p;
      }
      break;
    } else if (start == iprh_tmp->start) {
      /* received the same datagram twice: no need to keep the datagram */
      goto nullreturn;
#if IP_REASS_CHECK_OVERLAP
    } else if (start < iprh_tmp->end) {
      /* overlap: no need to keep the new datagram */
      IP6_FRAG_STATS_INC(ip6_frag.proterr);
      goto nullreturn;
#endif /* IP_REASS_CHECK_OVERLAP */
    } else {
      /* Check if the fragments received so far have no gaps. */
      if (iprh_prev != NULL) {
        if (iprh_prev->end != iprh_tmp->start) {
          /* There is a fragment missing between the current
           * and the previous fragment */
          valid = 0;
        }
      }
    }
    q = iprh_tmp->next_pbuf;
    iprh_prev = iprh_tmp;
  }

  /* If q is NULL, then we made it to the end of the list. Determine what to do now */
  if (q == NULL) {
    if (iprh_prev != NULL) {
      /* this is (for now), the fragment with the highest offset:
       * chain it to the last fragment */
#if IP_REASS_CHECK_OVERLAP
      LWIP_ASSERT("check fragments don't overlap", iprh_prev->end <= start);
#endif /* IP_REASS_CHECK_OVERLAP */
      iprh_prev->next_pbuf = p;
      if (iprh_prev->end != start) {
        valid = 0;
      }
    } else {
#if IP_REASS_CHECK_OVERLAP
      LWIP_ASSERT("no previous fragment, this must be the first fragment!",
        ipr->p == NULL);
#endif /* IP_REASS_CHECK_OVERLAP */
      /* this is the first fragment we ever received for this ip datagram */
      ipr->p = p;
    }
  }

  /* Track the current number of pbufs current 'in-flight', in order to limit
  the number of fragments that may be enqueued at any one time */
  ip6_reass_pbufcount = (u16_t)(ip6_reass_pbufcount + clen);

  /* Remember IPv6 header if this is the first fragment. */
  if (start == 0) {
    /* need to use the none-const pointer here: */
    ipr->iphdr = ip_data.current_ip6_header;
    /* Make a backup of the part of the packet data that we are about to
     * overwrite, so that we can restore the original later. */
    MEMCPY(ipr->orig_hdr, p->payload, sizeof(*iprh));
    /* For IPV6_FRAG_COPYHEADER there is no need to copy src/dst again, as they
     * will be the same as they were. With LWIP_IPV6_SCOPES, the same applies
     * to the source/destination zones. */
  }
  /* Only after the backup do we get to fill in the actual helper structure. */
  iprh->next_pbuf = next_pbuf;
  iprh->start = start;
  iprh->end = end;

  /* If this is the last fragment, calculate total packet length. */
  if ((offset & IP6_FRAG_MORE_FLAG) == 0) {
    ipr->datagram_len = iprh->end;
  }

  /* Additional validity tests: we have received first and last fragment. */
  iprh_tmp = (struct ip6_reass_helper*)ipr->p->payload;
  if (iprh_tmp->start != 0) {
    valid = 0;
  }
  if (ipr->datagram_len == 0) {
    valid = 0;
  }

  /* Final validity test: no gaps between current and last fragment. */
  iprh_prev = iprh;
  q = iprh->next_pbuf;
  while ((q != NULL) && valid) {
    iprh = (struct ip6_reass_helper*)q->payload;
    if (iprh_prev->end != iprh->start) {
      valid = 0;
      break;
    }
    iprh_prev = iprh;
    q = iprh->next_pbuf;
  }

  if (valid) {
    /* All fragments have been received */
    struct ip6_hdr* iphdr_ptr;

    /* chain together the pbufs contained within the ip6_reassdata list. */
    iprh = (struct ip6_reass_helper*) ipr->p->payload;
    while (iprh != NULL) {
      next_pbuf = iprh->next_pbuf;
      if (next_pbuf != NULL) {
        /* Save next helper struct (will be hidden in next step). */
        iprh_tmp = (struct ip6_reass_helper*)next_pbuf->payload;

        /* hide the fragment header for every succeeding fragment */
        pbuf_remove_header(next_pbuf, IP6_FRAG_HLEN);
#if IPV6_FRAG_COPYHEADER
        if (IPV6_FRAG_REQROOM > 0) {
          /* hide the extra bytes borrowed from ip6_hdr for struct ip6_reass_helper */
          u8_t hdrerr = pbuf_remove_header(next_pbuf, IPV6_FRAG_REQROOM);
          LWIP_UNUSED_ARG(hdrerr); /* in case of LWIP_NOASSERT */
          LWIP_ASSERT("no room for struct ip6_reass_helper", hdrerr == 0);
        }
#endif
        pbuf_cat(ipr->p, next_pbuf);
      }
      else {
        iprh_tmp = NULL;
      }

      iprh = iprh_tmp;
    }

    /* Get the first pbuf. */
    p = ipr->p;

#if IPV6_FRAG_COPYHEADER
    if (IPV6_FRAG_REQROOM > 0) {
      u8_t hdrerr;
      /* Restore (only) the bytes that we overwrote beyond the fragment header.
       * Those bytes may belong to either the IPv6 header or an extension
       * header placed before the fragment header. */
      MEMCPY(p->payload, ipr->orig_hdr, IPV6_FRAG_REQROOM);
      /* get back room for struct ip6_reass_helper (only required if sizeof(void*) > 4) */
      hdrerr = pbuf_remove_header(p, IPV6_FRAG_REQROOM);
      LWIP_UNUSED_ARG(hdrerr); /* in case of LWIP_NOASSERT */
      LWIP_ASSERT("no room for struct ip6_reass_helper", hdrerr == 0);
    }
#endif

    /* We need to get rid of the fragment header itself, which is somewhere in
     * the middle of the packet (but still in the first pbuf of the chain).
     * Getting rid of the header is required by RFC 2460 Sec. 4.5 and necessary
     * in order to be able to reassemble packets that are close to full size
     * (i.e., around 65535 bytes). We simply move up all the headers before the
     * fragment header, including the IPv6 header, and adjust the payload start
     * accordingly. This works because all these headers are in the first pbuf
     * of the chain, and because the caller adjusts all its pointers on
     * successful reassembly. */
    MEMMOVE((u8_t*)ipr->iphdr + sizeof(struct ip6_frag_hdr), ipr->iphdr,
      (size_t)((u8_t*)p->payload - (u8_t*)ipr->iphdr));

    /* This is where the IPv6 header is now. */
    iphdr_ptr = (struct ip6_hdr*)((u8_t*)ipr->iphdr +
      sizeof(struct ip6_frag_hdr));

    /* Adjust datagram length by adding header lengths. */
    ipr->datagram_len = (u16_t)(ipr->datagram_len + ((u8_t*)p->payload - (u8_t*)iphdr_ptr)
                         - IP6_HLEN);

    /* Set payload length in ip header. */
    iphdr_ptr->_plen = lwip_htons(ipr->datagram_len);

    /* With the fragment header gone, we now need to adjust the next-header
     * field of whatever header was originally before it. Since the packet made
     * it through the original header processing routines at least up to the
     * fragment header, we do not need any further sanity checks here. */
    if (IP6H_NEXTH(iphdr_ptr) == IP6_NEXTH_FRAGMENT) {
      iphdr_ptr->_nexth = ipr->nexth;
    } else {
      u8_t *ptr = (u8_t *)iphdr_ptr + IP6_HLEN;
      while (*ptr != IP6_NEXTH_FRAGMENT) {
        ptr += 8 * (1 + ptr[1]);
      }
      *ptr = ipr->nexth;
    }

    /* release the resources allocated for the fragment queue entry */
    if (reassdatagrams == ipr) {
      /* it was the first in the list */
      reassdatagrams = ipr->next;
    } else {
      /* it wasn't the first, so it must have a valid 'prev' */
      LWIP_ASSERT("sanity check linked list", ipr_prev != NULL);
      ipr_prev->next = ipr->next;
    }
    memp_free(MEMP_IP6_REASSDATA, ipr);

    /* adjust the number of pbufs currently queued for reassembly. */
    clen = pbuf_clen(p);
    LWIP_ASSERT("ip6_reass_pbufcount >= clen", ip6_reass_pbufcount >= clen);
    ip6_reass_pbufcount = (u16_t)(ip6_reass_pbufcount - clen);

    /* Move pbuf back to IPv6 header. This should never fail. */
    if (pbuf_header_force(p, (s16_t)((u8_t*)p->payload - (u8_t*)iphdr_ptr))) {
      LWIP_ASSERT("ip6_reass: moving p->payload to ip6 header failed\n", 0);
      pbuf_free(p);
      return NULL;
    }

    /* Return the pbuf chain */
    return p;
  }
  /* the datagram is not (yet?) reassembled completely */
  return NULL;

nullreturn:
  IP6_FRAG_STATS_INC(ip6_frag.drop);
  pbuf_free(p);
  return NULL;
}