]>
Commit | Line | Data |
---|---|---|
1 | /** | |
2 | * @file | |
3 | * This is the IPv4 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 | * Simon Goldschmidt | |
37 | * original reassembly code by Adam Dunkels <adam@sics.se> | |
38 | * | |
39 | */ | |
40 | ||
41 | #include "lwip/opt.h" | |
42 | #include "lwip/ip_frag.h" | |
43 | #include "lwip/ip.h" | |
44 | #include "lwip/inet.h" | |
45 | #include "lwip/inet_chksum.h" | |
46 | #include "lwip/netif.h" | |
47 | #include "lwip/snmp.h" | |
48 | #include "lwip/stats.h" | |
49 | #include "lwip/icmp.h" | |
50 | ||
51 | #include <string.h> | |
52 | ||
53 | #if IP_REASSEMBLY | |
54 | /** | |
55 | * The IP reassembly code currently has the following limitations: | |
56 | * - IP header options are not supported | |
57 | * - fragments must not overlap (e.g. due to different routes), | |
58 | * currently, overlapping or duplicate fragments are thrown away | |
59 | * if IP_REASS_CHECK_OVERLAP=1 (the default)! | |
60 | * | |
61 | * @todo: work with IP header options | |
62 | */ | |
63 | ||
64 | /** Setting this to 0, you can turn off checking the fragments for overlapping | |
65 | * regions. The code gets a little smaller. Only use this if you know that | |
66 | * overlapping won't occur on your network! */ | |
67 | #ifndef IP_REASS_CHECK_OVERLAP | |
68 | #define IP_REASS_CHECK_OVERLAP 1 | |
69 | #endif /* IP_REASS_CHECK_OVERLAP */ | |
70 | ||
71 | /** Set to 0 to prevent freeing the oldest datagram when the reassembly buffer is | |
72 | * full (IP_REASS_MAX_PBUFS pbufs are enqueued). The code gets a little smaller. | |
73 | * Datagrams will be freed by timeout only. Especially useful when MEMP_NUM_REASSDATA | |
74 | * is set to 1, so one datagram can be reassembled at a time, only. */ | |
75 | #ifndef IP_REASS_FREE_OLDEST | |
76 | #define IP_REASS_FREE_OLDEST 1 | |
77 | #endif /* IP_REASS_FREE_OLDEST */ | |
78 | ||
79 | #define IP_REASS_FLAG_LASTFRAG 0x01 | |
80 | ||
81 | /** This is a helper struct which holds the starting | |
82 | * offset and the ending offset of this fragment to | |
83 | * easily chain the fragments. | |
84 | */ | |
85 | struct ip_reass_helper { | |
86 | struct pbuf *next_pbuf; | |
87 | u16_t start; | |
88 | u16_t end; | |
89 | }; | |
90 | ||
91 | #define IP_ADDRESSES_AND_ID_MATCH(iphdrA, iphdrB) \ | |
92 | (ip_addr_cmp(&(iphdrA)->src, &(iphdrB)->src) && \ | |
93 | ip_addr_cmp(&(iphdrA)->dest, &(iphdrB)->dest) && \ | |
94 | IPH_ID(iphdrA) == IPH_ID(iphdrB)) ? 1 : 0 | |
95 | ||
96 | /* global variables */ | |
97 | static struct ip_reassdata *reassdatagrams; | |
98 | static u16_t ip_reass_pbufcount; | |
99 | ||
100 | /* function prototypes */ | |
101 | static void ip_reass_dequeue_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev); | |
102 | static int ip_reass_free_complete_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev); | |
103 | ||
104 | /** | |
105 | * Reassembly timer base function | |
106 | * for both NO_SYS == 0 and 1 (!). | |
107 | * | |
108 | * Should be called every 1000 msec (defined by IP_TMR_INTERVAL). | |
109 | */ | |
110 | void | |
111 | ip_reass_tmr(void) | |
112 | { | |
113 | struct ip_reassdata *r, *prev = NULL; | |
114 | ||
115 | r = reassdatagrams; | |
116 | while (r != NULL) { | |
117 | /* Decrement the timer. Once it reaches 0, | |
118 | * clean up the incomplete fragment assembly */ | |
119 | if (r->timer > 0) { | |
120 | r->timer--; | |
121 | LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass_tmr: timer dec %"U16_F"\n",(u16_t)r->timer)); | |
122 | prev = r; | |
123 | r = r->next; | |
124 | } else { | |
125 | /* reassembly timed out */ | |
126 | struct ip_reassdata *tmp; | |
127 | LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass_tmr: timer timed out\n")); | |
128 | tmp = r; | |
129 | /* get the next pointer before freeing */ | |
130 | r = r->next; | |
131 | /* free the helper struct and all enqueued pbufs */ | |
132 | ip_reass_free_complete_datagram(tmp, prev); | |
133 | } | |
134 | } | |
135 | } | |
136 | ||
137 | /** | |
138 | * Free a datagram (struct ip_reassdata) and all its pbufs. | |
139 | * Updates the total count of enqueued pbufs (ip_reass_pbufcount), | |
140 | * SNMP counters and sends an ICMP time exceeded packet. | |
141 | * | |
142 | * @param ipr datagram to free | |
143 | * @param prev the previous datagram in the linked list | |
144 | * @return the number of pbufs freed | |
145 | */ | |
146 | static int | |
147 | ip_reass_free_complete_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev) | |
148 | { | |
149 | int pbufs_freed = 0; | |
150 | struct pbuf *p; | |
151 | struct ip_reass_helper *iprh; | |
152 | ||
153 | LWIP_ASSERT("prev != ipr", prev != ipr); | |
154 | if (prev != NULL) { | |
155 | LWIP_ASSERT("prev->next == ipr", prev->next == ipr); | |
156 | } | |
157 | ||
158 | snmp_inc_ipreasmfails(); | |
159 | #if LWIP_ICMP | |
160 | iprh = (struct ip_reass_helper *)ipr->p->payload; | |
161 | if (iprh->start == 0) { | |
162 | /* The first fragment was received, send ICMP time exceeded. */ | |
163 | /* First, de-queue the first pbuf from r->p. */ | |
164 | p = ipr->p; | |
165 | ipr->p = iprh->next_pbuf; | |
166 | /* Then, copy the original header into it. */ | |
167 | SMEMCPY(p->payload, &ipr->iphdr, IP_HLEN); | |
168 | icmp_time_exceeded(p, ICMP_TE_FRAG); | |
169 | pbufs_freed += pbuf_clen(p); | |
170 | pbuf_free(p); | |
171 | } | |
172 | #endif /* LWIP_ICMP */ | |
173 | ||
174 | /* First, free all received pbufs. The individual pbufs need to be released | |
175 | separately as they have not yet been chained */ | |
176 | p = ipr->p; | |
177 | while (p != NULL) { | |
178 | struct pbuf *pcur; | |
179 | iprh = (struct ip_reass_helper *)p->payload; | |
180 | pcur = p; | |
181 | /* get the next pointer before freeing */ | |
182 | p = iprh->next_pbuf; | |
183 | pbufs_freed += pbuf_clen(pcur); | |
184 | pbuf_free(pcur); | |
185 | } | |
186 | /* Then, unchain the struct ip_reassdata from the list and free it. */ | |
187 | ip_reass_dequeue_datagram(ipr, prev); | |
188 | LWIP_ASSERT("ip_reass_pbufcount >= clen", ip_reass_pbufcount >= pbufs_freed); | |
189 | ip_reass_pbufcount -= pbufs_freed; | |
190 | ||
191 | return pbufs_freed; | |
192 | } | |
193 | ||
194 | #if IP_REASS_FREE_OLDEST | |
195 | /** | |
196 | * Free the oldest datagram to make room for enqueueing new fragments. | |
197 | * The datagram 'fraghdr' belongs to is not freed! | |
198 | * | |
199 | * @param fraghdr IP header of the current fragment | |
200 | * @param pbufs_needed number of pbufs needed to enqueue | |
201 | * (used for freeing other datagrams if not enough space) | |
202 | * @return the number of pbufs freed | |
203 | */ | |
204 | static int | |
205 | ip_reass_remove_oldest_datagram(struct ip_hdr *fraghdr, int pbufs_needed) | |
206 | { | |
207 | /* @todo Can't we simply remove the last datagram in the | |
208 | * linked list behind reassdatagrams? | |
209 | */ | |
210 | struct ip_reassdata *r, *oldest, *prev; | |
211 | int pbufs_freed = 0, pbufs_freed_current; | |
212 | int other_datagrams; | |
213 | ||
214 | /* Free datagrams until being allowed to enqueue 'pbufs_needed' pbufs, | |
215 | * but don't free the datagram that 'fraghdr' belongs to! */ | |
216 | do { | |
217 | oldest = NULL; | |
218 | prev = NULL; | |
219 | other_datagrams = 0; | |
220 | r = reassdatagrams; | |
221 | while (r != NULL) { | |
222 | if (!IP_ADDRESSES_AND_ID_MATCH(&r->iphdr, fraghdr)) { | |
223 | /* Not the same datagram as fraghdr */ | |
224 | other_datagrams++; | |
225 | if (oldest == NULL) { | |
226 | oldest = r; | |
227 | } else if (r->timer <= oldest->timer) { | |
228 | /* older than the previous oldest */ | |
229 | oldest = r; | |
230 | } | |
231 | } | |
232 | if (r->next != NULL) { | |
233 | prev = r; | |
234 | } | |
235 | r = r->next; | |
236 | } | |
237 | if (oldest != NULL) { | |
238 | pbufs_freed_current = ip_reass_free_complete_datagram(oldest, prev); | |
239 | pbufs_freed += pbufs_freed_current; | |
240 | } | |
241 | } while ((pbufs_freed < pbufs_needed) && (other_datagrams > 1)); | |
242 | return pbufs_freed; | |
243 | } | |
244 | #endif /* IP_REASS_FREE_OLDEST */ | |
245 | ||
246 | /** | |
247 | * Enqueues a new fragment into the fragment queue | |
248 | * @param fraghdr points to the new fragments IP hdr | |
249 | * @param clen number of pbufs needed to enqueue (used for freeing other datagrams if not enough space) | |
250 | * @return A pointer to the queue location into which the fragment was enqueued | |
251 | */ | |
252 | static struct ip_reassdata* | |
253 | ip_reass_enqueue_new_datagram(struct ip_hdr *fraghdr, int clen) | |
254 | { | |
255 | struct ip_reassdata* ipr; | |
256 | /* No matching previous fragment found, allocate a new reassdata struct */ | |
257 | ipr = memp_malloc(MEMP_REASSDATA); | |
258 | if (ipr == NULL) { | |
259 | #if IP_REASS_FREE_OLDEST | |
260 | if (ip_reass_remove_oldest_datagram(fraghdr, clen) >= clen) { | |
261 | ipr = memp_malloc(MEMP_REASSDATA); | |
262 | } | |
263 | if (ipr == NULL) | |
264 | #endif /* IP_REASS_FREE_OLDEST */ | |
265 | { | |
266 | IPFRAG_STATS_INC(ip_frag.memerr); | |
267 | LWIP_DEBUGF(IP_REASS_DEBUG,("Failed to alloc reassdata struct\n")); | |
268 | return NULL; | |
269 | } | |
270 | } | |
271 | memset(ipr, 0, sizeof(struct ip_reassdata)); | |
272 | ipr->timer = IP_REASS_MAXAGE; | |
273 | ||
274 | /* enqueue the new structure to the front of the list */ | |
275 | ipr->next = reassdatagrams; | |
276 | reassdatagrams = ipr; | |
277 | /* copy the ip header for later tests and input */ | |
278 | /* @todo: no ip options supported? */ | |
279 | SMEMCPY(&(ipr->iphdr), fraghdr, IP_HLEN); | |
280 | return ipr; | |
281 | } | |
282 | ||
283 | /** | |
284 | * Dequeues a datagram from the datagram queue. Doesn't deallocate the pbufs. | |
285 | * @param ipr points to the queue entry to dequeue | |
286 | */ | |
287 | static void | |
288 | ip_reass_dequeue_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev) | |
289 | { | |
290 | ||
291 | /* dequeue the reass struct */ | |
292 | if (reassdatagrams == ipr) { | |
293 | /* it was the first in the list */ | |
294 | reassdatagrams = ipr->next; | |
295 | } else { | |
296 | /* it wasn't the first, so it must have a valid 'prev' */ | |
297 | LWIP_ASSERT("sanity check linked list", prev != NULL); | |
298 | prev->next = ipr->next; | |
299 | } | |
300 | ||
301 | /* now we can free the ip_reass struct */ | |
302 | memp_free(MEMP_REASSDATA, ipr); | |
303 | } | |
304 | ||
305 | /** | |
306 | * Chain a new pbuf into the pbuf list that composes the datagram. The pbuf list | |
307 | * will grow over time as new pbufs are rx. | |
308 | * Also checks that the datagram passes basic continuity checks (if the last | |
309 | * fragment was received at least once). | |
310 | * @param root_p points to the 'root' pbuf for the current datagram being assembled. | |
311 | * @param new_p points to the pbuf for the current fragment | |
312 | * @return 0 if invalid, >0 otherwise | |
313 | */ | |
314 | static int | |
315 | ip_reass_chain_frag_into_datagram_and_validate(struct ip_reassdata *ipr, struct pbuf *new_p) | |
316 | { | |
317 | struct ip_reass_helper *iprh, *iprh_tmp, *iprh_prev=NULL; | |
318 | struct pbuf *q; | |
319 | u16_t offset,len; | |
320 | struct ip_hdr *fraghdr; | |
321 | int valid = 1; | |
322 | ||
323 | /* Extract length and fragment offset from current fragment */ | |
324 | fraghdr = (struct ip_hdr*)new_p->payload; | |
325 | len = ntohs(IPH_LEN(fraghdr)) - IPH_HL(fraghdr) * 4; | |
326 | offset = (ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) * 8; | |
327 | ||
328 | /* overwrite the fragment's ip header from the pbuf with our helper struct, | |
329 | * and setup the embedded helper structure. */ | |
330 | /* make sure the struct ip_reass_helper fits into the IP header */ | |
331 | LWIP_ASSERT("sizeof(struct ip_reass_helper) <= IP_HLEN", | |
332 | sizeof(struct ip_reass_helper) <= IP_HLEN); | |
333 | iprh = (struct ip_reass_helper*)new_p->payload; | |
334 | iprh->next_pbuf = NULL; | |
335 | iprh->start = offset; | |
336 | iprh->end = offset + len; | |
337 | ||
338 | /* Iterate through until we either get to the end of the list (append), | |
339 | * or we find on with a larger offset (insert). */ | |
340 | for (q = ipr->p; q != NULL;) { | |
341 | iprh_tmp = (struct ip_reass_helper*)q->payload; | |
342 | if (iprh->start < iprh_tmp->start) { | |
343 | /* the new pbuf should be inserted before this */ | |
344 | iprh->next_pbuf = q; | |
345 | if (iprh_prev != NULL) { | |
346 | /* not the fragment with the lowest offset */ | |
347 | #if IP_REASS_CHECK_OVERLAP | |
348 | if ((iprh->start < iprh_prev->end) || (iprh->end > iprh_tmp->start)) { | |
349 | /* fragment overlaps with previous or following, throw away */ | |
350 | goto freepbuf; | |
351 | } | |
352 | #endif /* IP_REASS_CHECK_OVERLAP */ | |
353 | iprh_prev->next_pbuf = new_p; | |
354 | } else { | |
355 | /* fragment with the lowest offset */ | |
356 | ipr->p = new_p; | |
357 | } | |
358 | break; | |
359 | } else if(iprh->start == iprh_tmp->start) { | |
360 | /* received the same datagram twice: no need to keep the datagram */ | |
361 | goto freepbuf; | |
362 | #if IP_REASS_CHECK_OVERLAP | |
363 | } else if(iprh->start < iprh_tmp->end) { | |
364 | /* overlap: no need to keep the new datagram */ | |
365 | goto freepbuf; | |
366 | #endif /* IP_REASS_CHECK_OVERLAP */ | |
367 | } else { | |
368 | /* Check if the fragments received so far have no wholes. */ | |
369 | if (iprh_prev != NULL) { | |
370 | if (iprh_prev->end != iprh_tmp->start) { | |
371 | /* There is a fragment missing between the current | |
372 | * and the previous fragment */ | |
373 | valid = 0; | |
374 | } | |
375 | } | |
376 | } | |
377 | q = iprh_tmp->next_pbuf; | |
378 | iprh_prev = iprh_tmp; | |
379 | } | |
380 | ||
381 | /* If q is NULL, then we made it to the end of the list. Determine what to do now */ | |
382 | if (q == NULL) { | |
383 | if (iprh_prev != NULL) { | |
384 | /* this is (for now), the fragment with the highest offset: | |
385 | * chain it to the last fragment */ | |
386 | #if IP_REASS_CHECK_OVERLAP | |
387 | LWIP_ASSERT("check fragments don't overlap", iprh_prev->end <= iprh->start); | |
388 | #endif /* IP_REASS_CHECK_OVERLAP */ | |
389 | iprh_prev->next_pbuf = new_p; | |
390 | if (iprh_prev->end != iprh->start) { | |
391 | valid = 0; | |
392 | } | |
393 | } else { | |
394 | #if IP_REASS_CHECK_OVERLAP | |
395 | LWIP_ASSERT("no previous fragment, this must be the first fragment!", | |
396 | ipr->p == NULL); | |
397 | #endif /* IP_REASS_CHECK_OVERLAP */ | |
398 | /* this is the first fragment we ever received for this ip datagram */ | |
399 | ipr->p = new_p; | |
400 | } | |
401 | } | |
402 | ||
403 | /* At this point, the validation part begins: */ | |
404 | /* If we already received the last fragment */ | |
405 | if ((ipr->flags & IP_REASS_FLAG_LASTFRAG) != 0) { | |
406 | /* and had no wholes so far */ | |
407 | if (valid) { | |
408 | /* then check if the rest of the fragments is here */ | |
409 | /* Check if the queue starts with the first datagram */ | |
410 | if (((struct ip_reass_helper*)ipr->p->payload)->start != 0) { | |
411 | valid = 0; | |
412 | } else { | |
413 | /* and check that there are no wholes after this datagram */ | |
414 | iprh_prev = iprh; | |
415 | q = iprh->next_pbuf; | |
416 | while (q != NULL) { | |
417 | iprh = (struct ip_reass_helper*)q->payload; | |
418 | if (iprh_prev->end != iprh->start) { | |
419 | valid = 0; | |
420 | break; | |
421 | } | |
422 | iprh_prev = iprh; | |
423 | q = iprh->next_pbuf; | |
424 | } | |
425 | /* if still valid, all fragments are received | |
426 | * (because to the MF==0 already arrived */ | |
427 | if (valid) { | |
428 | LWIP_ASSERT("sanity check", ipr->p != NULL); | |
429 | LWIP_ASSERT("sanity check", | |
430 | ((struct ip_reass_helper*)ipr->p->payload) != iprh); | |
431 | LWIP_ASSERT("validate_datagram:next_pbuf!=NULL", | |
432 | iprh->next_pbuf == NULL); | |
433 | LWIP_ASSERT("validate_datagram:datagram end!=datagram len", | |
434 | iprh->end == ipr->datagram_len); | |
435 | } | |
436 | } | |
437 | } | |
438 | /* If valid is 0 here, there are some fragments missing in the middle | |
439 | * (since MF == 0 has already arrived). Such datagrams simply time out if | |
440 | * no more fragments are received... */ | |
441 | return valid; | |
442 | } | |
443 | /* If we come here, not all fragments were received, yet! */ | |
444 | return 0; /* not yet valid! */ | |
445 | #if IP_REASS_CHECK_OVERLAP | |
446 | freepbuf: | |
447 | ip_reass_pbufcount -= pbuf_clen(new_p); | |
448 | pbuf_free(new_p); | |
449 | return 0; | |
450 | #endif /* IP_REASS_CHECK_OVERLAP */ | |
451 | } | |
452 | ||
453 | /** | |
454 | * Reassembles incoming IP fragments into an IP datagram. | |
455 | * | |
456 | * @param p points to a pbuf chain of the fragment | |
457 | * @return NULL if reassembly is incomplete, ? otherwise | |
458 | */ | |
459 | struct pbuf * | |
460 | ip_reass(struct pbuf *p) | |
461 | { | |
462 | struct pbuf *r; | |
463 | struct ip_hdr *fraghdr; | |
464 | struct ip_reassdata *ipr; | |
465 | struct ip_reass_helper *iprh; | |
466 | u16_t offset, len; | |
467 | u8_t clen; | |
468 | struct ip_reassdata *ipr_prev = NULL; | |
469 | ||
470 | IPFRAG_STATS_INC(ip_frag.recv); | |
471 | snmp_inc_ipreasmreqds(); | |
472 | ||
473 | fraghdr = (struct ip_hdr*)p->payload; | |
474 | ||
475 | if ((IPH_HL(fraghdr) * 4) != IP_HLEN) { | |
476 | LWIP_DEBUGF(IP_REASS_DEBUG,("ip_reass: IP options currently not supported!\n")); | |
477 | IPFRAG_STATS_INC(ip_frag.err); | |
478 | goto nullreturn; | |
479 | } | |
480 | ||
481 | offset = (ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) * 8; | |
482 | len = ntohs(IPH_LEN(fraghdr)) - IPH_HL(fraghdr) * 4; | |
483 | ||
484 | /* Check if we are allowed to enqueue more datagrams. */ | |
485 | clen = pbuf_clen(p); | |
486 | if ((ip_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS) { | |
487 | #if IP_REASS_FREE_OLDEST | |
488 | if (!ip_reass_remove_oldest_datagram(fraghdr, clen) || | |
489 | ((ip_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS)) | |
490 | #endif /* IP_REASS_FREE_OLDEST */ | |
491 | { | |
492 | /* No datagram could be freed and still too many pbufs enqueued */ | |
493 | LWIP_DEBUGF(IP_REASS_DEBUG,("ip_reass: Overflow condition: pbufct=%d, clen=%d, MAX=%d\n", | |
494 | ip_reass_pbufcount, clen, IP_REASS_MAX_PBUFS)); | |
495 | IPFRAG_STATS_INC(ip_frag.memerr); | |
496 | /* @todo: send ICMP time exceeded here? */ | |
497 | /* drop this pbuf */ | |
498 | goto nullreturn; | |
499 | } | |
500 | } | |
501 | ||
502 | /* Look for the datagram the fragment belongs to in the current datagram queue, | |
503 | * remembering the previous in the queue for later dequeueing. */ | |
504 | for (ipr = reassdatagrams; ipr != NULL; ipr = ipr->next) { | |
505 | /* Check if the incoming fragment matches the one currently present | |
506 | in the reassembly buffer. If so, we proceed with copying the | |
507 | fragment into the buffer. */ | |
508 | if (IP_ADDRESSES_AND_ID_MATCH(&ipr->iphdr, fraghdr)) { | |
509 | LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass: matching previous fragment ID=%"X16_F"\n", | |
510 | ntohs(IPH_ID(fraghdr)))); | |
511 | IPFRAG_STATS_INC(ip_frag.cachehit); | |
512 | break; | |
513 | } | |
514 | ipr_prev = ipr; | |
515 | } | |
516 | ||
517 | if (ipr == NULL) { | |
518 | /* Enqueue a new datagram into the datagram queue */ | |
519 | ipr = ip_reass_enqueue_new_datagram(fraghdr, clen); | |
520 | /* Bail if unable to enqueue */ | |
521 | if(ipr == NULL) { | |
522 | goto nullreturn; | |
523 | } | |
524 | } else { | |
525 | if (((ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) == 0) && | |
526 | ((ntohs(IPH_OFFSET(&ipr->iphdr)) & IP_OFFMASK) != 0)) { | |
527 | /* ipr->iphdr is not the header from the first fragment, but fraghdr is | |
528 | * -> copy fraghdr into ipr->iphdr since we want to have the header | |
529 | * of the first fragment (for ICMP time exceeded and later, for copying | |
530 | * all options, if supported)*/ | |
531 | SMEMCPY(&ipr->iphdr, fraghdr, IP_HLEN); | |
532 | } | |
533 | } | |
534 | /* Track the current number of pbufs current 'in-flight', in order to limit | |
535 | the number of fragments that may be enqueued at any one time */ | |
536 | ip_reass_pbufcount += clen; | |
537 | ||
538 | /* At this point, we have either created a new entry or pointing | |
539 | * to an existing one */ | |
540 | ||
541 | /* check for 'no more fragments', and update queue entry*/ | |
542 | if ((ntohs(IPH_OFFSET(fraghdr)) & IP_MF) == 0) { | |
543 | ipr->flags |= IP_REASS_FLAG_LASTFRAG; | |
544 | ipr->datagram_len = offset + len; | |
545 | LWIP_DEBUGF(IP_REASS_DEBUG, | |
546 | ("ip_reass: last fragment seen, total len %"S16_F"\n", | |
547 | ipr->datagram_len)); | |
548 | } | |
549 | /* find the right place to insert this pbuf */ | |
550 | /* @todo: trim pbufs if fragments are overlapping */ | |
551 | if (ip_reass_chain_frag_into_datagram_and_validate(ipr, p)) { | |
552 | /* the totally last fragment (flag more fragments = 0) was received at least | |
553 | * once AND all fragments are received */ | |
554 | ipr->datagram_len += IP_HLEN; | |
555 | ||
556 | /* save the second pbuf before copying the header over the pointer */ | |
557 | r = ((struct ip_reass_helper*)ipr->p->payload)->next_pbuf; | |
558 | ||
559 | /* copy the original ip header back to the first pbuf */ | |
560 | fraghdr = (struct ip_hdr*)(ipr->p->payload); | |
561 | SMEMCPY(fraghdr, &ipr->iphdr, IP_HLEN); | |
562 | IPH_LEN_SET(fraghdr, htons(ipr->datagram_len)); | |
563 | IPH_OFFSET_SET(fraghdr, 0); | |
564 | IPH_CHKSUM_SET(fraghdr, 0); | |
565 | /* @todo: do we need to set calculate the correct checksum? */ | |
566 | IPH_CHKSUM_SET(fraghdr, inet_chksum(fraghdr, IP_HLEN)); | |
567 | ||
568 | p = ipr->p; | |
569 | ||
570 | /* chain together the pbufs contained within the reass_data list. */ | |
571 | while(r != NULL) { | |
572 | iprh = (struct ip_reass_helper*)r->payload; | |
573 | ||
574 | /* hide the ip header for every succeding fragment */ | |
575 | pbuf_header(r, -IP_HLEN); | |
576 | pbuf_cat(p, r); | |
577 | r = iprh->next_pbuf; | |
578 | } | |
579 | /* release the sources allocate for the fragment queue entry */ | |
580 | ip_reass_dequeue_datagram(ipr, ipr_prev); | |
581 | ||
582 | /* and adjust the number of pbufs currently queued for reassembly. */ | |
583 | ip_reass_pbufcount -= pbuf_clen(p); | |
584 | ||
585 | /* Return the pbuf chain */ | |
586 | return p; | |
587 | } | |
588 | /* the datagram is not (yet?) reassembled completely */ | |
589 | LWIP_DEBUGF(IP_REASS_DEBUG,("ip_reass_pbufcount: %d out\n", ip_reass_pbufcount)); | |
590 | return NULL; | |
591 | ||
592 | nullreturn: | |
593 | LWIP_DEBUGF(IP_REASS_DEBUG,("ip_reass: nullreturn\n")); | |
594 | IPFRAG_STATS_INC(ip_frag.drop); | |
595 | pbuf_free(p); | |
596 | return NULL; | |
597 | } | |
598 | #endif /* IP_REASSEMBLY */ | |
599 | ||
600 | #if IP_FRAG | |
601 | #if IP_FRAG_USES_STATIC_BUF | |
602 | static u8_t buf[LWIP_MEM_ALIGN_SIZE(IP_FRAG_MAX_MTU)]; | |
603 | #endif /* IP_FRAG_USES_STATIC_BUF */ | |
604 | ||
605 | /** | |
606 | * Fragment an IP datagram if too large for the netif. | |
607 | * | |
608 | * Chop the datagram in MTU sized chunks and send them in order | |
609 | * by using a fixed size static memory buffer (PBUF_REF) or | |
610 | * point PBUF_REFs into p (depending on IP_FRAG_USES_STATIC_BUF). | |
611 | * | |
612 | * @param p ip packet to send | |
613 | * @param netif the netif on which to send | |
614 | * @param dest destination ip address to which to send | |
615 | * | |
616 | * @return ERR_OK if sent successfully, err_t otherwise | |
617 | */ | |
618 | err_t | |
619 | ip_frag(struct pbuf *p, struct netif *netif, struct ip_addr *dest) | |
620 | { | |
621 | struct pbuf *rambuf; | |
622 | #if IP_FRAG_USES_STATIC_BUF | |
623 | struct pbuf *header; | |
624 | #else | |
625 | struct pbuf *newpbuf; | |
626 | struct ip_hdr *original_iphdr; | |
627 | #endif | |
628 | struct ip_hdr *iphdr; | |
629 | u16_t nfb; | |
630 | u16_t left, cop; | |
631 | u16_t mtu = netif->mtu; | |
632 | u16_t ofo, omf; | |
633 | u16_t last; | |
634 | u16_t poff = IP_HLEN; | |
635 | u16_t tmp; | |
636 | #if !IP_FRAG_USES_STATIC_BUF | |
637 | u16_t newpbuflen = 0; | |
638 | u16_t left_to_copy; | |
639 | #endif | |
640 | ||
641 | /* Get a RAM based MTU sized pbuf */ | |
642 | #if IP_FRAG_USES_STATIC_BUF | |
643 | /* When using a static buffer, we use a PBUF_REF, which we will | |
644 | * use to reference the packet (without link header). | |
645 | * Layer and length is irrelevant. | |
646 | */ | |
647 | rambuf = pbuf_alloc(PBUF_LINK, 0, PBUF_REF); | |
648 | if (rambuf == NULL) { | |
649 | LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_frag: pbuf_alloc(PBUF_LINK, 0, PBUF_REF) failed\n")); | |
650 | return ERR_MEM; | |
651 | } | |
652 | rambuf->tot_len = rambuf->len = mtu; | |
653 | rambuf->payload = LWIP_MEM_ALIGN((void *)buf); | |
654 | ||
655 | /* Copy the IP header in it */ | |
656 | iphdr = rambuf->payload; | |
657 | SMEMCPY(iphdr, p->payload, IP_HLEN); | |
658 | #else /* IP_FRAG_USES_STATIC_BUF */ | |
659 | original_iphdr = p->payload; | |
660 | iphdr = original_iphdr; | |
661 | #endif /* IP_FRAG_USES_STATIC_BUF */ | |
662 | ||
663 | /* Save original offset */ | |
664 | tmp = ntohs(IPH_OFFSET(iphdr)); | |
665 | ofo = tmp & IP_OFFMASK; | |
666 | omf = tmp & IP_MF; | |
667 | ||
668 | left = p->tot_len - IP_HLEN; | |
669 | ||
670 | nfb = (mtu - IP_HLEN) / 8; | |
671 | ||
672 | while (left) { | |
673 | last = (left <= mtu - IP_HLEN); | |
674 | ||
675 | /* Set new offset and MF flag */ | |
676 | tmp = omf | (IP_OFFMASK & (ofo)); | |
677 | if (!last) | |
678 | tmp = tmp | IP_MF; | |
679 | ||
680 | /* Fill this fragment */ | |
681 | cop = last ? left : nfb * 8; | |
682 | ||
683 | #if IP_FRAG_USES_STATIC_BUF | |
684 | poff += pbuf_copy_partial(p, (u8_t*)iphdr + IP_HLEN, cop, poff); | |
685 | #else /* IP_FRAG_USES_STATIC_BUF */ | |
686 | /* When not using a static buffer, create a chain of pbufs. | |
687 | * The first will be a PBUF_RAM holding the link and IP header. | |
688 | * The rest will be PBUF_REFs mirroring the pbuf chain to be fragged, | |
689 | * but limited to the size of an mtu. | |
690 | */ | |
691 | rambuf = pbuf_alloc(PBUF_LINK, IP_HLEN, PBUF_RAM); | |
692 | if (rambuf == NULL) { | |
693 | return ERR_MEM; | |
694 | } | |
695 | LWIP_ASSERT("this needs a pbuf in one piece!", | |
696 | (p->len >= (IP_HLEN))); | |
697 | SMEMCPY(rambuf->payload, original_iphdr, IP_HLEN); | |
698 | iphdr = rambuf->payload; | |
699 | ||
700 | /* Can just adjust p directly for needed offset. */ | |
701 | p->payload = (u8_t *)p->payload + poff; | |
702 | p->len -= poff; | |
703 | ||
704 | left_to_copy = cop; | |
705 | while (left_to_copy) { | |
706 | newpbuflen = (left_to_copy < p->len) ? left_to_copy : p->len; | |
707 | /* Is this pbuf already empty? */ | |
708 | if (!newpbuflen) { | |
709 | p = p->next; | |
710 | continue; | |
711 | } | |
712 | newpbuf = pbuf_alloc(PBUF_RAW, 0, PBUF_REF); | |
713 | if (newpbuf == NULL) { | |
714 | pbuf_free(rambuf); | |
715 | return ERR_MEM; | |
716 | } | |
717 | /* Mirror this pbuf, although we might not need all of it. */ | |
718 | newpbuf->payload = p->payload; | |
719 | newpbuf->len = newpbuf->tot_len = newpbuflen; | |
720 | /* Add it to end of rambuf's chain, but using pbuf_cat, not pbuf_chain | |
721 | * so that it is removed when pbuf_dechain is later called on rambuf. | |
722 | */ | |
723 | pbuf_cat(rambuf, newpbuf); | |
724 | left_to_copy -= newpbuflen; | |
725 | if (left_to_copy) | |
726 | p = p->next; | |
727 | } | |
728 | poff = newpbuflen; | |
729 | #endif /* IP_FRAG_USES_STATIC_BUF */ | |
730 | ||
731 | /* Correct header */ | |
732 | IPH_OFFSET_SET(iphdr, htons(tmp)); | |
733 | IPH_LEN_SET(iphdr, htons(cop + IP_HLEN)); | |
734 | IPH_CHKSUM_SET(iphdr, 0); | |
735 | IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN)); | |
736 | ||
737 | #if IP_FRAG_USES_STATIC_BUF | |
738 | if (last) | |
739 | pbuf_realloc(rambuf, left + IP_HLEN); | |
740 | ||
741 | /* This part is ugly: we alloc a RAM based pbuf for | |
742 | * the link level header for each chunk and then | |
743 | * free it.A PBUF_ROM style pbuf for which pbuf_header | |
744 | * worked would make things simpler. | |
745 | */ | |
746 | header = pbuf_alloc(PBUF_LINK, 0, PBUF_RAM); | |
747 | if (header != NULL) { | |
748 | pbuf_chain(header, rambuf); | |
749 | netif->output(netif, header, dest); | |
750 | IPFRAG_STATS_INC(ip_frag.xmit); | |
751 | snmp_inc_ipfragcreates(); | |
752 | pbuf_free(header); | |
753 | } else { | |
754 | LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_frag: pbuf_alloc() for header failed\n")); | |
755 | pbuf_free(rambuf); | |
756 | return ERR_MEM; | |
757 | } | |
758 | #else /* IP_FRAG_USES_STATIC_BUF */ | |
759 | /* No need for separate header pbuf - we allowed room for it in rambuf | |
760 | * when allocated. | |
761 | */ | |
762 | netif->output(netif, rambuf, dest); | |
763 | IPFRAG_STATS_INC(ip_frag.xmit); | |
764 | ||
765 | /* Unfortunately we can't reuse rambuf - the hardware may still be | |
766 | * using the buffer. Instead we free it (and the ensuing chain) and | |
767 | * recreate it next time round the loop. If we're lucky the hardware | |
768 | * will have already sent the packet, the free will really free, and | |
769 | * there will be zero memory penalty. | |
770 | */ | |
771 | ||
772 | pbuf_free(rambuf); | |
773 | #endif /* IP_FRAG_USES_STATIC_BUF */ | |
774 | left -= cop; | |
775 | ofo += nfb; | |
776 | } | |
777 | #if IP_FRAG_USES_STATIC_BUF | |
778 | pbuf_free(rambuf); | |
779 | #endif /* IP_FRAG_USES_STATIC_BUF */ | |
780 | snmp_inc_ipfragoks(); | |
781 | return ERR_OK; | |
782 | } | |
783 | #endif /* IP_FRAG */ |