-
Notifications
You must be signed in to change notification settings - Fork 443
/
Copy pathtcp_out.c
2252 lines (2014 loc) · 77.2 KB
/
tcp_out.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @file
* Transmission Control Protocol, outgoing traffic
*
* The output functions of TCP.
*
* There are two distinct ways for TCP segments to get sent:
* - queued data: these are segments transferring data or segments containing
* SYN or FIN (which both count as one sequence number). They are created as
* struct @ref pbuf together with a struct tcp_seg and enqueue to the
* unsent list of the pcb. They are sent by tcp_output:
* - @ref tcp_write : creates data segments
* - @ref tcp_split_unsent_seg : splits a data segment
* - @ref tcp_enqueue_flags : creates SYN-only or FIN-only segments
* - @ref tcp_output / tcp_output_segment : finalize the tcp header
* (e.g. sequence numbers, options, checksum) and output to IP
* - the various tcp_rexmit functions shuffle around segments between the
* unsent an unacked lists to retransmit them
* - tcp_create_segment and tcp_pbuf_prealloc allocate pbuf and
* segment for these functions
* - direct send: these segments don't contain data but control the connection
* behaviour. They are created as pbuf only and sent directly without
* enqueueing them:
* - @ref tcp_send_empty_ack sends an ACK-only segment
* - @ref tcp_rst sends a RST segment
* - @ref tcp_keepalive sends a keepalive segment
* - @ref tcp_zero_window_probe sends a window probe segment
* - tcp_output_alloc_header allocates a header-only pbuf for these functions
*/
/*
* Copyright (c) 2001-2004 Swedish Institute of Computer Science.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* This file is part of the lwIP TCP/IP stack.
*
* Author: Adam Dunkels <[email protected]>
*
*/
#include "lwip/opt.h"
#if LWIP_TCP /* don't build if not configured for use in lwipopts.h */
#include "lwip/priv/tcp_priv.h"
#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/ip_addr.h"
#include "lwip/netif.h"
#include "lwip/inet_chksum.h"
#include "lwip/stats.h"
#include "lwip/ip6.h"
#include "lwip/ip6_addr.h"
#if LWIP_TCP_TIMESTAMPS
#include "lwip/sys.h"
#endif
#include <string.h>
#ifdef LWIP_HOOK_FILENAME
#include LWIP_HOOK_FILENAME
#endif
/* Allow to add custom TCP header options by defining this hook */
#ifdef LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH
#define LWIP_TCP_OPT_LENGTH_SEGMENT(flags, pcb) LWIP_HOOK_TCP_OUT_TCPOPT_LENGTH(pcb, LWIP_TCP_OPT_LENGTH(flags))
#else
#define LWIP_TCP_OPT_LENGTH_SEGMENT(flags, pcb) LWIP_TCP_OPT_LENGTH(flags)
#endif
/* Define some copy-macros for checksum-on-copy so that the code looks
nicer by preventing too many ifdef's. */
#if TCP_CHECKSUM_ON_COPY
#define TCP_DATA_COPY(dst, src, len, seg) do { \
tcp_seg_add_chksum(LWIP_CHKSUM_COPY(dst, src, len), \
len, &seg->chksum, &seg->chksum_swapped); \
seg->flags |= TF_SEG_DATA_CHECKSUMMED; } while(0)
#define TCP_DATA_COPY2(dst, src, len, chksum, chksum_swapped) \
tcp_seg_add_chksum(LWIP_CHKSUM_COPY(dst, src, len), len, chksum, chksum_swapped);
#else /* TCP_CHECKSUM_ON_COPY*/
#define TCP_DATA_COPY(dst, src, len, seg) MEMCPY(dst, src, len)
#define TCP_DATA_COPY2(dst, src, len, chksum, chksum_swapped) MEMCPY(dst, src, len)
#endif /* TCP_CHECKSUM_ON_COPY*/
/** Define this to 1 for an extra check that the output checksum is valid
* (useful when the checksum is generated by the application, not the stack) */
#ifndef TCP_CHECKSUM_ON_COPY_SANITY_CHECK
#define TCP_CHECKSUM_ON_COPY_SANITY_CHECK 0
#endif
/* Allow to override the failure of sanity check from warning to e.g. hard failure */
#if TCP_CHECKSUM_ON_COPY_SANITY_CHECK
#ifndef TCP_CHECKSUM_ON_COPY_SANITY_CHECK_FAIL
#define TCP_CHECKSUM_ON_COPY_SANITY_CHECK_FAIL(msg) LWIP_DEBUGF(TCP_DEBUG | LWIP_DBG_LEVEL_WARNING, msg)
#endif
#endif
#if TCP_OVERSIZE
/** The size of segment pbufs created when TCP_OVERSIZE is enabled */
#ifndef TCP_OVERSIZE_CALC_LENGTH
#define TCP_OVERSIZE_CALC_LENGTH(length) ((length) + TCP_OVERSIZE)
#endif
#endif
/* Forward declarations.*/
static err_t tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb, struct netif *netif);
static err_t tcp_output_control_segment_netif(const struct tcp_pcb *pcb, struct pbuf *p,
const ip_addr_t *src, const ip_addr_t *dst,
struct netif *netif);
/* tcp_route: common code that returns a fixed bound netif or calls ip_route */
static struct netif *
tcp_route(const struct tcp_pcb *pcb, const ip_addr_t *src, const ip_addr_t *dst)
{
LWIP_UNUSED_ARG(src); /* in case IPv4-only and source-based routing is disabled */
if ((pcb != NULL) && (pcb->netif_idx != NETIF_NO_INDEX)) {
return netif_get_by_index(pcb->netif_idx);
} else {
return ip_route(src, dst);
}
}
/**
* Create a TCP segment with prefilled header.
*
* Called by @ref tcp_write, @ref tcp_enqueue_flags and @ref tcp_split_unsent_seg
*
* @param pcb Protocol control block for the TCP connection.
* @param p pbuf that is used to hold the TCP header.
* @param hdrflags TCP flags for header.
* @param seqno TCP sequence number of this packet
* @param optflags options to include in TCP header
* @return a new tcp_seg pointing to p, or NULL.
* The TCP header is filled in except ackno and wnd.
* p is freed on failure.
*/
static struct tcp_seg *
tcp_create_segment(const struct tcp_pcb *pcb, struct pbuf *p, u8_t hdrflags, u32_t seqno, u8_t optflags)
{
struct tcp_seg *seg;
u8_t optlen;
LWIP_ASSERT("tcp_create_segment: invalid pcb", pcb != NULL);
LWIP_ASSERT("tcp_create_segment: invalid pbuf", p != NULL);
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(optflags, pcb);
if ((seg = (struct tcp_seg *)memp_malloc(MEMP_TCP_SEG)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_create_segment: no memory.\n"));
pbuf_free(p);
return NULL;
}
seg->flags = optflags;
seg->next = NULL;
seg->p = p;
LWIP_ASSERT("p->tot_len >= optlen", p->tot_len >= optlen);
seg->len = p->tot_len - optlen;
#if TCP_OVERSIZE_DBGCHECK
seg->oversize_left = 0;
#endif /* TCP_OVERSIZE_DBGCHECK */
#if TCP_CHECKSUM_ON_COPY
seg->chksum = 0;
seg->chksum_swapped = 0;
/* check optflags */
LWIP_ASSERT("invalid optflags passed: TF_SEG_DATA_CHECKSUMMED",
(optflags & TF_SEG_DATA_CHECKSUMMED) == 0);
#endif /* TCP_CHECKSUM_ON_COPY */
/* build TCP header */
if (pbuf_add_header(p, TCP_HLEN)) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_create_segment: no room for TCP header in pbuf.\n"));
TCP_STATS_INC(tcp.err);
tcp_seg_free(seg);
return NULL;
}
seg->tcphdr = (struct tcp_hdr *)seg->p->payload;
seg->tcphdr->src = lwip_htons(pcb->local_port);
seg->tcphdr->dest = lwip_htons(pcb->remote_port);
seg->tcphdr->seqno = lwip_htonl(seqno);
/* ackno is set in tcp_output */
TCPH_HDRLEN_FLAGS_SET(seg->tcphdr, (5 + optlen / 4), hdrflags);
/* wnd and chksum are set in tcp_output */
seg->tcphdr->urgp = 0;
return seg;
}
/**
* Allocate a PBUF_RAM pbuf, perhaps with extra space at the end.
*
* This function is like pbuf_alloc(layer, length, PBUF_RAM) except
* there may be extra bytes available at the end.
*
* Called by @ref tcp_write
*
* @param layer flag to define header size.
* @param length size of the pbuf's payload.
* @param max_length maximum usable size of payload+oversize.
* @param oversize pointer to a u16_t that will receive the number of usable tail bytes.
* @param pcb The TCP connection that will enqueue the pbuf.
* @param apiflags API flags given to tcp_write.
* @param first_seg true when this pbuf will be used in the first enqueued segment.
*/
#if TCP_OVERSIZE
static struct pbuf *
tcp_pbuf_prealloc(pbuf_layer layer, u16_t length, u16_t max_length,
u16_t *oversize, const struct tcp_pcb *pcb, u8_t apiflags,
u8_t first_seg)
{
struct pbuf *p;
u16_t alloc = length;
LWIP_ASSERT("tcp_pbuf_prealloc: invalid oversize", oversize != NULL);
LWIP_ASSERT("tcp_pbuf_prealloc: invalid pcb", pcb != NULL);
#if LWIP_NETIF_TX_SINGLE_PBUF
LWIP_UNUSED_ARG(max_length);
LWIP_UNUSED_ARG(pcb);
LWIP_UNUSED_ARG(apiflags);
LWIP_UNUSED_ARG(first_seg);
alloc = max_length;
#else /* LWIP_NETIF_TX_SINGLE_PBUF */
if (length < max_length) {
/* Should we allocate an oversized pbuf, or just the minimum
* length required? If tcp_write is going to be called again
* before this segment is transmitted, we want the oversized
* buffer. If the segment will be transmitted immediately, we can
* save memory by allocating only length. We use a simple
* heuristic based on the following information:
*
* Did the user set TCP_WRITE_FLAG_MORE?
*
* Will the Nagle algorithm defer transmission of this segment?
*/
if ((apiflags & TCP_WRITE_FLAG_MORE) ||
(!(pcb->flags & TF_NODELAY) &&
(!first_seg ||
pcb->unsent != NULL ||
pcb->unacked != NULL))) {
alloc = LWIP_MIN(max_length, LWIP_MEM_ALIGN_SIZE(TCP_OVERSIZE_CALC_LENGTH(length)));
}
}
#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
p = pbuf_alloc(layer, alloc, PBUF_RAM);
if (p == NULL) {
return NULL;
}
LWIP_ASSERT("need unchained pbuf", p->next == NULL);
*oversize = p->len - length;
/* trim p->len to the currently used size */
p->len = p->tot_len = length;
return p;
}
#else /* TCP_OVERSIZE */
#define tcp_pbuf_prealloc(layer, length, mx, os, pcb, api, fst) pbuf_alloc((layer), (length), PBUF_RAM)
#endif /* TCP_OVERSIZE */
#if TCP_CHECKSUM_ON_COPY
/** Add a checksum of newly added data to the segment.
*
* Called by tcp_write and tcp_split_unsent_seg.
*/
static void
tcp_seg_add_chksum(u16_t chksum, u16_t len, u16_t *seg_chksum,
u8_t *seg_chksum_swapped)
{
u32_t helper;
/* add chksum to old chksum and fold to u16_t */
helper = chksum + *seg_chksum;
chksum = FOLD_U32T(helper);
if ((len & 1) != 0) {
*seg_chksum_swapped = 1 - *seg_chksum_swapped;
chksum = SWAP_BYTES_IN_WORD(chksum);
}
*seg_chksum = chksum;
}
#endif /* TCP_CHECKSUM_ON_COPY */
/** Checks if tcp_write is allowed or not (checks state, snd_buf and snd_queuelen).
*
* @param pcb the tcp pcb to check for
* @param len length of data to send (checked against snd_buf)
* @return ERR_OK if tcp_write is allowed to proceed, another err_t otherwise
*/
static err_t
tcp_write_checks(struct tcp_pcb *pcb, u16_t len)
{
LWIP_ASSERT("tcp_write_checks: invalid pcb", pcb != NULL);
/* connection is in invalid state for data transmission? */
if ((pcb->state != ESTABLISHED) &&
(pcb->state != CLOSE_WAIT) &&
(pcb->state != SYN_SENT) &&
(pcb->state != SYN_RCVD)) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_STATE | LWIP_DBG_LEVEL_SEVERE, ("tcp_write() called in invalid state\n"));
return ERR_CONN;
} else if (len == 0) {
return ERR_OK;
}
/* fail on too much data */
if (len > pcb->snd_buf) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("tcp_write: too much data (len=%"U16_F" > snd_buf=%"TCPWNDSIZE_F")\n",
len, pcb->snd_buf));
tcp_set_flags(pcb, TF_NAGLEMEMERR);
return ERR_MEM;
}
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_write: queuelen: %"TCPWNDSIZE_F"\n", (tcpwnd_size_t)pcb->snd_queuelen));
/* If total number of pbufs on the unsent/unacked queues exceeds the
* configured maximum, return an error */
/* check for configured max queuelen and possible overflow */
if (pcb->snd_queuelen >= LWIP_MIN(TCP_SND_QUEUELEN, (TCP_SNDQUEUELEN_OVERFLOW + 1))) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("tcp_write: too long queue %"U16_F" (max %"U16_F")\n",
pcb->snd_queuelen, (u16_t)TCP_SND_QUEUELEN));
TCP_STATS_INC(tcp.memerr);
tcp_set_flags(pcb, TF_NAGLEMEMERR);
return ERR_MEM;
}
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_write: pbufs on queue => at least one queue non-empty",
pcb->unacked != NULL || pcb->unsent != NULL);
} else {
LWIP_ASSERT("tcp_write: no pbufs on queue => both queues empty",
pcb->unacked == NULL && pcb->unsent == NULL);
}
return ERR_OK;
}
/**
* @ingroup tcp_raw
* Write data for sending (but does not send it immediately).
*
* It waits in the expectation of more data being sent soon (as
* it can send them more efficiently by combining them together).
* To prompt the system to send data now, call tcp_output() after
* calling tcp_write().
*
* This function enqueues the data pointed to by the argument dataptr. The length of
* the data is passed as the len parameter. The apiflags can be one or more of:
* - TCP_WRITE_FLAG_COPY: indicates whether the new memory should be allocated
* for the data to be copied into. If this flag is not given, no new memory
* should be allocated and the data should only be referenced by pointer. This
* also means that the memory behind dataptr must not change until the data is
* ACKed by the remote host
* - TCP_WRITE_FLAG_MORE: indicates that more data follows. If this is omitted,
* the PSH flag is set in the last segment created by this call to tcp_write.
* If this flag is given, the PSH flag is not set.
*
* The tcp_write() function will fail and return ERR_MEM if the length
* of the data exceeds the current send buffer size or if the length of
* the queue of outgoing segment is larger than the upper limit defined
* in lwipopts.h. The number of bytes available in the output queue can
* be retrieved with the tcp_sndbuf() function.
*
* The proper way to use this function is to call the function with at
* most tcp_sndbuf() bytes of data. If the function returns ERR_MEM,
* the application should wait until some of the currently enqueued
* data has been successfully received by the other host and try again.
*
* @param pcb Protocol control block for the TCP connection to enqueue data for.
* @param arg Pointer to the data to be enqueued for sending.
* @param len Data length in bytes
* @param apiflags combination of following flags :
* - TCP_WRITE_FLAG_COPY (0x01) data will be copied into memory belonging to the stack
* - TCP_WRITE_FLAG_MORE (0x02) for TCP connection, PSH flag will not be set on last segment sent,
* @return ERR_OK if enqueued, another err_t on error
*/
err_t
tcp_write(struct tcp_pcb *pcb, const void *arg, u16_t len, u8_t apiflags)
{
struct pbuf *concat_p = NULL;
struct tcp_seg *last_unsent = NULL, *seg = NULL, *prev_seg = NULL, *queue = NULL;
u16_t pos = 0; /* position in 'arg' data */
u16_t queuelen;
u8_t optlen;
u8_t optflags = 0;
#if TCP_OVERSIZE
u16_t oversize = 0;
u16_t oversize_used = 0;
#if TCP_OVERSIZE_DBGCHECK
u16_t oversize_add = 0;
#endif /* TCP_OVERSIZE_DBGCHECK*/
#endif /* TCP_OVERSIZE */
u16_t extendlen = 0;
#if TCP_CHECKSUM_ON_COPY
u16_t concat_chksum = 0;
u8_t concat_chksum_swapped = 0;
u16_t concat_chksummed = 0;
#endif /* TCP_CHECKSUM_ON_COPY */
err_t err;
u16_t mss_local;
LWIP_ERROR("tcp_write: invalid pcb", pcb != NULL, return ERR_ARG);
/* don't allocate segments bigger than half the maximum window we ever received */
mss_local = LWIP_MIN(pcb->mss, TCPWND_MIN16(pcb->snd_wnd_max / 2));
mss_local = mss_local ? mss_local : pcb->mss;
LWIP_ASSERT_CORE_LOCKED();
#if LWIP_NETIF_TX_SINGLE_PBUF
/* Always copy to try to create single pbufs for TX */
apiflags |= TCP_WRITE_FLAG_COPY;
#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_write(pcb=%p, data=%p, len=%"U16_F", apiflags=%"U16_F")\n",
(void *)pcb, arg, len, (u16_t)apiflags));
LWIP_ERROR("tcp_write: arg == NULL (programmer violates API)",
arg != NULL, return ERR_ARG;);
err = tcp_write_checks(pcb, len);
if (err != ERR_OK) {
return err;
}
queuelen = pcb->snd_queuelen;
#if LWIP_TCP_TIMESTAMPS
if ((pcb->flags & TF_TIMESTAMP)) {
/* Make sure the timestamp option is only included in data segments if we
agreed about it with the remote host. */
optflags = TF_SEG_OPTS_TS;
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(TF_SEG_OPTS_TS, pcb);
/* ensure that segments can hold at least one data byte... */
mss_local = LWIP_MAX(mss_local, LWIP_TCP_OPT_LEN_TS + 1);
} else
#endif /* LWIP_TCP_TIMESTAMPS */
{
optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(0, pcb);
}
/*
* TCP segmentation is done in three phases with increasing complexity:
*
* 1. Copy data directly into an oversized pbuf.
* 2. Chain a new pbuf to the end of pcb->unsent.
* 3. Create new segments.
*
* We may run out of memory at any point. In that case we must
* return ERR_MEM and not change anything in pcb. Therefore, all
* changes are recorded in local variables and committed at the end
* of the function. Some pcb fields are maintained in local copies:
*
* queuelen = pcb->snd_queuelen
* oversize = pcb->unsent_oversize
*
* These variables are set consistently by the phases:
*
* seg points to the last segment tampered with.
*
* pos records progress as data is segmented.
*/
/* Find the tail of the unsent queue. */
if (pcb->unsent != NULL) {
u16_t space;
u16_t unsent_optlen;
/* @todo: this could be sped up by keeping last_unsent in the pcb */
for (last_unsent = pcb->unsent; last_unsent->next != NULL;
last_unsent = last_unsent->next);
/* Usable space at the end of the last unsent segment */
unsent_optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(last_unsent->flags, pcb);
LWIP_ASSERT("mss_local is too small", mss_local >= last_unsent->len + unsent_optlen);
space = mss_local - (last_unsent->len + unsent_optlen);
/*
* Phase 1: Copy data directly into an oversized pbuf.
*
* The number of bytes copied is recorded in the oversize_used
* variable. The actual copying is done at the bottom of the
* function.
*/
#if TCP_OVERSIZE
#if TCP_OVERSIZE_DBGCHECK
/* check that pcb->unsent_oversize matches last_unsent->oversize_left */
LWIP_ASSERT("unsent_oversize mismatch (pcb vs. last_unsent)",
pcb->unsent_oversize == last_unsent->oversize_left);
#endif /* TCP_OVERSIZE_DBGCHECK */
oversize = pcb->unsent_oversize;
if (oversize > 0) {
LWIP_ASSERT("inconsistent oversize vs. space", oversize <= space);
seg = last_unsent;
oversize_used = LWIP_MIN(space, LWIP_MIN(oversize, len));
pos += oversize_used;
oversize -= oversize_used;
space -= oversize_used;
}
/* now we are either finished or oversize is zero */
LWIP_ASSERT("inconsistent oversize vs. len", (oversize == 0) || (pos == len));
#endif /* TCP_OVERSIZE */
#if !LWIP_NETIF_TX_SINGLE_PBUF
/*
* Phase 2: Chain a new pbuf to the end of pcb->unsent.
*
* As an exception when NOT copying the data, if the given data buffer
* directly follows the last unsent data buffer in memory, extend the last
* ROM pbuf reference to the buffer, thus saving a ROM pbuf allocation.
*
* We don't extend segments containing SYN/FIN flags or options
* (len==0). The new pbuf is kept in concat_p and pbuf_cat'ed at
* the end.
*
* This phase is skipped for LWIP_NETIF_TX_SINGLE_PBUF as we could only execute
* it after rexmit puts a segment from unacked to unsent and at this point,
* oversize info is lost.
*/
if ((pos < len) && (space > 0) && (last_unsent->len > 0)) {
u16_t seglen = LWIP_MIN(space, len - pos);
seg = last_unsent;
/* Create a pbuf with a copy or reference to seglen bytes. We
* can use PBUF_RAW here since the data appears in the middle of
* a segment. A header will never be prepended. */
if (apiflags & TCP_WRITE_FLAG_COPY) {
/* Data is copied */
if ((concat_p = tcp_pbuf_prealloc(PBUF_RAW, seglen, space, &oversize, pcb, apiflags, 1)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n",
seglen));
goto memerr;
}
#if TCP_OVERSIZE_DBGCHECK
oversize_add = oversize;
#endif /* TCP_OVERSIZE_DBGCHECK */
TCP_DATA_COPY2(concat_p->payload, (const u8_t *)arg + pos, seglen, &concat_chksum, &concat_chksum_swapped);
#if TCP_CHECKSUM_ON_COPY
concat_chksummed += seglen;
#endif /* TCP_CHECKSUM_ON_COPY */
queuelen += pbuf_clen(concat_p);
} else {
/* Data is not copied */
/* If the last unsent pbuf is of type PBUF_ROM, try to extend it. */
struct pbuf *p;
for (p = last_unsent->p; p->next != NULL; p = p->next);
if (((p->type_internal & (PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_FLAG_DATA_VOLATILE)) == 0) &&
(const u8_t *)p->payload + p->len == (const u8_t *)arg) {
LWIP_ASSERT("tcp_write: ROM pbufs cannot be oversized", pos == 0);
extendlen = seglen;
} else {
if ((concat_p = pbuf_alloc(PBUF_RAW, seglen, PBUF_ROM)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("tcp_write: could not allocate memory for zero-copy pbuf\n"));
goto memerr;
}
/* reference the non-volatile payload data */
((struct pbuf_rom *)concat_p)->payload = (const u8_t *)arg + pos;
queuelen += pbuf_clen(concat_p);
}
#if TCP_CHECKSUM_ON_COPY
/* calculate the checksum of nocopy-data */
tcp_seg_add_chksum(~inet_chksum((const u8_t *)arg + pos, seglen), seglen,
&concat_chksum, &concat_chksum_swapped);
concat_chksummed += seglen;
#endif /* TCP_CHECKSUM_ON_COPY */
}
pos += seglen;
}
#endif /* !LWIP_NETIF_TX_SINGLE_PBUF */
} else {
#if TCP_OVERSIZE
LWIP_ASSERT("unsent_oversize mismatch (pcb->unsent is NULL)",
pcb->unsent_oversize == 0);
#endif /* TCP_OVERSIZE */
}
/*
* Phase 3: Create new segments.
*
* The new segments are chained together in the local 'queue'
* variable, ready to be appended to pcb->unsent.
*/
while (pos < len) {
struct pbuf *p;
u16_t left = len - pos;
u16_t max_len = mss_local - optlen;
u16_t seglen = LWIP_MIN(left, max_len);
#if TCP_CHECKSUM_ON_COPY
u16_t chksum = 0;
u8_t chksum_swapped = 0;
#endif /* TCP_CHECKSUM_ON_COPY */
if (apiflags & TCP_WRITE_FLAG_COPY) {
/* If copy is set, memory should be allocated and data copied
* into pbuf */
if ((p = tcp_pbuf_prealloc(PBUF_TRANSPORT, seglen + optlen, mss_local, &oversize, pcb, apiflags, queue == NULL)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n", seglen));
goto memerr;
}
LWIP_ASSERT("tcp_write: check that first pbuf can hold the complete seglen",
(p->len >= seglen));
TCP_DATA_COPY2((char *)p->payload + optlen, (const u8_t *)arg + pos, seglen, &chksum, &chksum_swapped);
} else {
/* Copy is not set: First allocate a pbuf for holding the data.
* Since the referenced data is available at least until it is
* sent out on the link (as it has to be ACKed by the remote
* party) we can safely use PBUF_ROM instead of PBUF_REF here.
*/
struct pbuf *p2;
#if TCP_OVERSIZE
LWIP_ASSERT("oversize == 0", oversize == 0);
#endif /* TCP_OVERSIZE */
if ((p2 = pbuf_alloc(PBUF_TRANSPORT, seglen, PBUF_ROM)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write: could not allocate memory for zero-copy pbuf\n"));
goto memerr;
}
#if TCP_CHECKSUM_ON_COPY
/* calculate the checksum of nocopy-data */
chksum = ~inet_chksum((const u8_t *)arg + pos, seglen);
if (seglen & 1) {
chksum_swapped = 1;
chksum = SWAP_BYTES_IN_WORD(chksum);
}
#endif /* TCP_CHECKSUM_ON_COPY */
/* reference the non-volatile payload data */
((struct pbuf_rom *)p2)->payload = (const u8_t *)arg + pos;
/* Second, allocate a pbuf for the headers. */
if ((p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {
/* If allocation fails, we have to deallocate the data pbuf as
* well. */
pbuf_free(p2);
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write: could not allocate memory for header pbuf\n"));
goto memerr;
}
/* Concatenate the headers and data pbufs together. */
pbuf_cat(p/*header*/, p2/*data*/);
}
queuelen += pbuf_clen(p);
/* Now that there are more segments queued, we check again if the
* length of the queue exceeds the configured maximum or
* overflows. */
if (queuelen > LWIP_MIN(TCP_SND_QUEUELEN, TCP_SNDQUEUELEN_OVERFLOW)) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write: queue too long %"U16_F" (%d)\n",
queuelen, (int)TCP_SND_QUEUELEN));
pbuf_free(p);
goto memerr;
}
if ((seg = tcp_create_segment(pcb, p, 0, pcb->snd_lbb + pos, optflags)) == NULL) {
goto memerr;
}
#if TCP_OVERSIZE_DBGCHECK
seg->oversize_left = oversize;
#endif /* TCP_OVERSIZE_DBGCHECK */
#if TCP_CHECKSUM_ON_COPY
seg->chksum = chksum;
seg->chksum_swapped = chksum_swapped;
seg->flags |= TF_SEG_DATA_CHECKSUMMED;
#endif /* TCP_CHECKSUM_ON_COPY */
/* first segment of to-be-queued data? */
if (queue == NULL) {
queue = seg;
} else {
/* Attach the segment to the end of the queued segments */
LWIP_ASSERT("prev_seg != NULL", prev_seg != NULL);
prev_seg->next = seg;
}
/* remember last segment of to-be-queued data for next iteration */
prev_seg = seg;
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_TRACE, ("tcp_write: queueing %"U32_F":%"U32_F"\n",
lwip_ntohl(seg->tcphdr->seqno),
lwip_ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg)));
pos += seglen;
}
/*
* All three segmentation phases were successful. We can commit the
* transaction.
*/
#if TCP_OVERSIZE_DBGCHECK
if ((last_unsent != NULL) && (oversize_add != 0)) {
last_unsent->oversize_left += oversize_add;
}
#endif /* TCP_OVERSIZE_DBGCHECK */
/*
* Phase 1: If data has been added to the preallocated tail of
* last_unsent, we update the length fields of the pbuf chain.
*/
#if TCP_OVERSIZE
if (oversize_used > 0) {
struct pbuf *p;
/* Bump tot_len of whole chain, len of tail */
for (p = last_unsent->p; p; p = p->next) {
p->tot_len += oversize_used;
if (p->next == NULL) {
TCP_DATA_COPY((char *)p->payload + p->len, arg, oversize_used, last_unsent);
p->len += oversize_used;
}
}
last_unsent->len += oversize_used;
#if TCP_OVERSIZE_DBGCHECK
LWIP_ASSERT("last_unsent->oversize_left >= oversize_used",
last_unsent->oversize_left >= oversize_used);
last_unsent->oversize_left -= oversize_used;
#endif /* TCP_OVERSIZE_DBGCHECK */
}
pcb->unsent_oversize = oversize;
#endif /* TCP_OVERSIZE */
/*
* Phase 2: concat_p can be concatenated onto last_unsent->p, unless we
* determined that the last ROM pbuf can be extended to include the new data.
*/
if (concat_p != NULL) {
LWIP_ASSERT("tcp_write: cannot concatenate when pcb->unsent is empty",
(last_unsent != NULL));
pbuf_cat(last_unsent->p, concat_p);
last_unsent->len += concat_p->tot_len;
} else if (extendlen > 0) {
struct pbuf *p;
LWIP_ASSERT("tcp_write: extension of reference requires reference",
last_unsent != NULL && last_unsent->p != NULL);
for (p = last_unsent->p; p->next != NULL; p = p->next) {
p->tot_len += extendlen;
}
p->tot_len += extendlen;
p->len += extendlen;
last_unsent->len += extendlen;
}
#if TCP_CHECKSUM_ON_COPY
if (concat_chksummed) {
LWIP_ASSERT("tcp_write: concat checksum needs concatenated data",
concat_p != NULL || extendlen > 0);
/*if concat checksumm swapped - swap it back */
if (concat_chksum_swapped) {
concat_chksum = SWAP_BYTES_IN_WORD(concat_chksum);
}
tcp_seg_add_chksum(concat_chksum, concat_chksummed, &last_unsent->chksum,
&last_unsent->chksum_swapped);
last_unsent->flags |= TF_SEG_DATA_CHECKSUMMED;
}
#endif /* TCP_CHECKSUM_ON_COPY */
/*
* Phase 3: Append queue to pcb->unsent. Queue may be NULL, but that
* is harmless
*/
if (last_unsent == NULL) {
pcb->unsent = queue;
} else {
last_unsent->next = queue;
}
/*
* Finally update the pcb state.
*/
pcb->snd_lbb += len;
pcb->snd_buf -= len;
pcb->snd_queuelen = queuelen;
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_write: %"S16_F" (after enqueued)\n",
pcb->snd_queuelen));
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_write: valid queue length",
pcb->unacked != NULL || pcb->unsent != NULL);
}
/* Set the PSH flag in the last segment that we enqueued. */
if (seg != NULL && seg->tcphdr != NULL && ((apiflags & TCP_WRITE_FLAG_MORE) == 0)) {
TCPH_SET_FLAG(seg->tcphdr, TCP_PSH);
}
return ERR_OK;
memerr:
tcp_set_flags(pcb, TF_NAGLEMEMERR);
TCP_STATS_INC(tcp.memerr);
if (concat_p != NULL) {
pbuf_free(concat_p);
}
if (queue != NULL) {
tcp_segs_free(queue);
}
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_write: valid queue length", pcb->unacked != NULL ||
pcb->unsent != NULL);
}
LWIP_DEBUGF(TCP_QLEN_DEBUG | LWIP_DBG_STATE, ("tcp_write: %"S16_F" (with mem err)\n", pcb->snd_queuelen));
return ERR_MEM;
}
/**
* Split segment on the head of the unsent queue. If return is not
* ERR_OK, existing head remains intact
*
* The split is accomplished by creating a new TCP segment and pbuf
* which holds the remainder payload after the split. The original
* pbuf is trimmed to new length. This allows splitting of read-only
* pbufs
*
* @param pcb the tcp_pcb for which to split the unsent head
* @param split the amount of payload to remain in the head
*/
err_t
tcp_split_unsent_seg(struct tcp_pcb *pcb, u16_t split)
{
struct tcp_seg *seg = NULL, *useg = NULL;
struct pbuf *p = NULL;
u8_t optlen;
u8_t optflags;
u8_t split_flags;
u8_t remainder_flags;
u16_t remainder;
u16_t offset;
#if TCP_CHECKSUM_ON_COPY
u16_t chksum = 0;
u8_t chksum_swapped = 0;
struct pbuf *q;
#endif /* TCP_CHECKSUM_ON_COPY */
LWIP_ASSERT("tcp_split_unsent_seg: invalid pcb", pcb != NULL);
useg = pcb->unsent;
if (useg == NULL) {
return ERR_MEM;
}
if (split == 0) {
LWIP_ASSERT("Can't split segment into length 0", 0);
return ERR_VAL;
}
if (useg->len <= split) {
return ERR_OK;
}
LWIP_ASSERT("split <= mss", split <= pcb->mss);
LWIP_ASSERT("useg->len > 0", useg->len > 0);
/* We should check that we don't exceed TCP_SND_QUEUELEN but we need
* to split this packet so we may actually exceed the max value by
* one!
*/
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue: split_unsent_seg: %u\n", (unsigned int)pcb->snd_queuelen));
optflags = useg->flags;
#if TCP_CHECKSUM_ON_COPY
/* Remove since checksum is not stored until after tcp_create_segment() */
optflags &= ~TF_SEG_DATA_CHECKSUMMED;
#endif /* TCP_CHECKSUM_ON_COPY */
optlen = LWIP_TCP_OPT_LENGTH(optflags);
remainder = useg->len - split;
/* Create new pbuf for the remainder of the split */
p = pbuf_alloc(PBUF_TRANSPORT, remainder + optlen, PBUF_RAM);
if (p == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("tcp_split_unsent_seg: could not allocate memory for pbuf remainder %u\n", remainder));
goto memerr;
}
/* Offset into the original pbuf is past TCP/IP headers, options, and split amount */
offset = useg->p->tot_len - useg->len + split;
/* Copy remainder into new pbuf, headers and options will not be filled out */
if (pbuf_copy_partial(useg->p, (u8_t *)p->payload + optlen, remainder, offset ) != remainder) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("tcp_split_unsent_seg: could not copy pbuf remainder %u\n", remainder));
goto memerr;
}
#if TCP_CHECKSUM_ON_COPY
/* calculate the checksum on remainder data */
tcp_seg_add_chksum(~inet_chksum((const u8_t *)p->payload + optlen, remainder), remainder,
&chksum, &chksum_swapped);
#endif /* TCP_CHECKSUM_ON_COPY */
/* Options are created when calling tcp_output() */
/* Migrate flags from original segment */
split_flags = TCPH_FLAGS(useg->tcphdr);
remainder_flags = 0; /* ACK added in tcp_output() */
if (split_flags & TCP_PSH) {
split_flags &= ~TCP_PSH;
remainder_flags |= TCP_PSH;
}
if (split_flags & TCP_FIN) {
split_flags &= ~TCP_FIN;
remainder_flags |= TCP_FIN;
}
/* SYN should be left on split, RST should not be present with data */
seg = tcp_create_segment(pcb, p, remainder_flags, lwip_ntohl(useg->tcphdr->seqno) + split, optflags);
if (seg == NULL) {
p = NULL; /* Freed by tcp_create_segment */
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("tcp_split_unsent_seg: could not create new TCP segment\n"));
goto memerr;
}
#if TCP_CHECKSUM_ON_COPY
seg->chksum = chksum;
seg->chksum_swapped = chksum_swapped;
seg->flags |= TF_SEG_DATA_CHECKSUMMED;
#endif /* TCP_CHECKSUM_ON_COPY */
/* Remove this segment from the queue since trimming it may free pbufs */
pcb->snd_queuelen -= pbuf_clen(useg->p);
/* Trim the original pbuf into our split size. At this point our remainder segment must be setup
successfully because we are modifying the original segment */
pbuf_realloc(useg->p, useg->p->tot_len - remainder);
useg->len -= remainder;
TCPH_SET_FLAG(useg->tcphdr, split_flags);
#if TCP_OVERSIZE_DBGCHECK
/* By trimming, realloc may have actually shrunk the pbuf, so clear oversize_left */
useg->oversize_left = 0;
#endif /* TCP_OVERSIZE_DBGCHECK */
/* Add back to the queue with new trimmed pbuf */
pcb->snd_queuelen += pbuf_clen(useg->p);
#if TCP_CHECKSUM_ON_COPY
/* The checksum on the split segment is now incorrect. We need to re-run it over the split */
useg->chksum = 0;
useg->chksum_swapped = 0;
q = useg->p;
offset = q->tot_len - useg->len; /* Offset due to exposed headers */
/* Advance to the pbuf where the offset ends */
while (q != NULL && offset > q->len) {
offset -= q->len;
q = q->next;
}
LWIP_ASSERT("Found start of payload pbuf", q != NULL);
/* Checksum the first payload pbuf accounting for offset, then other pbufs are all payload */
for (; q != NULL; offset = 0, q = q->next) {
tcp_seg_add_chksum(~inet_chksum((const u8_t *)q->payload + offset, q->len - offset), q->len - offset,
&useg->chksum, &useg->chksum_swapped);
}
#endif /* TCP_CHECKSUM_ON_COPY */
/* Update number of segments on the queues. Note that length now may
* exceed TCP_SND_QUEUELEN! We don't have to touch pcb->snd_buf
* because the total amount of data is constant when packet is split */
pcb->snd_queuelen += pbuf_clen(seg->p);
/* Finally insert remainder into queue after split (which stays head) */
seg->next = useg->next;
useg->next = seg;
#if TCP_OVERSIZE
/* If remainder is last segment on the unsent, ensure we clear the oversize amount
* because the remainder is always sized to the exact remaining amount */
if (seg->next == NULL) {
pcb->unsent_oversize = 0;
}
#endif /* TCP_OVERSIZE */
return ERR_OK;
memerr:
TCP_STATS_INC(tcp.memerr);
LWIP_ASSERT("seg == NULL", seg == NULL);
if (p != NULL) {
pbuf_free(p);
}
return ERR_MEM;
}
/**
* Called by tcp_close() to send a segment including FIN flag but not data.
* This FIN may be added to an existing segment or a new, otherwise empty
* segment is enqueued.
*