#include "lwip/opt.h"
#include "lwip/ip6_frag.h"
#include "lwip/ip6.h"
#include "lwip/icmp6.h"
#include "lwip/nd6.h"
#include "lwip/ip.h"
#include "lwip/pbuf.h"
#include "lwip/memp.h"
#include "lwip/stats.h"
#include <string.h>
#if LWIP_IPV6 && LWIP_IPV6_REASS
#ifndef IP_REASS_CHECK_OVERLAP
#define IP_REASS_CHECK_OVERLAP 1
#endif
#ifndef IP_REASS_FREE_OLDEST
#define IP_REASS_FREE_OLDEST 1
#endif
#if IPV6_FRAG_COPYHEADER
#define IPV6_FRAG_REQROOM ((s16_t)(sizeof(struct ip6_reass_helper) - IP6_FRAG_HLEN))
#endif
#define IP_REASS_FLAG_LASTFRAG 0x01
#ifdef PACK_STRUCT_USE_INCLUDES
# include "arch/bpstruct.h"
#endif
PACK_STRUCT_BEGIN
struct ip6_reass_helper {
PACK_STRUCT_FIELD(struct pbuf *next_pbuf);
PACK_STRUCT_FIELD(u16_t start);
PACK_STRUCT_FIELD(u16_t end);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#ifdef PACK_STRUCT_USE_INCLUDES
# include "arch/epstruct.h"
#endif
static struct ip6_reassdata *reassdatagrams;
static u16_t ip6_reass_pbufcount;
static void ip6_reass_free_complete_datagram(struct ip6_reassdata *ipr);
#if IP_REASS_FREE_OLDEST
static void ip6_reass_remove_oldest_datagram(struct ip6_reassdata *ipr, int pbufs_needed);
#endif
void
ip6_reass_tmr(void)
{
struct ip6_reassdata *r, *tmp;
#if !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
r = reassdatagrams;
while (r != NULL) {
if (r->timer > 0) {
r->timer--;
r = r->next;
} else {
tmp = r;
r = r->next;
ip6_reass_free_complete_datagram(tmp);
}
}
}
static void
ip6_reass_free_complete_datagram(struct ip6_reassdata *ipr)
{
struct ip6_reassdata *prev;
u16_t pbufs_freed = 0;
u16_t clen;
struct pbuf *p;
struct ip6_reass_helper *iprh;
#if LWIP_ICMP6
iprh = (struct ip6_reass_helper *)ipr->p->payload;
if (iprh->start == 0) {
p = ipr->p;
ipr->p = iprh->next_pbuf;
MEMCPY(p->payload, ipr->orig_hdr, sizeof(*iprh));
if (pbuf_header_force(p, (s16_t)((u8_t*)p->payload - (u8_t*)ipr->iphdr))) {
LWIP_ASSERT("ip6_reass_free: moving p->payload to ip6 header failed", 0);
}
else {
ip6_addr_t src_addr, dest_addr;
ip6_addr_copy_from_packed(src_addr, IPV6_FRAG_SRC(ipr));
ip6_addr_set_zone(&src_addr, ipr->src_zone);
ip6_addr_copy_from_packed(dest_addr, IPV6_FRAG_DEST(ipr));
ip6_addr_set_zone(&dest_addr, ipr->dest_zone);
icmp6_time_exceeded_with_addrs(p, ICMP6_TE_FRAG, &src_addr, &dest_addr);
}
clen = pbuf_clen(p);
LWIP_ASSERT("pbufs_freed + clen <= 0xffff", pbufs_freed + clen <= 0xffff);
pbufs_freed = (u16_t)(pbufs_freed + clen);
pbuf_free(p);
}
#endif
p = ipr->p;
while (p != NULL) {
struct pbuf *pcur;
iprh = (struct ip6_reass_helper *)p->payload;
pcur = p;
p = iprh->next_pbuf;
clen = pbuf_clen(pcur);
LWIP_ASSERT("pbufs_freed + clen <= 0xffff", pbufs_freed + clen <= 0xffff);
pbufs_freed = (u16_t)(pbufs_freed + clen);
pbuf_free(pcur);
}
if (ipr == reassdatagrams) {
reassdatagrams = ipr->next;
} else {
prev = reassdatagrams;
while (prev != NULL) {
if (prev->next == ipr) {
break;
}
prev = prev->next;
}
if (prev != NULL) {
prev->next = ipr->next;
}
}
memp_free(MEMP_IP6_REASSDATA, ipr);
LWIP_ASSERT("ip_reass_pbufcount >= clen", ip6_reass_pbufcount >= pbufs_freed);
ip6_reass_pbufcount = (u16_t)(ip6_reass_pbufcount - pbufs_freed);
}
#if IP_REASS_FREE_OLDEST
static void
ip6_reass_remove_oldest_datagram(struct ip6_reassdata *ipr, int pbufs_needed)
{
struct ip6_reassdata *r, *oldest;
do {
r = oldest = reassdatagrams;
while (r != NULL) {
if (r != ipr) {
if (r->timer <= oldest->timer) {
oldest = r;
}
}
r = r->next;
}
if (oldest == ipr) {
return;
}
if (oldest != NULL) {
ip6_reass_free_complete_datagram(oldest);
}
} while (((ip6_reass_pbufcount + pbufs_needed) > IP_REASS_MAX_PBUFS) && (reassdatagrams != NULL));
}
#endif
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);
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);
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)) {
IP6_FRAG_STATS_INC(ip6_frag.proterr);
goto nullreturn;
}
for (ipr = reassdatagrams, ipr_prev = NULL; ipr != NULL; ipr = ipr->next) {
if ((frag_hdr->_identification == ipr->identification) &&
ip6_addr_packed_eq(ip6_current_src_addr(), &(IPV6_FRAG_SRC(ipr)), ipr->src_zone) &&
ip6_addr_packed_eq(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) {
ipr = (struct ip6_reassdata *)memp_malloc(MEMP_IP6_REASSDATA);
if (ipr == NULL) {
#if IP_REASS_FREE_OLDEST
ip6_reass_remove_oldest_datagram(ipr, clen);
ipr = (struct ip6_reassdata *)memp_malloc(MEMP_IP6_REASSDATA);
if (ipr != NULL) {
for (ipr_prev = reassdatagrams; ipr_prev != NULL; ipr_prev = ipr_prev->next) {
if (ipr_prev->next == ipr) {
break;
}
}
} else
#endif
{
IP6_FRAG_STATS_INC(ip6_frag.memerr);
goto nullreturn;
}
}
memset(ipr, 0, sizeof(struct ip6_reassdata));
ipr->timer = IPV6_REASS_MAXAGE;
ipr->next = reassdatagrams;
reassdatagrams = ipr;
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
#if LWIP_IPV6_SCOPES
ipr->src_zone = ip6_addr_zone(ip6_current_src_addr());
ipr->dest_zone = ip6_addr_zone(ip6_current_dest_addr());
#endif
ipr->identification = frag_hdr->_identification;
ipr->nexth = frag_hdr->_nexth;
}
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) {
for (ipr_prev = reassdatagrams; ipr_prev != NULL; ipr_prev = ipr_prev->next) {
if (ipr_prev->next == ipr) {
break;
}
}
} else
#endif
{
IP6_FRAG_STATS_INC(ip6_frag.memerr);
goto nullreturn;
}
}
#if IPV6_FRAG_COPYHEADER
if (IPV6_FRAG_REQROOM > 0) {
u8_t hdrerr = pbuf_header_force(p, IPV6_FRAG_REQROOM);
LWIP_UNUSED_ARG(hdrerr);
LWIP_ASSERT("no room for struct ip6_reass_helper", hdrerr == 0);
}
#else
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
iprh = (struct ip6_reass_helper *)p->payload;
next_pbuf = NULL;
end = (u16_t)(start + len);
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) {
IP6_FRAG_STATS_INC(ip6_frag.proterr);
goto nullreturn;
}
if (iprh_prev != NULL) {
if (start < iprh_prev->end) {
IP6_FRAG_STATS_INC(ip6_frag.proterr);
goto nullreturn;
}
}
#endif
if (iprh_prev != NULL) {
if (iprh_prev->end != start) {
valid = 0;
}
}
if (end != iprh_tmp->start) {
valid = 0;
}
next_pbuf = q;
if (iprh_prev != NULL) {
iprh_prev->next_pbuf = p;
} else {
ipr->p = p;
}
break;
} else if (start == iprh_tmp->start) {
goto nullreturn;
#if IP_REASS_CHECK_OVERLAP
} else if (start < iprh_tmp->end) {
IP6_FRAG_STATS_INC(ip6_frag.proterr);
goto nullreturn;
#endif
} else {
if (iprh_prev != NULL) {
if (iprh_prev->end != iprh_tmp->start) {
valid = 0;
}
}
}
q = iprh_tmp->next_pbuf;
iprh_prev = iprh_tmp;
}
if (q == NULL) {
if (iprh_prev != NULL) {
#if IP_REASS_CHECK_OVERLAP
LWIP_ASSERT("check fragments don't overlap", iprh_prev->end <= start);
#endif
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
ipr->p = p;
}
}
ip6_reass_pbufcount = (u16_t)(ip6_reass_pbufcount + clen);
if (start == 0) {
ipr->iphdr = ip_data.current_ip6_header;
MEMCPY(ipr->orig_hdr, p->payload, sizeof(*iprh));
}
iprh->next_pbuf = next_pbuf;
iprh->start = start;
iprh->end = end;
if ((offset & IP6_FRAG_MORE_FLAG) == 0) {
ipr->datagram_len = iprh->end;
}
iprh_tmp = (struct ip6_reass_helper*)ipr->p->payload;
if (iprh_tmp->start != 0) {
valid = 0;
}
if (ipr->datagram_len == 0) {
valid = 0;
}
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) {
struct ip6_hdr* iphdr_ptr;
iprh = (struct ip6_reass_helper*) ipr->p->payload;
while (iprh != NULL) {
next_pbuf = iprh->next_pbuf;
if (next_pbuf != NULL) {
iprh_tmp = (struct ip6_reass_helper*)next_pbuf->payload;
pbuf_remove_header(next_pbuf, IP6_FRAG_HLEN);
#if IPV6_FRAG_COPYHEADER
if (IPV6_FRAG_REQROOM > 0) {
u8_t hdrerr = pbuf_remove_header(next_pbuf, IPV6_FRAG_REQROOM);
LWIP_UNUSED_ARG(hdrerr);
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;
}
p = ipr->p;
#if IPV6_FRAG_COPYHEADER
if (IPV6_FRAG_REQROOM > 0) {
u8_t hdrerr;
MEMCPY(p->payload, ipr->orig_hdr, IPV6_FRAG_REQROOM);
hdrerr = pbuf_remove_header(p, IPV6_FRAG_REQROOM);
LWIP_UNUSED_ARG(hdrerr);
LWIP_ASSERT("no room for struct ip6_reass_helper", hdrerr == 0);
}
#endif
MEMMOVE((u8_t*)ipr->iphdr + sizeof(struct ip6_frag_hdr), ipr->iphdr,
(size_t)((u8_t*)p->payload - (u8_t*)ipr->iphdr));
iphdr_ptr = (struct ip6_hdr*)((u8_t*)ipr->iphdr +
sizeof(struct ip6_frag_hdr));
ipr->datagram_len = (u16_t)(ipr->datagram_len + ((u8_t*)p->payload - (u8_t*)iphdr_ptr)
- IP6_HLEN);
iphdr_ptr->_plen = lwip_htons(ipr->datagram_len);
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;
}
if (reassdatagrams == ipr) {
reassdatagrams = ipr->next;
} else {
LWIP_ASSERT("sanity check linked list", ipr_prev != NULL);
ipr_prev->next = ipr->next;
}
memp_free(MEMP_IP6_REASSDATA, ipr);
clen = pbuf_clen(p);
LWIP_ASSERT("ip6_reass_pbufcount >= clen", ip6_reass_pbufcount >= clen);
ip6_reass_pbufcount = (u16_t)(ip6_reass_pbufcount - clen);
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", 0);
pbuf_free(p);
return NULL;
}
MIB2_STATS_INC(mib2.ip6reasmoks);
return p;
}
return NULL;
nullreturn:
IP6_FRAG_STATS_INC(ip6_frag.drop);
pbuf_free(p);
return NULL;
}
#endif
#if LWIP_IPV6 && LWIP_IPV6_FRAG
#if !LWIP_NETIF_TX_SINGLE_PBUF
static struct pbuf_custom_ref*
ip6_frag_alloc_pbuf_custom_ref(void)
{
return (struct pbuf_custom_ref*)memp_malloc(MEMP_FRAG_PBUF);
}
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);
}
static void
ip6_frag_free_pbuf_custom(struct pbuf *p)
{
struct pbuf_custom_ref *pcr = (struct pbuf_custom_ref*)p;
LWIP_ASSERT("pcr != NULL", pcr != NULL);
LWIP_ASSERT("pcr == p", (void*)pcr == (void*)p);
if (pcr->original != NULL) {
pbuf_free(pcr->original);
}
ip6_frag_free_pbuf_custom_ref(pcr);
}
#endif
err_t
ip6_frag(struct pbuf *p, struct netif *netif, const ip6_addr_t *dest)
{
struct ip6_hdr *original_ip6hdr;
struct ip6_hdr *ip6hdr;
struct ip6_frag_hdr *frag_hdr;
struct pbuf *rambuf;
#if !LWIP_NETIF_TX_SINGLE_PBUF
struct pbuf *newpbuf;
u16_t newpbuflen = 0;
u16_t left_to_copy;
#endif
static u32_t identification;
u16_t left, cop;
const u16_t mtu = nd6_get_destination_mtu(dest, netif);
const u16_t nfb = (u16_t)((mtu - (IP6_HLEN + IP6_FRAG_HLEN)) & IP6_FRAG_OFFSET_MASK);
u16_t fragment_offset = 0;
u16_t last;
u16_t poff = IP6_HLEN;
identification++;
original_ip6hdr = (struct ip6_hdr *)p->payload;
LWIP_ASSERT("p->tot_len >= IP6_HLEN", p->tot_len >= IP6_HLEN);
left = (u16_t)(p->tot_len - IP6_HLEN);
while (left) {
last = (left <= nfb);
cop = last ? left : nfb;
#if LWIP_NETIF_TX_SINGLE_PBUF
rambuf = pbuf_alloc(PBUF_IP, cop + IP6_FRAG_HLEN, PBUF_RAM);
if (rambuf == NULL) {
IP6_FRAG_STATS_INC(ip6_frag.memerr);
return ERR_MEM;
}
LWIP_ASSERT("this needs a pbuf in one piece!",
(rambuf->len == rambuf->tot_len) && (rambuf->next == NULL));
poff += pbuf_copy_partial(p, (u8_t*)rambuf->payload + IP6_FRAG_HLEN, cop, poff);
if (pbuf_add_header(rambuf, IP6_HLEN)) {
pbuf_free(rambuf);
IP6_FRAG_STATS_INC(ip6_frag.memerr);
return ERR_MEM;
}
SMEMCPY(rambuf->payload, original_ip6hdr, IP6_HLEN);
ip6hdr = (struct ip6_hdr *)rambuf->payload;
frag_hdr = (struct ip6_frag_hdr *)((u8_t*)rambuf->payload + IP6_HLEN);
#else
rambuf = pbuf_alloc(PBUF_LINK, IP6_HLEN + IP6_FRAG_HLEN, PBUF_RAM);
if (rambuf == NULL) {
IP6_FRAG_STATS_INC(ip6_frag.memerr);
return ERR_MEM;
}
LWIP_ASSERT("this needs a pbuf in one piece!",
(rambuf->len >= (IP6_HLEN)));
SMEMCPY(rambuf->payload, original_ip6hdr, IP6_HLEN);
ip6hdr = (struct ip6_hdr *)rambuf->payload;
frag_hdr = (struct ip6_frag_hdr *)((u8_t*)rambuf->payload + IP6_HLEN);
p->payload = (u8_t *)p->payload + poff;
p->len = (u16_t)(p->len - poff);
p->tot_len = (u16_t)(p->tot_len - poff);
left_to_copy = cop;
while (left_to_copy) {
struct pbuf_custom_ref *pcr;
newpbuflen = (left_to_copy < p->len) ? left_to_copy : p->len;
if (!newpbuflen) {
p = p->next;
continue;
}
pcr = ip6_frag_alloc_pbuf_custom_ref();
if (pcr == NULL) {
pbuf_free(rambuf);
IP6_FRAG_STATS_INC(ip6_frag.memerr);
return ERR_MEM;
}
newpbuf = pbuf_alloced_custom(PBUF_RAW, newpbuflen, PBUF_REF, &pcr->pc, p->payload, newpbuflen);
if (newpbuf == NULL) {
ip6_frag_free_pbuf_custom_ref(pcr);
pbuf_free(rambuf);
IP6_FRAG_STATS_INC(ip6_frag.memerr);
return ERR_MEM;
}
pbuf_ref(p);
pcr->original = p;
pcr->pc.custom_free_function = ip6_frag_free_pbuf_custom;
pbuf_cat(rambuf, newpbuf);
left_to_copy = (u16_t)(left_to_copy - newpbuflen);
if (left_to_copy) {
p = p->next;
}
}
poff = newpbuflen;
#endif
frag_hdr->_nexth = original_ip6hdr->_nexth;
frag_hdr->reserved = 0;
frag_hdr->_fragment_offset = lwip_htons((u16_t)((fragment_offset & IP6_FRAG_OFFSET_MASK) | (last ? 0 : IP6_FRAG_MORE_FLAG)));
frag_hdr->_identification = lwip_htonl(identification);
IP6H_NEXTH_SET(ip6hdr, IP6_NEXTH_FRAGMENT);
IP6H_PLEN_SET(ip6hdr, (u16_t)(cop + IP6_FRAG_HLEN));
IP6_FRAG_STATS_INC(ip6_frag.xmit);
netif->output_ip6(netif, rambuf, dest);
pbuf_free(rambuf);
left = (u16_t)(left - cop);
fragment_offset = (u16_t)(fragment_offset + cop);
}
return ERR_OK;
}
#endif