forked from Stichting-MINIX-Research-Foundation/minix
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtcpsock.c
More file actions
2793 lines (2331 loc) · 80.9 KB
/
Copy pathtcpsock.c
File metadata and controls
2793 lines (2331 loc) · 80.9 KB
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
/* LWIP service - tcpsock.c - TCP sockets */
/*
* This module implements support for TCP sockets based on lwIP's core TCP PCB
* module, which is largely but not fully cooperative with exactly what we want
* to achieve, with as a result that this module is rather complicated.
*
* Each socket has a send queue and a receive queue. Both are using lwIP's own
* (pbuf) buffers, which largely come out of the main 512-byte buffer pool.
* The buffers on the send queue are allocated and freed by us--the latter only
* once they are no longer in use by lwIP as well. A bit counterintuitively,
* we deliberately use a smaller lwIP per-PCB TCP send buffer limit
* (TCP_SND_BUF) in the lwIP send configuration (lwipopts.h) in order to more
* easily trigger conditions where we cannot enqueue data (or the final FIN)
* right away. This way, we get to test the internal logic of this module a
* lot more easily. The small lwIP send queue size should not have any impact
* on performance, as our own per-socket send queues can be much larger and we
* enqueue more of that on the lwIP PCB as soon as we can in all cases.
*
* The receive queue consists of whatever buffers were given to us by lwIP, but
* since those may be many buffers with small amounts of data each, we perform
* fairly aggressive merging of consecutive buffers. The intended result is
* that we waste no more than 50% of memory within the receive queue. Merging
* requires memory copies, which makes it expensive, but we do not configure
* lwIP with enough buffers to make running out of buffers a non-issue, so this
* trade-off is necessary. Practical experience and measurements of the merge
* policy will have to show whether and how the current policy may be improved.
*
* As can be expected, the connection close semantics are by far the most
* complicated part of this module. We attempt to get rid of the lwIP PCB as
* soon as we can, letting lwIP take care of the TIME_WAIT state for example.
* However, there are various conditions that have to be met before we can
* forget about the PCB here--most importantly, that none of our sent data
* blocks are still referenced by lwIP because they have not yet been sent or
* acknowledged. We can only free the data blocks once lwIP is done with them.
*
* We do consider the TCP state of lwIP's PCB, in order to avoid duplicating
* full state tracking here. However, we do not look at a socket's TCP state
* while in a lwIP-generated event for that socket, because the state may not
* necessarily reflect the (correct or new) TCP state of the connection, nor
* may the PCB be available--this is the case for error events. For these
* reasons we use a few internal TCPF_ flags to perform partial state tracking.
*
* More generally, we tend to access lwIP PCB fields directly only when lwIP's
* own BSD API implementation does that too and there is no better alternative.
* One example of this is the check to see if our FIN was acknowledged, for
* SO_LINGER support. In terms of maintenance, our hope is that if lwIP's API
* changes later, we can change our code to imitate whatever lwIP's BSD API
* implementation does at that point.
*/
#include <sys/socketvar.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/ip_var.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_var.h>
#include <netinet/tcp_fsm.h>
/*
* Unfortunately, NetBSD and lwIP have different definitions of a few relevant
* preprocessor variables. Make sure we do not attempt to use the NetBSD one
* where it matters. We do need one of the NetBSD definitions though.
*/
static const unsigned int NETBSD_TF_NODELAY = TF_NODELAY;
#undef TF_NODELAY
#undef TCP_MSS
#include "lwip.h"
#include "tcpisn.h"
#include "lwip/tcp.h"
#include "lwip/priv/tcp_priv.h" /* for tcp_pcb_lists */
/*
* The number of TCP sockets (NR_TCPSOCK) is defined in the lwIP configuration.
*/
/*
* We fully control the send buffer, so we can let its size be set to whatever
* we want. The receive buffer is different: if it is smaller than the window
* size, we may have to refuse data that lwIP hands us, at which point more
* incoming data will cause lwIP to abort the TCP connection--even aside from
* performance issues. Therefore, we must make sure the receive buffer is
* larger than the TCP window at all times.
*/
#define TCP_SNDBUF_MIN 1 /* minimum TCP send buffer size */
#define TCP_SNDBUF_DEF 32768 /* default TCP send buffer size */
#define TCP_SNDBUF_MAX 131072 /* maximum TCP send buffer size */
#define TCP_RCVBUF_MIN TCP_WND /* minimum TCP receive buffer size */
#define TCP_RCVBUF_DEF MAX(TCP_WND, 32768) /* default TCP recv buffer size */
#define TCP_RCVBUF_MAX MAX(TCP_WND, 131072) /* maximum TCP recv buffer size */
/*
* The total number of buffers that may in use for TCP socket send queues. The
* goal is to allow at least some progress to be made on receiving from TCP
* sockets and on differently-typed sockets, at least as long as the LWIP
* service can manage to allocate the memory it wants. For the case that it
* does not, we can only reactively kill off TCP sockets and/or free enqueued
* ethernet packets, neither of which is currently implemented (TODO).
*/
#define TCP_MAX_SENDBUFS (mempool_max_buffers() * 3 / 4)
/* Polling intervals, in 500-millsecond units. */
#define TCP_POLL_REG_INTERVAL 10 /* interval for reattempting sends */
#define TCP_POLL_CLOSE_INTERVAL 1 /* interval while closing connection */
static struct tcpsock {
struct ipsock tcp_ipsock; /* IP socket, MUST be first */
struct tcp_pcb *tcp_pcb; /* lwIP TCP control block */
union pxfer_tcp_queue { /* free/accept queue */
TAILQ_ENTRY(tcpsock) tq_next; /* next in queue */
TAILQ_HEAD(, tcpsock) tq_head; /* head of queue */
} tcp_queue;
struct tcpsock *tcp_listener; /* listener if on accept q. */
struct { /* send queue */
struct pbuf *ts_head; /* first pbuf w/unacked data */
struct pbuf *ts_unsent; /* first pbuf w/unsent data */
struct pbuf *ts_tail; /* most recently added data */
size_t ts_len; /* total sent + unsent */
unsigned short ts_head_off; /* offset into head pbuf */
unsigned short ts_unsent_off; /* offset into unsent pbuf */
} tcp_snd;
struct { /* receive queue */
struct pbuf *tr_head; /* first pbuf w/unrecvd data */
struct pbuf **tr_pre_tailp; /* ptr-ptr to newest pbuf */
size_t tr_len; /* bytes on receive queue */
unsigned short tr_head_off; /* offset into head pbuf */
unsigned short tr_unacked; /* current window reduction */
} tcp_rcv;
} tcp_array[NR_TCPSOCK];
static TAILQ_HEAD(, tcpsock) tcp_freelist; /* list of free TCP sockets */
static const struct sockevent_ops tcpsock_ops;
static unsigned int tcpsock_sendbufs; /* # send buffers in use */
static unsigned int tcpsock_recvbufs; /* # receive buffers in use */
/* A bunch of macros that are just for convenience. */
#define tcpsock_get_id(tcp) (SOCKID_TCP | (sockid_t)((tcp) - tcp_array))
#define tcpsock_get_ipsock(tcp) (&(tcp)->tcp_ipsock)
#define tcpsock_get_sock(tcp) (ipsock_get_sock(tcpsock_get_ipsock(tcp)))
#define tcpsock_get_sndbuf(tcp) (ipsock_get_sndbuf(tcpsock_get_ipsock(tcp)))
#define tcpsock_get_rcvbuf(tcp) (ipsock_get_rcvbuf(tcpsock_get_ipsock(tcp)))
#define tcpsock_is_ipv6(tcp) (ipsock_is_ipv6(tcpsock_get_ipsock(tcp)))
#define tcpsock_is_shutdown(tcp,fl) \
(sockevent_is_shutdown(tcpsock_get_sock(tcp), fl))
#define tcpsock_is_listening(tcp) \
(sockevent_is_listening(tcpsock_get_sock(tcp)))
#define tcpsock_get_flags(tcp) (ipsock_get_flags(tcpsock_get_ipsock(tcp)))
#define tcpsock_set_flag(tcp,fl) \
(ipsock_set_flag(tcpsock_get_ipsock(tcp), fl))
#define tcpsock_clear_flag(tcp,fl) \
(ipsock_clear_flag(tcpsock_get_ipsock(tcp), fl))
static ssize_t tcpsock_pcblist(struct rmib_call *, struct rmib_node *,
struct rmib_oldp *, struct rmib_newp *);
/* The CTL_NET {PF_INET,PF_INET6} IPPROTO_TCP subtree. */
/* TODO: add many more and make some of them writable.. */
static struct rmib_node net_inet_tcp_table[] = {
/* 2*/ [TCPCTL_SENDSPACE] = RMIB_INT(RMIB_RO, TCP_SNDBUF_DEF,
"sendspace",
"Default TCP send buffer size"),
/* 3*/ [TCPCTL_RECVSPACE] = RMIB_INT(RMIB_RO, TCP_RCVBUF_DEF,
"recvspace",
"Default TCP receive buffer size"),
/*29*/ [TCPCTL_LOOPBACKCKSUM] = RMIB_FUNC(RMIB_RW | CTLTYPE_INT, sizeof(int),
loopif_cksum, "do_loopback_cksum",
"Perform TCP checksum on loopback"),
/*+0*/ [TCPCTL_MAXID] = RMIB_FUNC(RMIB_RO | CTLTYPE_NODE, 0,
tcpsock_pcblist, "pcblist",
"TCP protocol control block list"),
/*+1*/ [TCPCTL_MAXID + 1] = RMIB_FUNC(RMIB_RW | CTLFLAG_PRIVATE |
CTLFLAG_HIDDEN | CTLTYPE_STRING,
TCPISN_SECRET_HEX_LENGTH, tcpisn_secret,
"isn_secret",
"TCP ISN secret (MINIX 3 specific)")
};
static struct rmib_node net_inet_tcp_node =
RMIB_NODE(RMIB_RO, net_inet_tcp_table, "tcp", "TCP related settings");
static struct rmib_node net_inet6_tcp6_node =
RMIB_NODE(RMIB_RO, net_inet_tcp_table, "tcp6", "TCP related settings");
/*
* Initialize the TCP sockets module.
*/
void
tcpsock_init(void)
{
unsigned int slot;
/* Initialize the list of free TCP sockets. */
TAILQ_INIT(&tcp_freelist);
for (slot = 0; slot < __arraycount(tcp_array); slot++)
TAILQ_INSERT_TAIL(&tcp_freelist, &tcp_array[slot],
tcp_queue.tq_next);
/* Initialize other variables. */
tcpsock_sendbufs = 0;
/* Register the net.inet.tcp and net.inet6.tcp6 RMIB subtrees. */
mibtree_register_inet(PF_INET, IPPROTO_TCP, &net_inet_tcp_node);
mibtree_register_inet(PF_INET6, IPPROTO_TCP, &net_inet6_tcp6_node);
}
/*
* Initialize the state of a TCP socket's send queue.
*/
static void
tcpsock_reset_send(struct tcpsock * tcp)
{
tcp->tcp_snd.ts_tail = NULL;
tcp->tcp_snd.ts_unsent = NULL;
tcp->tcp_snd.ts_head = NULL;
tcp->tcp_snd.ts_len = 0;
tcp->tcp_snd.ts_unsent_off = 0;
tcp->tcp_snd.ts_head_off = 0;
}
/*
* Initialize the state of a TCP socket's receive queue.
*/
static void
tcpsock_reset_recv(struct tcpsock * tcp)
{
tcp->tcp_rcv.tr_pre_tailp = NULL;
tcp->tcp_rcv.tr_head = NULL;
tcp->tcp_rcv.tr_len = 0;
tcp->tcp_rcv.tr_head_off = 0;
tcp->tcp_rcv.tr_unacked = 0;
}
/*
* Create a TCP socket.
*/
sockid_t
tcpsock_socket(int domain, int protocol, struct sock ** sockp,
const struct sockevent_ops ** ops)
{
struct tcpsock *tcp;
uint8_t ip_type;
switch (protocol) {
case 0:
case IPPROTO_TCP:
break;
default:
return EPROTONOSUPPORT;
}
if (TAILQ_EMPTY(&tcp_freelist))
return ENOBUFS;
tcp = TAILQ_FIRST(&tcp_freelist);
/*
* Initialize the structure. Do not memset it to zero, as it is still
* part of the linked free list. Initialization may still fail. When
* adding new fields, make sure to change tcpsock_clone() accordingly.
*/
ip_type = ipsock_socket(tcpsock_get_ipsock(tcp), domain,
TCP_SNDBUF_DEF, TCP_RCVBUF_DEF, sockp);
if ((tcp->tcp_pcb = tcp_new_ip_type(ip_type)) == NULL)
return ENOBUFS;
tcp_arg(tcp->tcp_pcb, tcp);
tcp->tcp_listener = NULL;
tcpsock_reset_send(tcp);
tcpsock_reset_recv(tcp);
TAILQ_REMOVE(&tcp_freelist, tcp, tcp_queue.tq_next);
*ops = &tcpsock_ops;
return tcpsock_get_id(tcp);
}
/*
* Create a TCP socket for the TCP PCB 'pcb' which identifies a new connection
* incoming on listening socket 'listener'. The new socket is essentially a
* "clone" of the listening TCP socket, in that it should inherit any settings
* from the listening socket. The socket has not yet been accepted by userland
* so add it to the queue of connetions pending for the listening socket. On
* success, return OK. On failure, return a negative error code.
*/
static int
tcpsock_clone(struct tcpsock * listener, struct tcp_pcb * pcb)
{
struct tcpsock *tcp;
if (TAILQ_EMPTY(&tcp_freelist))
return ENOBUFS;
tcp = TAILQ_FIRST(&tcp_freelist);
/*
* Initialize the structure. Do not memset it to zero, as it is still
* part of the linked free list. Initialization may still fail. Most
* settings should be inherited from the listening socket here, rather
* than being initialized to their default state.
*/
ipsock_clone(tcpsock_get_ipsock(listener), tcpsock_get_ipsock(tcp),
tcpsock_get_id(tcp));
tcp->tcp_pcb = pcb;
tcp_arg(pcb, tcp);
tcpsock_reset_send(tcp);
tcpsock_reset_recv(tcp);
/*
* Remove the new socket from the free list, and add it to the queue of
* the listening socket--in this order, because the same next pointer
* is used for both.
*/
TAILQ_REMOVE(&tcp_freelist, tcp, tcp_queue.tq_next);
TAILQ_INSERT_TAIL(&listener->tcp_queue.tq_head, tcp,
tcp_queue.tq_next);
tcp->tcp_listener = listener;
return OK;
}
/*
* Allocate a buffer from the pool, using the standard pool size. The returned
* buffer is a single element--never a chain.
*/
static struct pbuf *
tcpsock_alloc_buf(void)
{
struct pbuf *pbuf;
pbuf = pbuf_alloc(PBUF_RAW, MEMPOOL_BUFSIZE, PBUF_RAM);
assert(pbuf == NULL || pbuf->len == pbuf->tot_len);
return pbuf;
}
/*
* Free the given buffer. Ensure that pbuf_free() will not attempt to free the
* next buffer(s) in the chain as well. This may be called for pbufs other
* than those allocated with tcpsock_alloc_buf().
*/
static void
tcpsock_free_buf(struct pbuf * pbuf)
{
/*
* Resetting the length is currently not necessary, but better safe
* than sorry..
*/
pbuf->len = pbuf->tot_len;
pbuf->next = NULL;
pbuf_free(pbuf);
}
/*
* Clear the send queue of a TCP socket. The caller must ensure that lwIP will
* no longer access any of data on the send queue.
*/
static void
tcpsock_clear_send(struct tcpsock * tcp)
{
struct pbuf *phead;
assert(tcp->tcp_pcb == NULL);
while ((phead = tcp->tcp_snd.ts_head) != NULL) {
tcp->tcp_snd.ts_head = phead->next;
assert(tcpsock_sendbufs > 0);
tcpsock_sendbufs--;
tcpsock_free_buf(phead);
}
tcpsock_reset_send(tcp);
}
/*
* Clear the receive queue of a TCP socket. If 'ack_data' is set, also
* acknowledge the previous contents of the receive queue to lwIP.
*/
static size_t
tcpsock_clear_recv(struct tcpsock * tcp, int ack_data)
{
struct pbuf *phead;
size_t rlen;
rlen = tcp->tcp_rcv.tr_len;
while ((phead = tcp->tcp_rcv.tr_head) != NULL) {
tcp->tcp_rcv.tr_head = phead->next;
assert(tcpsock_recvbufs > 0);
tcpsock_recvbufs--;
tcpsock_free_buf(phead);
}
/*
* From now on, we will basically be discarding incoming data as fast
* as possible, to keep the full window open at all times.
*/
if (ack_data && tcp->tcp_pcb != NULL && tcp->tcp_rcv.tr_unacked > 0)
tcp_recved(tcp->tcp_pcb, tcp->tcp_rcv.tr_unacked);
tcpsock_reset_recv(tcp);
return rlen;
}
/*
* The TCP socket's PCB has been detached from the socket, typically because
* the connection was aborted, either by us or by lwIP. Either way, any TCP
* connection is gone. Clear the socket's send queue, remove the socket from
* a listening socket's queue, and if the socket itself is ready and allowed to
* be freed, free it now. The socket is ready to be freed if it was either on
* a listening queue or being closed already. The socket is allowed to be
* freed only if 'may_free' is TRUE. If the socket is not freed, its receive
* queue is left as is, as it may still have data to be received by userland.
*/
static int
tcpsock_cleanup(struct tcpsock * tcp, int may_free)
{
int destroy;
assert(tcp->tcp_pcb == NULL);
/*
* Free any data on the send queue. This is safe to do right now,
* because the PCB has been aborted (or was already gone). We must be
* very careful about clearing the send queue in all other situations.
*/
tcpsock_clear_send(tcp);
/*
* If this was a socket pending acceptance, remove it from the
* corresponding listener socket's queue, and free it. Otherwise, free
* the socket only if it suspended a graceful close operation.
*/
if (tcp->tcp_listener != NULL) {
TAILQ_REMOVE(&tcp->tcp_listener->tcp_queue.tq_head, tcp,
tcp_queue.tq_next);
tcp->tcp_listener = NULL;
/*
* The listener socket's backlog count should be adjusted by
* lwIP whenever the PCB is freed up, so we need (and must) not
* attempt to do that here.
*/
destroy = TRUE;
} else
destroy = sockevent_is_closing(tcpsock_get_sock(tcp));
/*
* Do not free the socket if 'may_free' is FALSE. That flag may be set
* if we are currently in the second tcpsock_close() call on the
* socket, in which case sockevent_is_closing() is TRUE but we must
* still not free the socket now: doing so would derail libsockevent.
*/
if (destroy && may_free) {
(void)tcpsock_clear_recv(tcp, FALSE /*ack_data*/);
sockevent_raise(tcpsock_get_sock(tcp), SEV_CLOSE);
}
return destroy;
}
/*
* Abort the lwIP PCB for the given socket, using tcp_abort(). If the PCB is
* connected, this will cause the connection to be reset. The PCB, which must
* have still been present before the call, will be gone after the call.
*/
static void
tcpsock_pcb_abort(struct tcpsock * tcp)
{
assert(tcp->tcp_pcb != NULL);
assert(!tcpsock_is_listening(tcp));
tcp_recv(tcp->tcp_pcb, NULL);
tcp_sent(tcp->tcp_pcb, NULL);
tcp_err(tcp->tcp_pcb, NULL);
tcp_poll(tcp->tcp_pcb, NULL, TCP_POLL_REG_INTERVAL);
tcp_arg(tcp->tcp_pcb, NULL);
tcp_abort(tcp->tcp_pcb);
tcp->tcp_pcb = NULL;
}
/*
* Close the lwIP PCB for the given socket, using tcp_close(). If the PCB is
* connected, its graceful close will be finished by lwIP in the background.
* The PCB, which must have still been present before the call, will be gone
* after the call.
*/
static void
tcpsock_pcb_close(struct tcpsock * tcp)
{
err_t err;
assert(tcp->tcp_pcb != NULL);
assert(tcp->tcp_snd.ts_len == 0);
if (!tcpsock_is_listening(tcp)) {
tcp_recv(tcp->tcp_pcb, NULL);
tcp_sent(tcp->tcp_pcb, NULL);
tcp_err(tcp->tcp_pcb, NULL);
tcp_poll(tcp->tcp_pcb, NULL, TCP_POLL_REG_INTERVAL);
}
tcp_arg(tcp->tcp_pcb, NULL);
if ((err = tcp_close(tcp->tcp_pcb)) != ERR_OK)
panic("unexpected TCP close failure: %d", err);
tcp->tcp_pcb = NULL;
}
/*
* Return TRUE if all conditions are met for closing the TCP socket's PCB, or
* FALSE if they are not. Upon calling this function, the socket's PCB must
* still be around.
*/
static int
tcpsock_may_close(struct tcpsock * tcp)
{
assert(tcp->tcp_pcb != NULL);
/*
* Regular closing of the PCB requires three conditions to be met:
*
* 1. all our data has been transmitted AND acknowledged, so that we do
* not risk corruption in case there are still unsent or unack'ed
* data buffers that may otherwise be recycled too soon;
* 2. we have sent our FIN to the peer; and,
* 3. we have received a FIN from the peer.
*/
return ((tcpsock_get_flags(tcp) & (TCPF_SENT_FIN | TCPF_RCVD_FIN)) ==
(TCPF_SENT_FIN | TCPF_RCVD_FIN) && tcp->tcp_snd.ts_len == 0);
}
/*
* The given socket is ready to be closed as per the tcpsock_may_close() rules.
* This implies that its send queue is already empty. Gracefully close the
* PCB. In addition, if the socket is being closed gracefully, meaning we
* suspended an earlier tcpsock_close() call (and as such already emptied the
* receive queue as well), then tell libsockevent that the close is finished,
* freeing the socket. Return TRUE if the socket has indeed been freed this
* way, or FALSE if the socket is still around.
*/
static int
tcpsock_finish_close(struct tcpsock * tcp)
{
assert(tcp->tcp_snd.ts_len == 0);
assert(tcp->tcp_listener == NULL);
/*
* If we get here, we have already shut down the sending side of the
* PCB. Technically, we are interested only in shutting down the
* receiving side of the PCB here, so that lwIP may decide to recycle
* the socket later etcetera. We call tcp_close() because we do not
* want to rely on tcp_shutdown(RX) doing the exact same thing.
* However, we do rely on the fact that the PCB is not immediately
* destroyed by the tcp_close() call: otherwise we may have to return
* ERR_ABRT if this function is called from a lwIP-generated event.
*/
tcpsock_pcb_close(tcp);
/*
* If we suspended an earlier tcpsock_close() call, we have to tell
* libsockevent that the close operation is now complete.
*/
if (sockevent_is_closing(tcpsock_get_sock(tcp))) {
assert(tcp->tcp_rcv.tr_len == 0);
sockevent_raise(tcpsock_get_sock(tcp), SEV_CLOSE);
return TRUE;
} else
return FALSE;
}
/*
* Attempt to start or resume enqueuing data and/or a FIN to send on the given
* TCP socket. Return TRUE if anything at all could be newly enqueued on the
* lwIP PCB, even if less than desired. In that case, the caller should try to
* send whatever was enqueued, and if applicable, check if the socket may now
* be closed (due to the FIN being enqueued). In particular, in any situation
* where the socket may be in the process of being closed, the caller must use
* tcpsock_may_close() if TRUE is returned. Return FALSE if nothing new could
* be enqueued, in which case no send attempt need to be made either.
*/
static int
tcpsock_pcb_enqueue(struct tcpsock * tcp)
{
struct pbuf *punsent;
size_t space, chunk;
unsigned int flags;
err_t err;
int enqueued;
assert(tcp->tcp_pcb != NULL);
if (tcpsock_get_flags(tcp) & TCPF_FULL)
return FALSE;
/*
* Attempt to enqueue more unsent data, if any, on the PCB's send
* queue.
*/
enqueued = FALSE;
while (tcp->tcp_snd.ts_unsent != NULL) {
if ((space = tcp_sndbuf(tcp->tcp_pcb)) == 0)
break;
/*
* We may maintain a non-NULL unsent pointer even when there is
* nothing more to send right now, because the tail buffer may
* be filled up further later on.
*/
punsent = tcp->tcp_snd.ts_unsent;
assert(punsent->len >= tcp->tcp_snd.ts_unsent_off);
chunk = (size_t)punsent->len - tcp->tcp_snd.ts_unsent_off;
if (chunk == 0)
break;
if (chunk > space)
chunk = space;
/* Try to enqueue more data for sending. */
if (chunk < punsent->len || punsent->next != NULL)
flags = TCP_WRITE_FLAG_MORE;
else
flags = 0;
err = tcp_write(tcp->tcp_pcb, (char *)punsent->payload +
tcp->tcp_snd.ts_unsent_off, chunk, flags);
/*
* Since tcp_write() enqueues data only, it should only return
* out-of-memory errors; no fatal ones. In any case, stop.
*/
if (err != ERR_OK) {
assert(err == ERR_MEM);
break;
}
/* We have successfully enqueued data. */
enqueued = TRUE;
tcp->tcp_snd.ts_unsent_off += chunk;
if (tcp->tcp_snd.ts_unsent_off < punsent->tot_len) {
assert(tcp->tcp_snd.ts_unsent_off < punsent->len ||
punsent->next == NULL);
break;
}
tcp->tcp_snd.ts_unsent = punsent->next;
tcp->tcp_snd.ts_unsent_off = 0;
}
/*
* If all pending data has been enqueued for sending, and we should
* shut down the sending end of the socket, try that now.
*/
if ((tcp->tcp_snd.ts_unsent == NULL ||
tcp->tcp_snd.ts_unsent_off == tcp->tcp_snd.ts_unsent->len) &&
tcpsock_is_shutdown(tcp, SFL_SHUT_WR) &&
!(tcpsock_get_flags(tcp) & TCPF_SENT_FIN)) {
err = tcp_shutdown(tcp->tcp_pcb, 0 /*shut_rx*/, 1 /*shut_tx*/);
if (err == ERR_OK) {
/*
* We have successfully enqueued a FIN. The caller is
* now responsible for checking whether the PCB and
* possibly even the socket object can now be freed.
*/
tcpsock_set_flag(tcp, TCPF_SENT_FIN);
enqueued = TRUE;
} else {
assert(err == ERR_MEM);
/*
* FIXME: the resolution for lwIP bug #47485 has taken
* away even more control over the closing process from
* us, making tracking sockets especially for SO_LINGER
* even harder. For now, we simply effectively undo
* the patch by clearing TF_CLOSEPEND if tcp_shutdown()
* returns ERR_MEM. This will not be sustainable in
* the long term, though.
*/
tcp->tcp_pcb->flags &= ~TF_CLOSEPEND;
tcpsock_set_flag(tcp, TCPF_FULL);
}
}
return enqueued;
}
/*
* Request lwIP to start sending any enqueued data and/or FIN on the TCP
* socket's lwIP PCB. On success, return OK. On failure, return a negative
* error code, after cleaning up the socket, freeing the PCB. If the socket
* was already being closed, also free the socket object in that case; the
* caller must then not touch the socket object anymore upon return. If the
* socket object is not freed, and if 'raise_error' is TRUE, raise the error
* on the socket object.
*/
static int
tcpsock_pcb_send(struct tcpsock * tcp, int raise_error)
{
err_t err;
int r;
assert(tcp->tcp_pcb != NULL);
/*
* If we have enqueued something, ask lwIP to send TCP packets now.
* This may result in a fatal error, in which case we clean up the
* socket and return the error to the caller. Since cleaning up the
* socket may free the socket object, and the caller cannot tell
* whether that will happen or has happened, also possibly raise the
* error on the socket object if it is not gone. As such, callers that
* set 'raise_error' to FALSE must know for sure that the socket was
* not being closed, for example because the caller is processing a
* (send) call from userland.
*/
err = tcp_output(tcp->tcp_pcb);
if (err != ERR_OK && err != ERR_MEM) {
tcpsock_pcb_abort(tcp);
r = util_convert_err(err);
if (!tcpsock_cleanup(tcp, TRUE /*may_free*/)) {
if (raise_error)
sockevent_set_error(tcpsock_get_sock(tcp), r);
}
/* Otherwise, do not touch the socket object anymore! */
return r;
} else
return OK;
}
/*
* Callback from lwIP. The given number of data bytes have been acknowledged
* as received by the remote end. Dequeue and free data from the TCP socket's
* send queue as appropriate.
*/
static err_t
tcpsock_event_sent(void * arg, struct tcp_pcb * pcb __unused, uint16_t len)
{
struct tcpsock *tcp = (struct tcpsock *)arg;
struct pbuf *phead;
size_t left;
assert(tcp != NULL);
assert(pcb == tcp->tcp_pcb);
assert(len > 0);
assert(tcp->tcp_snd.ts_len >= len);
assert(tcp->tcp_snd.ts_head != NULL);
left = len;
/*
* First see if we can free up whole buffers. Check against the head
* buffer's 'len' rather than 'tot_len', or we may end up leaving an
* empty buffer on the chain.
*/
while ((phead = tcp->tcp_snd.ts_head) != NULL &&
left >= (size_t)phead->len - tcp->tcp_snd.ts_head_off) {
left -= (size_t)phead->len - tcp->tcp_snd.ts_head_off;
tcp->tcp_snd.ts_head = phead->next;
tcp->tcp_snd.ts_head_off = 0;
if (phead == tcp->tcp_snd.ts_unsent) {
assert(tcp->tcp_snd.ts_unsent_off == phead->len);
tcp->tcp_snd.ts_unsent = phead->next;
tcp->tcp_snd.ts_unsent_off = 0;
}
assert(tcpsock_sendbufs > 0);
tcpsock_sendbufs--;
tcpsock_free_buf(phead);
}
/*
* The rest of the given length is for less than the current head
* buffer.
*/
if (left > 0) {
assert(tcp->tcp_snd.ts_head != NULL);
assert((size_t)tcp->tcp_snd.ts_head->len -
tcp->tcp_snd.ts_head_off > left);
tcp->tcp_snd.ts_head_off += left;
}
tcp->tcp_snd.ts_len -= (size_t)len;
if (tcp->tcp_snd.ts_head == NULL) {
assert(tcp->tcp_snd.ts_len == 0);
assert(tcp->tcp_snd.ts_unsent == NULL);
tcp->tcp_snd.ts_tail = NULL;
} else
assert(tcp->tcp_snd.ts_len > 0);
/*
* If we emptied the send queue, and we already managed to send a FIN
* earlier, we may now have met all requirements to close the socket's
* PCB. Otherwise, we may also be able to send more now, so try to
* resume sending. Since we are invoked from the "sent" event,
* tcp_output() will not actually process anything, and so we do not
* call it either. If we did, we would have to deal with errors here.
*/
if (tcpsock_may_close(tcp)) {
if (tcpsock_finish_close(tcp))
return ERR_OK;
} else {
tcpsock_clear_flag(tcp, TCPF_FULL);
/*
* If we now manage to enqueue a FIN, we may be ready to close
* the PCB after all.
*/
if (tcpsock_pcb_enqueue(tcp)) {
if (tcpsock_may_close(tcp) &&
tcpsock_finish_close(tcp))
return ERR_OK;
}
}
/* The user may also be able to send more now. */
sockevent_raise(tcpsock_get_sock(tcp), SEV_SEND);
return ERR_OK;
}
/*
* Check whether any (additional) data previously received on a TCP socket
* should be acknowledged, possibly allowing the remote end to send additional
* data as a result.
*/
static void
tcpsock_ack_recv(struct tcpsock * tcp)
{
size_t rcvbuf, left, delta, ack;
assert(tcp->tcp_pcb != NULL);
/*
* We must make sure that at all times, we can still add an entire
* window's worth of data to the receive queue. If the amount of free
* space drops below that threshold, we stop acknowledging received
* data. The user may change the receive buffer size at all times; we
* update the window size lazily as appropriate.
*/
rcvbuf = tcpsock_get_rcvbuf(tcp);
if (rcvbuf > tcp->tcp_rcv.tr_len && tcp->tcp_rcv.tr_unacked > 0) {
/*
* The number of bytes that lwIP can still give us at any time
* is represented as 'left'. The number of bytes that we still
* allow to be stored in the receive queue is represented as
* 'delta'. We must make sure that 'left' does not ever exceed
* 'delta' while acknowledging as many bytes as possible under
* that rule.
*/
left = TCP_WND - tcp->tcp_rcv.tr_unacked;
delta = rcvbuf - tcp->tcp_rcv.tr_len;
if (left < delta) {
ack = delta - left;
if (ack > tcp->tcp_rcv.tr_unacked)
ack = tcp->tcp_rcv.tr_unacked;
tcp_recved(tcp->tcp_pcb, ack);
tcp->tcp_rcv.tr_unacked -= ack;
assert(tcp->tcp_rcv.tr_len + TCP_WND -
tcp->tcp_rcv.tr_unacked <= rcvbuf);
}
}
}
/*
* Attempt to merge two consecutive underfilled buffers in the receive queue of
* a TCP socket, freeing up one of the two buffers as a result. The first
* (oldest) buffer is 'ptail', and the pointer to this buffer is stored at
* 'pnext'. The second (new) buffer is 'pbuf', which is already attached to
* the first buffer. The second buffer may be followed by additional buffers
* with even more new data. Return TRUE if buffers have been merged, in which
* case the pointer at 'pnext' may have changed, and no assumptions should be
* made about whether 'ptail' and 'pbuf' still exist in any form. Return FALSE
* if no merging was necessary or if no new buffer could be allocated.
*/
static int
tcpsock_try_merge(struct pbuf **pnext, struct pbuf * ptail, struct pbuf * pbuf)
{
struct pbuf *pnew;
assert(*pnext == ptail);
assert(ptail->next == pbuf);
/*
* Unfortunately, we cannot figure out what kind of pbuf we were given
* by the lower layers, so we cannot merge two buffers without first
* allocating a third. Once we have done that, though, we can easily
* merge more into that new buffer. For now we use the following
* policies:
*
* 1. if two consecutive lwIP-provided buffers are both used less than
* half the size of a full buffer, try to allocate a new buffer and
* copy both lwIP-provided buffers into that new buffer, freeing up
* the pair afterwards;
* 2. if the tail buffer on the chain is allocated by us and not yet
* full, and the next buffer's contents can be added to the tail
* buffer in their entirety, do just that.
*
* Obviously there is a trade-off between the performance overhead of
* copying and the resource overhead of keeping less-than-full buffers
* on the receive queue, but this policy should both keep actual memory
* usage to no more than twice the receive queue length and prevent
* excessive copying. The policy deliberately performs more aggressive
* merging into a buffer that we allocated ourselves.
*/
if (ptail->tot_len <= MEMPOOL_BUFSIZE / 2 &&
pbuf->len <= MEMPOOL_BUFSIZE / 2) {
/*
* Case #1.
*/
assert(ptail->tot_len == ptail->len);
assert(pbuf->tot_len == pbuf->len);
pnew = tcpsock_alloc_buf();
if (pnew == NULL)
return FALSE;
memcpy(pnew->payload, ptail->payload, ptail->len);
memcpy((char *)pnew->payload + ptail->len, pbuf->payload,
pbuf->len);
pnew->len = ptail->len + pbuf->len;
assert(pnew->len <= pnew->tot_len);
pnew->next = pbuf->next;
/* For now, we need not inherit any flags from either pbuf. */
*pnext = pnew;
/* One allocated, two about to be deallocated. */
assert(tcpsock_recvbufs > 0);
tcpsock_recvbufs--;
tcpsock_free_buf(ptail);
tcpsock_free_buf(pbuf);
return TRUE;
} else if (ptail->tot_len - ptail->len >= pbuf->len) {
/*
* Case #2.
*/
memcpy((char *)ptail->payload + ptail->len, pbuf->payload,
pbuf->len);
ptail->len += pbuf->len;