Travis Geiselbrecht | 1d0df69 | 2008-09-01 02:26:09 -0700 | [diff] [blame^] | 1 | /* @file |
| 2 | * |
| 3 | * This is the IP packet segmentation and reassembly implementation. |
| 4 | * |
| 5 | */ |
| 6 | |
| 7 | /* |
| 8 | * Copyright (c) 2001-2004 Swedish Institute of Computer Science. |
| 9 | * All rights reserved. |
| 10 | * |
| 11 | * Redistribution and use in source and binary forms, with or without modification, |
| 12 | * are permitted provided that the following conditions are met: |
| 13 | * |
| 14 | * 1. Redistributions of source code must retain the above copyright notice, |
| 15 | * this list of conditions and the following disclaimer. |
| 16 | * 2. Redistributions in binary form must reproduce the above copyright notice, |
| 17 | * this list of conditions and the following disclaimer in the documentation |
| 18 | * and/or other materials provided with the distribution. |
| 19 | * 3. The name of the author may not be used to endorse or promote products |
| 20 | * derived from this software without specific prior written permission. |
| 21 | * |
| 22 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| 23 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| 24 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT |
| 25 | * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 26 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT |
| 27 | * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 28 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING |
| 30 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY |
| 31 | * OF SUCH DAMAGE. |
| 32 | * |
| 33 | * This file is part of the lwIP TCP/IP stack. |
| 34 | * |
| 35 | * Author: Jani Monoses <jani@iv.ro> |
| 36 | * original reassembly code by Adam Dunkels <adam@sics.se> |
| 37 | * |
| 38 | */ |
| 39 | |
| 40 | #include <string.h> |
| 41 | |
| 42 | #include "lwip/opt.h" |
| 43 | /* #include "lwip/sys.h" */ |
| 44 | #include "lwip/ip.h" |
| 45 | #include "lwip/ip_frag.h" |
| 46 | #include "lwip/netif.h" |
| 47 | #include "lwip/stats.h" |
| 48 | |
| 49 | |
| 50 | /* |
| 51 | * Copy len bytes from offset in pbuf to buffer |
| 52 | * |
| 53 | * helper used by both ip_reass and ip_frag |
| 54 | */ |
| 55 | static struct pbuf * |
| 56 | copy_from_pbuf(struct pbuf *p, u16_t * offset, |
| 57 | u8_t * buffer, u16_t len) |
| 58 | { |
| 59 | u16_t l; |
| 60 | |
| 61 | p->payload = (u8_t *)p->payload + *offset; |
| 62 | p->len -= *offset; |
| 63 | while (len) { |
| 64 | l = len < p->len ? len : p->len; |
| 65 | memcpy(buffer, p->payload, l); |
| 66 | buffer += l; |
| 67 | len -= l; |
| 68 | if (len) |
| 69 | p = p->next; |
| 70 | else |
| 71 | *offset = l; |
| 72 | } |
| 73 | return p; |
| 74 | } |
| 75 | |
| 76 | #define IP_REASS_BUFSIZE 5760 |
| 77 | #define IP_REASS_MAXAGE 30 |
| 78 | #define IP_REASS_TMO 1000 |
| 79 | |
| 80 | static u8_t ip_reassbuf[IP_HLEN + IP_REASS_BUFSIZE]; |
| 81 | static u8_t ip_reassbitmap[IP_REASS_BUFSIZE / (8 * 8) + 1]; |
| 82 | static const u8_t bitmap_bits[8] = { 0xff, 0x7f, 0x3f, 0x1f, |
| 83 | 0x0f, 0x07, 0x03, 0x01 |
| 84 | }; |
| 85 | static u16_t ip_reasslen; |
| 86 | static u8_t ip_reassflags; |
| 87 | #define IP_REASS_FLAG_LASTFRAG 0x01 |
| 88 | |
| 89 | static u8_t ip_reasstmr; |
| 90 | |
| 91 | /** |
| 92 | * Reassembly timer base function |
| 93 | * for both NO_SYS == 0 and 1 (!). |
| 94 | * |
| 95 | * Should be called every 1000 msec. |
| 96 | */ |
| 97 | void |
| 98 | ip_reass_tmr(void) |
| 99 | { |
| 100 | if (ip_reasstmr > 0) { |
| 101 | ip_reasstmr--; |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | /** |
| 106 | * Reassembles incoming IP fragments into an IP datagram. |
| 107 | * |
| 108 | * @param p points to a pbuf chain of the fragment |
| 109 | * @return NULL if reassembly is incomplete, ? otherwise |
| 110 | */ |
| 111 | struct pbuf * |
| 112 | ip_reass(struct pbuf *p) |
| 113 | { |
| 114 | struct pbuf *q; |
| 115 | struct ip_hdr *fraghdr, *iphdr; |
| 116 | u16_t offset, len; |
| 117 | u16_t i; |
| 118 | |
| 119 | IPFRAG_STATS_INC(ip_frag.recv); |
| 120 | |
| 121 | iphdr = (struct ip_hdr *) ip_reassbuf; |
| 122 | fraghdr = (struct ip_hdr *) p->payload; |
| 123 | /* If ip_reasstmr is zero, no packet is present in the buffer, so we |
| 124 | write the IP header of the fragment into the reassembly |
| 125 | buffer. The timer is updated with the maximum age. */ |
| 126 | if (ip_reasstmr == 0) { |
| 127 | LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass: new packet\n")); |
| 128 | memcpy(iphdr, fraghdr, IP_HLEN); |
| 129 | ip_reasstmr = IP_REASS_MAXAGE; |
| 130 | ip_reassflags = 0; |
| 131 | /* Clear the bitmap. */ |
| 132 | memset(ip_reassbitmap, 0, sizeof(ip_reassbitmap)); |
| 133 | } |
| 134 | |
| 135 | /* Check if the incoming fragment matches the one currently present |
| 136 | in the reasembly buffer. If so, we proceed with copying the |
| 137 | fragment into the buffer. */ |
| 138 | if (ip_addr_cmp(&iphdr->src, &fraghdr->src) && |
| 139 | ip_addr_cmp(&iphdr->dest, &fraghdr->dest) && |
| 140 | IPH_ID(iphdr) == IPH_ID(fraghdr)) { |
| 141 | LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass: matching previous fragment ID=%"X16_F"\n", |
| 142 | ntohs(IPH_ID(fraghdr)))); |
| 143 | IPFRAG_STATS_INC(ip_frag.cachehit); |
| 144 | /* Find out the offset in the reassembly buffer where we should |
| 145 | copy the fragment. */ |
| 146 | len = ntohs(IPH_LEN(fraghdr)) - IPH_HL(fraghdr) * 4; |
| 147 | offset = (ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) * 8; |
| 148 | |
| 149 | /* If the offset or the offset + fragment length overflows the |
| 150 | reassembly buffer, we discard the entire packet. */ |
| 151 | if (offset > IP_REASS_BUFSIZE || offset + len > IP_REASS_BUFSIZE) { |
| 152 | LWIP_DEBUGF(IP_REASS_DEBUG, |
| 153 | ("ip_reass: fragment outside of buffer (%"S16_F":%"S16_F"/%"S16_F").\n", offset, |
| 154 | offset + len, IP_REASS_BUFSIZE)); |
| 155 | ip_reasstmr = 0; |
| 156 | goto nullreturn; |
| 157 | } |
| 158 | |
| 159 | /* Copy the fragment into the reassembly buffer, at the right |
| 160 | offset. */ |
| 161 | LWIP_DEBUGF(IP_REASS_DEBUG, |
| 162 | ("ip_reass: copying with offset %"S16_F" into %"S16_F":%"S16_F"\n", offset, |
| 163 | IP_HLEN + offset, IP_HLEN + offset + len)); |
| 164 | i = IPH_HL(fraghdr) * 4; |
| 165 | copy_from_pbuf(p, &i, &ip_reassbuf[IP_HLEN + offset], len); |
| 166 | |
| 167 | /* Update the bitmap. */ |
| 168 | if (offset / (8 * 8) == (offset + len) / (8 * 8)) { |
| 169 | LWIP_DEBUGF(IP_REASS_DEBUG, |
| 170 | ("ip_reass: updating single byte in bitmap.\n")); |
| 171 | /* If the two endpoints are in the same byte, we only update that byte. */ |
| 172 | LWIP_ASSERT("offset / (8 * 8) < sizeof(ip_reassbitmap)", |
| 173 | offset / (8 * 8) < sizeof(ip_reassbitmap)); |
| 174 | ip_reassbitmap[offset / (8 * 8)] |= |
| 175 | bitmap_bits[(offset / 8) & 7] & |
| 176 | ~bitmap_bits[((offset + len) / 8) & 7]; |
| 177 | } else { |
| 178 | /* If the two endpoints are in different bytes, we update the |
| 179 | bytes in the endpoints and fill the stuff inbetween with |
| 180 | 0xff. */ |
| 181 | LWIP_ASSERT("offset / (8 * 8) < sizeof(ip_reassbitmap)", |
| 182 | offset / (8 * 8) < sizeof(ip_reassbitmap)); |
| 183 | ip_reassbitmap[offset / (8 * 8)] |= bitmap_bits[(offset / 8) & 7]; |
| 184 | LWIP_DEBUGF(IP_REASS_DEBUG, |
| 185 | ("ip_reass: updating many bytes in bitmap (%"S16_F":%"S16_F").\n", |
| 186 | 1 + offset / (8 * 8), (offset + len) / (8 * 8))); |
| 187 | for (i = 1 + offset / (8 * 8); i < (offset + len) / (8 * 8); ++i) { |
| 188 | ip_reassbitmap[i] = 0xff; |
| 189 | } |
| 190 | LWIP_ASSERT("(offset + len) / (8 * 8) < sizeof(ip_reassbitmap)", |
| 191 | (offset + len) / (8 * 8) < sizeof(ip_reassbitmap)); |
| 192 | ip_reassbitmap[(offset + len) / (8 * 8)] |= |
| 193 | ~bitmap_bits[((offset + len) / 8) & 7]; |
| 194 | } |
| 195 | |
| 196 | /* If this fragment has the More Fragments flag set to zero, we |
| 197 | know that this is the last fragment, so we can calculate the |
| 198 | size of the entire packet. We also set the |
| 199 | IP_REASS_FLAG_LASTFRAG flag to indicate that we have received |
| 200 | the final fragment. */ |
| 201 | |
| 202 | if ((ntohs(IPH_OFFSET(fraghdr)) & IP_MF) == 0) { |
| 203 | ip_reassflags |= IP_REASS_FLAG_LASTFRAG; |
| 204 | ip_reasslen = offset + len; |
| 205 | LWIP_DEBUGF(IP_REASS_DEBUG, |
| 206 | ("ip_reass: last fragment seen, total len %"S16_F"\n", |
| 207 | ip_reasslen)); |
| 208 | } |
| 209 | |
| 210 | /* Finally, we check if we have a full packet in the buffer. We do |
| 211 | this by checking if we have the last fragment and if all bits |
| 212 | in the bitmap are set. */ |
| 213 | if (ip_reassflags & IP_REASS_FLAG_LASTFRAG) { |
| 214 | /* Check all bytes up to and including all but the last byte in |
| 215 | the bitmap. */ |
| 216 | LWIP_ASSERT("ip_reasslen / (8 * 8) - 1 < sizeof(ip_reassbitmap)", |
| 217 | ip_reasslen / (8 * 8) - 1 < sizeof(ip_reassbitmap)); |
| 218 | for (i = 0; i < ip_reasslen / (8 * 8) - 1; ++i) { |
| 219 | if (ip_reassbitmap[i] != 0xff) { |
| 220 | LWIP_DEBUGF(IP_REASS_DEBUG, |
| 221 | ("ip_reass: last fragment seen, bitmap %"S16_F"/%"S16_F" failed (%"X16_F")\n", |
| 222 | i, ip_reasslen / (8 * 8) - 1, ip_reassbitmap[i])); |
| 223 | goto nullreturn; |
| 224 | } |
| 225 | } |
| 226 | /* Check the last byte in the bitmap. It should contain just the |
| 227 | right amount of bits. */ |
| 228 | LWIP_ASSERT("ip_reasslen / (8 * 8) < sizeof(ip_reassbitmap)", |
| 229 | ip_reasslen / (8 * 8) < sizeof(ip_reassbitmap)); |
| 230 | if (ip_reassbitmap[ip_reasslen / (8 * 8)] != |
| 231 | (u8_t) ~ bitmap_bits[ip_reasslen / 8 & 7]) { |
| 232 | LWIP_DEBUGF(IP_REASS_DEBUG, |
| 233 | ("ip_reass: last fragment seen, bitmap %"S16_F" didn't contain %"X16_F" (%"X16_F")\n", |
| 234 | ip_reasslen / (8 * 8), ~bitmap_bits[ip_reasslen / 8 & 7], |
| 235 | ip_reassbitmap[ip_reasslen / (8 * 8)])); |
| 236 | goto nullreturn; |
| 237 | } |
| 238 | |
| 239 | /* Pretend to be a "normal" (i.e., not fragmented) IP packet |
| 240 | from now on. */ |
| 241 | ip_reasslen += IP_HLEN; |
| 242 | |
| 243 | IPH_LEN_SET(iphdr, htons(ip_reasslen)); |
| 244 | IPH_OFFSET_SET(iphdr, 0); |
| 245 | IPH_CHKSUM_SET(iphdr, 0); |
| 246 | IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN)); |
| 247 | |
| 248 | /* If we have come this far, we have a full packet in the |
| 249 | buffer, so we allocate a pbuf and copy the packet into it. We |
| 250 | also reset the timer. */ |
| 251 | ip_reasstmr = 0; |
| 252 | pbuf_free(p); |
| 253 | p = pbuf_alloc(PBUF_LINK, ip_reasslen, PBUF_POOL); |
| 254 | if (p != NULL) { |
| 255 | i = 0; |
| 256 | for (q = p; q != NULL; q = q->next) { |
| 257 | /* Copy enough bytes to fill this pbuf in the chain. The |
| 258 | available data in the pbuf is given by the q->len variable. */ |
| 259 | LWIP_DEBUGF(IP_REASS_DEBUG, |
| 260 | ("ip_reass: memcpy from %p (%"S16_F") to %p, %"S16_F" bytes\n", |
| 261 | (void *)&ip_reassbuf[i], i, q->payload, |
| 262 | q->len > ip_reasslen - i ? ip_reasslen - i : q->len)); |
| 263 | memcpy(q->payload, &ip_reassbuf[i], |
| 264 | q->len > ip_reasslen - i ? ip_reasslen - i : q->len); |
| 265 | i += q->len; |
| 266 | } |
| 267 | IPFRAG_STATS_INC(ip_frag.fw); |
| 268 | } else { |
| 269 | IPFRAG_STATS_INC(ip_frag.memerr); |
| 270 | } |
| 271 | LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass: p %p\n", (void*)p)); |
| 272 | return p; |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | nullreturn: |
| 277 | IPFRAG_STATS_INC(ip_frag.drop); |
| 278 | pbuf_free(p); |
| 279 | return NULL; |
| 280 | } |
| 281 | |
| 282 | #define MAX_MTU 1500 |
| 283 | static u8_t buf[MEM_ALIGN_SIZE(MAX_MTU)]; |
| 284 | |
| 285 | /** |
| 286 | * Fragment an IP datagram if too large for the netif. |
| 287 | * |
| 288 | * Chop the datagram in MTU sized chunks and send them in order |
| 289 | * by using a fixed size static memory buffer (PBUF_ROM) |
| 290 | */ |
| 291 | err_t |
| 292 | ip_frag(struct pbuf *p, struct netif *netif, struct ip_addr *dest) |
| 293 | { |
| 294 | struct pbuf *rambuf; |
| 295 | struct pbuf *header; |
| 296 | struct ip_hdr *iphdr; |
| 297 | u16_t nfb = 0; |
| 298 | u16_t left, cop; |
| 299 | u16_t mtu = netif->mtu; |
| 300 | u16_t ofo, omf; |
| 301 | u16_t last; |
| 302 | u16_t poff = IP_HLEN; |
| 303 | u16_t tmp; |
| 304 | |
| 305 | /* Get a RAM based MTU sized pbuf */ |
| 306 | rambuf = pbuf_alloc(PBUF_LINK, 0, PBUF_REF); |
| 307 | if (rambuf == NULL) { |
| 308 | return ERR_MEM; |
| 309 | } |
| 310 | rambuf->tot_len = rambuf->len = mtu; |
| 311 | rambuf->payload = MEM_ALIGN((void *)buf); |
| 312 | |
| 313 | /* Copy the IP header in it */ |
| 314 | iphdr = rambuf->payload; |
| 315 | memcpy(iphdr, p->payload, IP_HLEN); |
| 316 | |
| 317 | /* Save original offset */ |
| 318 | tmp = ntohs(IPH_OFFSET(iphdr)); |
| 319 | ofo = tmp & IP_OFFMASK; |
| 320 | omf = tmp & IP_MF; |
| 321 | |
| 322 | left = p->tot_len - IP_HLEN; |
| 323 | |
| 324 | while (left) { |
| 325 | last = (left <= mtu - IP_HLEN); |
| 326 | |
| 327 | /* Set new offset and MF flag */ |
| 328 | ofo += nfb; |
| 329 | tmp = omf | (IP_OFFMASK & (ofo)); |
| 330 | if (!last) |
| 331 | tmp = tmp | IP_MF; |
| 332 | IPH_OFFSET_SET(iphdr, htons(tmp)); |
| 333 | |
| 334 | /* Fill this fragment */ |
| 335 | nfb = (mtu - IP_HLEN) / 8; |
| 336 | cop = last ? left : nfb * 8; |
| 337 | |
| 338 | p = copy_from_pbuf(p, &poff, (u8_t *) iphdr + IP_HLEN, cop); |
| 339 | |
| 340 | /* Correct header */ |
| 341 | IPH_LEN_SET(iphdr, htons(cop + IP_HLEN)); |
| 342 | IPH_CHKSUM_SET(iphdr, 0); |
| 343 | IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN)); |
| 344 | |
| 345 | if (last) |
| 346 | pbuf_realloc(rambuf, left + IP_HLEN); |
| 347 | /* This part is ugly: we alloc a RAM based pbuf for |
| 348 | * the link level header for each chunk and then |
| 349 | * free it.A PBUF_ROM style pbuf for which pbuf_header |
| 350 | * worked would make things simpler. |
| 351 | */ |
| 352 | header = pbuf_alloc(PBUF_LINK, 0, PBUF_RAM); |
| 353 | if (header != NULL) { |
| 354 | pbuf_chain(header, rambuf); |
| 355 | netif->output(netif, header, dest); |
| 356 | IPFRAG_STATS_INC(ip_frag.xmit); |
| 357 | pbuf_free(header); |
| 358 | } else { |
| 359 | pbuf_free(rambuf); |
| 360 | return ERR_MEM; |
| 361 | } |
| 362 | left -= cop; |
| 363 | } |
| 364 | pbuf_free(rambuf); |
| 365 | return ERR_OK; |
| 366 | } |