source: mainline/uspace/srv/net/tcp/conn.c@ 2989c7e

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 2989c7e was 2989c7e, checked in by Jiri Svoboda <jiri@…>, 10 years ago

Association map / portrange prototype.

  • Property mode set to 100644
File size: 33.6 KB
Line 
1/*
2 * Copyright (c) 2015 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup tcp
30 * @{
31 */
32
33/**
34 * @file TCP connection processing and state machine
35 */
36
37#include <adt/list.h>
38#include <errno.h>
39#include <inet/endpoint.h>
40#include <io/log.h>
41#include <macros.h>
42#include <nettl/amap.h>
43#include <stdbool.h>
44#include <stdlib.h>
45#include "conn.h"
46#include "iqueue.h"
47#include "segment.h"
48#include "seq_no.h"
49#include "tcp_type.h"
50#include "tqueue.h"
51#include "ucall.h"
52
53#define RCV_BUF_SIZE 4096/*2*/
54#define SND_BUF_SIZE 4096
55
56#define MAX_SEGMENT_LIFETIME (15*1000*1000) //(2*60*1000*1000)
57#define TIME_WAIT_TIMEOUT (2*MAX_SEGMENT_LIFETIME)
58
59static LIST_INITIALIZE(conn_list);
60static FIBRIL_MUTEX_INITIALIZE(conn_list_lock);
61static amap_t *amap;
62
63static void tcp_conn_seg_process(tcp_conn_t *conn, tcp_segment_t *seg);
64static void tcp_conn_tw_timer_set(tcp_conn_t *conn);
65static void tcp_conn_tw_timer_clear(tcp_conn_t *conn);
66
67/** Initialize connections. */
68int tcp_conns_init(void)
69{
70 int rc;
71
72 rc = amap_create(&amap);
73 if (rc != EOK) {
74 assert(rc == ENOMEM);
75 return ENOMEM;
76 }
77
78 return EOK;
79}
80
81/** Create new connection structure.
82 *
83 * @param epp Endpoint pair (will be deeply copied)
84 * @return New connection or NULL
85 */
86tcp_conn_t *tcp_conn_new(inet_ep2_t *epp)
87{
88 tcp_conn_t *conn = NULL;
89 bool tqueue_inited = false;
90
91 /* Allocate connection structure */
92 conn = calloc(1, sizeof(tcp_conn_t));
93 if (conn == NULL)
94 goto error;
95
96 fibril_mutex_initialize(&conn->lock);
97
98 conn->tw_timer = fibril_timer_create(&conn->lock);
99 if (conn->tw_timer == NULL)
100 goto error;
101
102 /* One for the user, one for not being in closed state */
103 atomic_set(&conn->refcnt, 2);
104
105 /* Allocate receive buffer */
106 fibril_condvar_initialize(&conn->rcv_buf_cv);
107 conn->rcv_buf_size = RCV_BUF_SIZE;
108 conn->rcv_buf_used = 0;
109 conn->rcv_buf_fin = false;
110
111 conn->rcv_buf = calloc(1, conn->rcv_buf_size);
112 if (conn->rcv_buf == NULL)
113 goto error;
114
115 /** Allocate send buffer */
116 fibril_condvar_initialize(&conn->snd_buf_cv);
117 conn->snd_buf_size = SND_BUF_SIZE;
118 conn->snd_buf_used = 0;
119 conn->snd_buf_fin = false;
120 conn->snd_buf = calloc(1, conn->snd_buf_size);
121 if (conn->snd_buf == NULL)
122 goto error;
123
124 /* Set up receive window. */
125 conn->rcv_wnd = conn->rcv_buf_size;
126
127 /* Initialize incoming segment queue */
128 tcp_iqueue_init(&conn->incoming, conn);
129
130 /* Initialize retransmission queue */
131 if (tcp_tqueue_init(&conn->retransmit, conn) != EOK)
132 goto error;
133
134 tqueue_inited = true;
135
136 /* Connection state change signalling */
137 fibril_condvar_initialize(&conn->cstate_cv);
138
139 conn->cb = NULL;
140
141 conn->cstate = st_listen;
142 conn->reset = false;
143 conn->deleted = false;
144 conn->ap = ap_passive;
145 conn->fin_is_acked = false;
146 if (epp != NULL)
147 conn->ident = *epp;
148
149 return conn;
150
151error:
152 if (tqueue_inited)
153 tcp_tqueue_fini(&conn->retransmit);
154 if (conn != NULL && conn->rcv_buf != NULL)
155 free(conn->rcv_buf);
156 if (conn != NULL && conn->snd_buf != NULL)
157 free(conn->snd_buf);
158 if (conn != NULL && conn->tw_timer != NULL)
159 fibril_timer_destroy(conn->tw_timer);
160 if (conn != NULL)
161 free(conn);
162
163 return NULL;
164}
165
166/** Destroy connection structure.
167 *
168 * Connection structure should be destroyed when the folowing condtitions
169 * are met:
170 * (1) user has deleted the connection
171 * (2) the connection has entered closed state
172 * (3) nobody is holding references to the connection
173 *
174 * This happens when @a conn->refcnt is zero as we count (1) and (2)
175 * as special references.
176 *
177 * @param conn Connection
178 */
179static void tcp_conn_free(tcp_conn_t *conn)
180{
181 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_conn_free(%p)", conn->name, conn);
182 tcp_tqueue_fini(&conn->retransmit);
183
184 if (conn->rcv_buf != NULL)
185 free(conn->rcv_buf);
186 if (conn->snd_buf != NULL)
187 free(conn->snd_buf);
188 if (conn->tw_timer != NULL)
189 fibril_timer_destroy(conn->tw_timer);
190 free(conn);
191}
192
193/** Add reference to connection.
194 *
195 * Increase connection reference count by one.
196 *
197 * @param conn Connection
198 */
199void tcp_conn_addref(tcp_conn_t *conn)
200{
201 log_msg(LOG_DEFAULT, LVL_DEBUG2, "%s: tcp_conn_addref(%p)", conn->name, conn);
202 atomic_inc(&conn->refcnt);
203}
204
205/** Remove reference from connection.
206 *
207 * Decrease connection reference count by one.
208 *
209 * @param conn Connection
210 */
211void tcp_conn_delref(tcp_conn_t *conn)
212{
213 log_msg(LOG_DEFAULT, LVL_DEBUG2, "%s: tcp_conn_delref(%p)", conn->name, conn);
214
215 if (atomic_predec(&conn->refcnt) == 0)
216 tcp_conn_free(conn);
217}
218
219/** Lock connection.
220 *
221 * Must be called before any other connection-manipulating function,
222 * except tcp_conn_{add|del}ref(). Locks the connection including
223 * its timers. Must not be called inside any of the connection
224 * timer handlers.
225 *
226 * @param conn Connection
227 */
228void tcp_conn_lock(tcp_conn_t *conn)
229{
230 fibril_mutex_lock(&conn->lock);
231}
232
233/** Unlock connection.
234 *
235 * @param conn Connection
236 */
237void tcp_conn_unlock(tcp_conn_t *conn)
238{
239 fibril_mutex_unlock(&conn->lock);
240}
241
242/** Delete connection.
243 *
244 * The caller promises not make no further references to @a conn.
245 * TCP will free @a conn eventually.
246 *
247 * @param conn Connection
248 */
249void tcp_conn_delete(tcp_conn_t *conn)
250{
251 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_conn_delete(%p)", conn->name, conn);
252
253 assert(conn->deleted == false);
254 conn->deleted = true;
255 conn->cb = NULL;
256 conn->cb_arg = NULL;
257 tcp_conn_delref(conn);
258}
259
260/** Enlist connection.
261 *
262 * Add connection to the connection map.
263 */
264int tcp_conn_add(tcp_conn_t *conn)
265{
266 inet_ep2_t aepp;
267 int rc;
268
269 tcp_conn_addref(conn);
270 fibril_mutex_lock(&conn_list_lock);
271
272 rc = amap_insert(amap, &conn->ident, conn, af_allow_system, &aepp);
273 if (rc != EOK) {
274 tcp_conn_delref(conn);
275 fibril_mutex_unlock(&conn_list_lock);
276 return rc;
277 }
278
279 conn->ident = aepp;
280 list_append(&conn->link, &conn_list);
281 fibril_mutex_unlock(&conn_list_lock);
282
283 return EOK;
284}
285
286/** Delist connection.
287 *
288 * Remove connection from the connection map.
289 */
290void tcp_conn_remove(tcp_conn_t *conn)
291{
292 fibril_mutex_lock(&conn_list_lock);
293 amap_remove(amap, &conn->ident);
294 list_remove(&conn->link);
295 fibril_mutex_unlock(&conn_list_lock);
296 tcp_conn_delref(conn);
297}
298
299static void tcp_conn_state_set(tcp_conn_t *conn, tcp_cstate_t nstate)
300{
301 tcp_cstate_t old_state;
302
303 log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_state_set(%p)", conn);
304
305 old_state = conn->cstate;
306 conn->cstate = nstate;
307 fibril_condvar_broadcast(&conn->cstate_cv);
308
309 /* Run user callback function */
310 if (conn->cb != NULL && conn->cb->cstate_change != NULL) {
311 log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_state_set() - run user CB");
312 conn->cb->cstate_change(conn, conn->cb_arg, old_state);
313 } else {
314 log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_state_set() - no user CB");
315 }
316
317 assert(old_state != st_closed);
318 if (nstate == st_closed) {
319 /* Drop one reference for now being in closed state */
320 tcp_conn_delref(conn);
321 }
322}
323
324/** Synchronize connection.
325 *
326 * This is the first step of an active connection attempt,
327 * sends out SYN and sets up ISS and SND.xxx.
328 */
329void tcp_conn_sync(tcp_conn_t *conn)
330{
331 /* XXX select ISS */
332 conn->iss = 1;
333 conn->snd_nxt = conn->iss;
334 conn->snd_una = conn->iss;
335 conn->ap = ap_active;
336
337 tcp_tqueue_ctrl_seg(conn, CTL_SYN);
338 tcp_conn_state_set(conn, st_syn_sent);
339}
340
341/** FIN has been sent.
342 *
343 * This function should be called when FIN is sent over the connection,
344 * as a result the connection state is changed appropriately.
345 */
346void tcp_conn_fin_sent(tcp_conn_t *conn)
347{
348 switch (conn->cstate) {
349 case st_syn_received:
350 case st_established:
351 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: FIN sent -> Fin-Wait-1", conn->name);
352 tcp_conn_state_set(conn, st_fin_wait_1);
353 break;
354 case st_close_wait:
355 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: FIN sent -> Last-Ack", conn->name);
356 tcp_conn_state_set(conn, st_last_ack);
357 break;
358 default:
359 log_msg(LOG_DEFAULT, LVL_ERROR, "%s: Connection state %d", conn->name,
360 conn->cstate);
361 assert(false);
362 }
363
364 conn->fin_is_acked = false;
365}
366
367/** Match endpoint with pattern. */
368static bool tcp_ep_match(inet_ep_t *ep, inet_ep_t *patt)
369{
370 log_msg(LOG_DEFAULT, LVL_DEBUG2,
371 "tcp_ep_match(ep=(%u), pat=(%u))", ep->port, patt->port);
372
373 if ((!inet_addr_is_any(&patt->addr)) &&
374 (!inet_addr_compare(&patt->addr, &ep->addr)))
375 return false;
376
377 if ((patt->port != inet_port_any) &&
378 (patt->port != ep->port))
379 return false;
380
381 log_msg(LOG_DEFAULT, LVL_DEBUG2, " -> match");
382
383 return true;
384}
385
386/** Match endpoint pair with pattern. */
387static bool tcp_ep2_match(inet_ep2_t *epp, inet_ep2_t *pattern)
388{
389 log_msg(LOG_DEFAULT, LVL_DEBUG2, "tcp_ep2_match(%p, %p)", epp, pattern);
390
391 if (!tcp_ep_match(&epp->local, &pattern->local))
392 return false;
393
394 if (!tcp_ep_match(&epp->remote, &pattern->remote))
395 return false;
396
397 return true;
398}
399
400/** Find connection structure for specified endpoint pair.
401 *
402 * A connection is uniquely identified by a endpoint pair. Look up our
403 * connection map and return connection structure based on endpoint pair.
404 * The connection reference count is bumped by one.
405 *
406 * @param epp Endpoint pair
407 * @return Connection structure or NULL if not found.
408 */
409tcp_conn_t *tcp_conn_find_ref(inet_ep2_t *epp)
410{
411 log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_find_ref(%p)", epp);
412
413 log_msg(LOG_DEFAULT, LVL_DEBUG2, "compare conn (f:(%u), l:(%u))",
414 epp->remote.port, epp->local.port);
415
416 fibril_mutex_lock(&conn_list_lock);
417
418 list_foreach(conn_list, link, tcp_conn_t, conn) {
419 inet_ep2_t *cepp = &conn->ident;
420
421 log_msg(LOG_DEFAULT, LVL_DEBUG2, " - with (f:(%u), l:(%u))",
422 cepp->remote.port, cepp->local.port);
423
424 if (tcp_ep2_match(epp, cepp)) {
425 tcp_conn_addref(conn);
426 fibril_mutex_unlock(&conn_list_lock);
427 return conn;
428 }
429 }
430
431 fibril_mutex_unlock(&conn_list_lock);
432 return NULL;
433}
434
435/** Reset connection.
436 *
437 * @param conn Connection
438 */
439void tcp_conn_reset(tcp_conn_t *conn)
440{
441 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_conn_reset()", conn->name);
442 tcp_conn_state_set(conn, st_closed);
443 conn->reset = true;
444
445 tcp_conn_tw_timer_clear(conn);
446 tcp_tqueue_clear(&conn->retransmit);
447
448 fibril_condvar_broadcast(&conn->rcv_buf_cv);
449 fibril_condvar_broadcast(&conn->snd_buf_cv);
450}
451
452/** Signal to the user that connection has been reset.
453 *
454 * Send an out-of-band signal to the user.
455 */
456static void tcp_reset_signal(tcp_conn_t *conn)
457{
458 /* TODO */
459 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_reset_signal()", conn->name);
460}
461
462/** Determine if SYN has been received.
463 *
464 * @param conn Connection
465 * @return @c true if SYN has been received, @c false otherwise.
466 */
467bool tcp_conn_got_syn(tcp_conn_t *conn)
468{
469 switch (conn->cstate) {
470 case st_listen:
471 case st_syn_sent:
472 return false;
473 case st_syn_received:
474 case st_established:
475 case st_fin_wait_1:
476 case st_fin_wait_2:
477 case st_close_wait:
478 case st_closing:
479 case st_last_ack:
480 case st_time_wait:
481 return true;
482 case st_closed:
483 log_msg(LOG_DEFAULT, LVL_WARN, "state=%d", (int) conn->cstate);
484 assert(false);
485 }
486
487 assert(false);
488}
489
490/** Segment arrived in Listen state.
491 *
492 * @param conn Connection
493 * @param seg Segment
494 */
495static void tcp_conn_sa_listen(tcp_conn_t *conn, tcp_segment_t *seg)
496{
497 log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_sa_listen(%p, %p)", conn, seg);
498
499 if ((seg->ctrl & CTL_RST) != 0) {
500 log_msg(LOG_DEFAULT, LVL_DEBUG, "Ignoring incoming RST.");
501 return;
502 }
503
504 if ((seg->ctrl & CTL_ACK) != 0) {
505 log_msg(LOG_DEFAULT, LVL_DEBUG, "Incoming ACK, send acceptable RST.");
506 tcp_reply_rst(&conn->ident, seg);
507 return;
508 }
509
510 if ((seg->ctrl & CTL_SYN) == 0) {
511 log_msg(LOG_DEFAULT, LVL_DEBUG, "SYN not present. Ignoring segment.");
512 return;
513 }
514
515 log_msg(LOG_DEFAULT, LVL_DEBUG, "Got SYN, sending SYN, ACK.");
516
517 conn->rcv_nxt = seg->seq + 1;
518 conn->irs = seg->seq;
519
520
521 log_msg(LOG_DEFAULT, LVL_DEBUG, "rcv_nxt=%u", conn->rcv_nxt);
522
523 if (seg->len > 1)
524 log_msg(LOG_DEFAULT, LVL_WARN, "SYN combined with data, ignoring data.");
525
526 /* XXX select ISS */
527 conn->iss = 1;
528 conn->snd_nxt = conn->iss;
529 conn->snd_una = conn->iss;
530
531 /*
532 * Surprisingly the spec does not deal with initial window setting.
533 * Set SND.WND = SEG.WND and set SND.WL1 so that next segment
534 * will always be accepted as new window setting.
535 */
536 conn->snd_wnd = seg->wnd;
537 conn->snd_wl1 = seg->seq;
538 conn->snd_wl2 = seg->seq;
539
540 tcp_conn_state_set(conn, st_syn_received);
541
542 tcp_tqueue_ctrl_seg(conn, CTL_SYN | CTL_ACK /* XXX */);
543
544 tcp_segment_delete(seg);
545}
546
547/** Segment arrived in Syn-Sent state.
548 *
549 * @param conn Connection
550 * @param seg Segment
551 */
552static void tcp_conn_sa_syn_sent(tcp_conn_t *conn, tcp_segment_t *seg)
553{
554 log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_sa_syn_sent(%p, %p)", conn, seg);
555
556 if ((seg->ctrl & CTL_ACK) != 0) {
557 log_msg(LOG_DEFAULT, LVL_DEBUG, "snd_una=%u, seg.ack=%u, snd_nxt=%u",
558 conn->snd_una, seg->ack, conn->snd_nxt);
559 if (!seq_no_ack_acceptable(conn, seg->ack)) {
560 if ((seg->ctrl & CTL_RST) == 0) {
561 log_msg(LOG_DEFAULT, LVL_WARN, "ACK not acceptable, send RST");
562 tcp_reply_rst(&conn->ident, seg);
563 } else {
564 log_msg(LOG_DEFAULT, LVL_WARN, "RST,ACK not acceptable, drop");
565 }
566 return;
567 }
568 }
569
570 if ((seg->ctrl & CTL_RST) != 0) {
571 /* If we get here, we have either an acceptable ACK or no ACK */
572 if ((seg->ctrl & CTL_ACK) != 0) {
573 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: Connection reset. -> Closed",
574 conn->name);
575 /* Reset connection */
576 tcp_conn_reset(conn);
577 return;
578 } else {
579 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: RST without ACK, drop",
580 conn->name);
581 return;
582 }
583 }
584
585 /* XXX precedence */
586
587 if ((seg->ctrl & CTL_SYN) == 0) {
588 log_msg(LOG_DEFAULT, LVL_DEBUG, "No SYN bit, ignoring segment.");
589 return;
590 }
591
592 conn->rcv_nxt = seg->seq + 1;
593 conn->irs = seg->seq;
594
595 if ((seg->ctrl & CTL_ACK) != 0) {
596 conn->snd_una = seg->ack;
597
598 /*
599 * Prune acked segments from retransmission queue and
600 * possibly transmit more data.
601 */
602 tcp_tqueue_ack_received(conn);
603 }
604
605 log_msg(LOG_DEFAULT, LVL_DEBUG, "Sent SYN, got SYN.");
606
607 /*
608 * Surprisingly the spec does not deal with initial window setting.
609 * Set SND.WND = SEG.WND and set SND.WL1 so that next segment
610 * will always be accepted as new window setting.
611 */
612 log_msg(LOG_DEFAULT, LVL_DEBUG, "SND.WND := %" PRIu32 ", SND.WL1 := %" PRIu32 ", "
613 "SND.WL2 = %" PRIu32, seg->wnd, seg->seq, seg->seq);
614 conn->snd_wnd = seg->wnd;
615 conn->snd_wl1 = seg->seq;
616 conn->snd_wl2 = seg->seq;
617
618 if (seq_no_syn_acked(conn)) {
619 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: syn acked -> Established", conn->name);
620 tcp_conn_state_set(conn, st_established);
621 tcp_tqueue_ctrl_seg(conn, CTL_ACK /* XXX */);
622 } else {
623 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: syn not acked -> Syn-Received",
624 conn->name);
625 tcp_conn_state_set(conn, st_syn_received);
626 tcp_tqueue_ctrl_seg(conn, CTL_SYN | CTL_ACK /* XXX */);
627 }
628
629 tcp_segment_delete(seg);
630}
631
632/** Segment arrived in state where segments are processed in sequence order.
633 *
634 * Queue segment in incoming segments queue for processing.
635 *
636 * @param conn Connection
637 * @param seg Segment
638 */
639static void tcp_conn_sa_queue(tcp_conn_t *conn, tcp_segment_t *seg)
640{
641 tcp_segment_t *pseg;
642
643 log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_sa_seq(%p, %p)", conn, seg);
644
645 /* Discard unacceptable segments ("old duplicates") */
646 if (!seq_no_segment_acceptable(conn, seg)) {
647 log_msg(LOG_DEFAULT, LVL_DEBUG, "Replying ACK to unacceptable segment.");
648 tcp_tqueue_ctrl_seg(conn, CTL_ACK);
649 tcp_segment_delete(seg);
650 return;
651 }
652
653 /* Queue for processing */
654 tcp_iqueue_insert_seg(&conn->incoming, seg);
655
656 /*
657 * Process all segments from incoming queue that are ready.
658 * Unacceptable segments are discarded by tcp_iqueue_get_ready_seg().
659 *
660 * XXX Need to return ACK for unacceptable segments
661 */
662 while (tcp_iqueue_get_ready_seg(&conn->incoming, &pseg) == EOK)
663 tcp_conn_seg_process(conn, pseg);
664}
665
666/** Process segment RST field.
667 *
668 * @param conn Connection
669 * @param seg Segment
670 * @return cp_done if we are done with this segment, cp_continue
671 * if not
672 */
673static cproc_t tcp_conn_seg_proc_rst(tcp_conn_t *conn, tcp_segment_t *seg)
674{
675 if ((seg->ctrl & CTL_RST) == 0)
676 return cp_continue;
677
678 switch (conn->cstate) {
679 case st_syn_received:
680 /* XXX In case of passive open, revert to Listen state */
681 if (conn->ap == ap_passive) {
682 tcp_conn_state_set(conn, st_listen);
683 /* XXX Revert conn->ident */
684 tcp_conn_tw_timer_clear(conn);
685 tcp_tqueue_clear(&conn->retransmit);
686 } else {
687 tcp_conn_reset(conn);
688 }
689 break;
690 case st_established:
691 case st_fin_wait_1:
692 case st_fin_wait_2:
693 case st_close_wait:
694 /* General "connection reset" signal */
695 tcp_reset_signal(conn);
696 tcp_conn_reset(conn);
697 break;
698 case st_closing:
699 case st_last_ack:
700 case st_time_wait:
701 tcp_conn_reset(conn);
702 break;
703 case st_listen:
704 case st_syn_sent:
705 case st_closed:
706 assert(false);
707 }
708
709 return cp_done;
710}
711
712/** Process segment security and precedence fields.
713 *
714 * @param conn Connection
715 * @param seg Segment
716 * @return cp_done if we are done with this segment, cp_continue
717 * if not
718 */
719static cproc_t tcp_conn_seg_proc_sp(tcp_conn_t *conn, tcp_segment_t *seg)
720{
721 /* TODO */
722 return cp_continue;
723}
724
725/** Process segment SYN field.
726 *
727 * @param conn Connection
728 * @param seg Segment
729 * @return cp_done if we are done with this segment, cp_continue
730 * if not
731 */
732static cproc_t tcp_conn_seg_proc_syn(tcp_conn_t *conn, tcp_segment_t *seg)
733{
734 if ((seg->ctrl & CTL_SYN) == 0)
735 return cp_continue;
736
737 /*
738 * Assert SYN is in receive window, otherwise this step should not
739 * be reached.
740 */
741 assert(seq_no_in_rcv_wnd(conn, seg->seq));
742
743 log_msg(LOG_DEFAULT, LVL_WARN, "SYN is in receive window, should send reset. XXX");
744
745 /*
746 * TODO
747 *
748 * Send a reset, resond "reset" to all outstanding RECEIVEs and SEND,
749 * flush segment queues. Send unsolicited "connection reset" signal
750 * to user, connection -> closed state, delete TCB, return.
751 */
752 return cp_done;
753}
754
755/** Process segment ACK field in Syn-Received state.
756 *
757 * @param conn Connection
758 * @param seg Segment
759 * @return cp_done if we are done with this segment, cp_continue
760 * if not
761 */
762static cproc_t tcp_conn_seg_proc_ack_sr(tcp_conn_t *conn, tcp_segment_t *seg)
763{
764 if (!seq_no_ack_acceptable(conn, seg->ack)) {
765 /* ACK is not acceptable, send RST. */
766 log_msg(LOG_DEFAULT, LVL_WARN, "Segment ACK not acceptable, sending RST.");
767 tcp_reply_rst(&conn->ident, seg);
768 tcp_segment_delete(seg);
769 return cp_done;
770 }
771
772 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: SYN ACKed -> Established", conn->name);
773
774 tcp_conn_state_set(conn, st_established);
775
776 /* XXX Not mentioned in spec?! */
777 conn->snd_una = seg->ack;
778
779 return cp_continue;
780}
781
782/** Process segment ACK field in Established state.
783 *
784 * @param conn Connection
785 * @param seg Segment
786 * @return cp_done if we are done with this segment, cp_continue
787 * if not
788 */
789static cproc_t tcp_conn_seg_proc_ack_est(tcp_conn_t *conn, tcp_segment_t *seg)
790{
791 log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_seg_proc_ack_est(%p, %p)", conn, seg);
792
793 log_msg(LOG_DEFAULT, LVL_DEBUG, "SEG.ACK=%u, SND.UNA=%u, SND.NXT=%u",
794 (unsigned)seg->ack, (unsigned)conn->snd_una,
795 (unsigned)conn->snd_nxt);
796
797 if (!seq_no_ack_acceptable(conn, seg->ack)) {
798 log_msg(LOG_DEFAULT, LVL_DEBUG, "ACK not acceptable.");
799 if (!seq_no_ack_duplicate(conn, seg->ack)) {
800 log_msg(LOG_DEFAULT, LVL_WARN, "Not acceptable, not duplicate. "
801 "Send ACK and drop.");
802 /* Not acceptable, not duplicate. Send ACK and drop. */
803 tcp_tqueue_ctrl_seg(conn, CTL_ACK);
804 tcp_segment_delete(seg);
805 return cp_done;
806 } else {
807 log_msg(LOG_DEFAULT, LVL_DEBUG, "Ignoring duplicate ACK.");
808 }
809 } else {
810 /* Update SND.UNA */
811 conn->snd_una = seg->ack;
812 }
813
814 if (seq_no_new_wnd_update(conn, seg)) {
815 conn->snd_wnd = seg->wnd;
816 conn->snd_wl1 = seg->seq;
817 conn->snd_wl2 = seg->ack;
818
819 log_msg(LOG_DEFAULT, LVL_DEBUG, "Updating send window, SND.WND=%" PRIu32
820 ", SND.WL1=%" PRIu32 ", SND.WL2=%" PRIu32,
821 conn->snd_wnd, conn->snd_wl1, conn->snd_wl2);
822 }
823
824 /*
825 * Prune acked segments from retransmission queue and
826 * possibly transmit more data.
827 */
828 tcp_tqueue_ack_received(conn);
829
830 return cp_continue;
831}
832
833/** Process segment ACK field in Fin-Wait-1 state.
834 *
835 * @param conn Connection
836 * @param seg Segment
837 * @return cp_done if we are done with this segment, cp_continue
838 * if not
839 */
840static cproc_t tcp_conn_seg_proc_ack_fw1(tcp_conn_t *conn, tcp_segment_t *seg)
841{
842 if (tcp_conn_seg_proc_ack_est(conn, seg) == cp_done)
843 return cp_done;
844
845 if (conn->fin_is_acked) {
846 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: FIN acked -> Fin-Wait-2", conn->name);
847 tcp_conn_state_set(conn, st_fin_wait_2);
848 }
849
850 return cp_continue;
851}
852
853/** Process segment ACK field in Fin-Wait-2 state.
854 *
855 * @param conn Connection
856 * @param seg Segment
857 * @return cp_done if we are done with this segment, cp_continue
858 * if not
859 */
860static cproc_t tcp_conn_seg_proc_ack_fw2(tcp_conn_t *conn, tcp_segment_t *seg)
861{
862 if (tcp_conn_seg_proc_ack_est(conn, seg) == cp_done)
863 return cp_done;
864
865 /* TODO */
866 return cp_continue;
867}
868
869/** Process segment ACK field in Close-Wait state.
870 *
871 * @param conn Connection
872 * @param seg Segment
873 * @return cp_done if we are done with this segment, cp_continue
874 * if not
875 */
876static cproc_t tcp_conn_seg_proc_ack_cw(tcp_conn_t *conn, tcp_segment_t *seg)
877{
878 /* The same processing as in Established state */
879 return tcp_conn_seg_proc_ack_est(conn, seg);
880}
881
882/** Process segment ACK field in Closing state.
883 *
884 * @param conn Connection
885 * @param seg Segment
886 * @return cp_done if we are done with this segment, cp_continue
887 * if not
888 */
889static cproc_t tcp_conn_seg_proc_ack_cls(tcp_conn_t *conn, tcp_segment_t *seg)
890{
891 if (tcp_conn_seg_proc_ack_est(conn, seg) == cp_done)
892 return cp_done;
893
894 /* TODO */
895 return cp_continue;
896}
897
898/** Process segment ACK field in Last-Ack state.
899 *
900 * @param conn Connection
901 * @param seg Segment
902 * @return cp_done if we are done with this segment, cp_continue
903 * if not
904 */
905static cproc_t tcp_conn_seg_proc_ack_la(tcp_conn_t *conn, tcp_segment_t *seg)
906{
907 if (tcp_conn_seg_proc_ack_est(conn, seg) == cp_done)
908 return cp_done;
909
910 if (conn->fin_is_acked) {
911 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: FIN acked -> Closed", conn->name);
912 tcp_conn_remove(conn);
913 tcp_conn_state_set(conn, st_closed);
914 return cp_done;
915 }
916
917 return cp_continue;
918}
919
920/** Process segment ACK field in Time-Wait state.
921 *
922 * @param conn Connection
923 * @param seg Segment
924 * @return cp_done if we are done with this segment, cp_continue
925 * if not
926 */
927static cproc_t tcp_conn_seg_proc_ack_tw(tcp_conn_t *conn, tcp_segment_t *seg)
928{
929 /* Nothing to do */
930 return cp_continue;
931}
932
933/** Process segment ACK field.
934 *
935 * @param conn Connection
936 * @param seg Segment
937 * @return cp_done if we are done with this segment, cp_continue
938 * if not
939 */
940static cproc_t tcp_conn_seg_proc_ack(tcp_conn_t *conn, tcp_segment_t *seg)
941{
942 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_conn_seg_proc_ack(%p, %p)",
943 conn->name, conn, seg);
944
945 if ((seg->ctrl & CTL_ACK) == 0) {
946 log_msg(LOG_DEFAULT, LVL_WARN, "Segment has no ACK. Dropping.");
947 tcp_segment_delete(seg);
948 return cp_done;
949 }
950
951 switch (conn->cstate) {
952 case st_syn_received:
953 return tcp_conn_seg_proc_ack_sr(conn, seg);
954 case st_established:
955 return tcp_conn_seg_proc_ack_est(conn, seg);
956 case st_fin_wait_1:
957 return tcp_conn_seg_proc_ack_fw1(conn, seg);
958 case st_fin_wait_2:
959 return tcp_conn_seg_proc_ack_fw2(conn, seg);
960 case st_close_wait:
961 return tcp_conn_seg_proc_ack_cw(conn, seg);
962 case st_closing:
963 return tcp_conn_seg_proc_ack_cls(conn, seg);
964 case st_last_ack:
965 return tcp_conn_seg_proc_ack_la(conn, seg);
966 case st_time_wait:
967 return tcp_conn_seg_proc_ack_tw(conn, seg);
968 case st_listen:
969 case st_syn_sent:
970 case st_closed:
971 assert(false);
972 }
973
974 assert(false);
975}
976
977/** Process segment URG field.
978 *
979 * @param conn Connection
980 * @param seg Segment
981 * @return cp_done if we are done with this segment, cp_continue
982 * if not
983 */
984static cproc_t tcp_conn_seg_proc_urg(tcp_conn_t *conn, tcp_segment_t *seg)
985{
986 return cp_continue;
987}
988
989/** Process segment text.
990 *
991 * @param conn Connection
992 * @param seg Segment
993 * @return cp_done if we are done with this segment, cp_continue
994 * if not
995 */
996static cproc_t tcp_conn_seg_proc_text(tcp_conn_t *conn, tcp_segment_t *seg)
997{
998 size_t text_size;
999 size_t xfer_size;
1000
1001 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_conn_seg_proc_text(%p, %p)",
1002 conn->name, conn, seg);
1003
1004 switch (conn->cstate) {
1005 case st_established:
1006 case st_fin_wait_1:
1007 case st_fin_wait_2:
1008 /* OK */
1009 break;
1010 case st_close_wait:
1011 case st_closing:
1012 case st_last_ack:
1013 case st_time_wait:
1014 /* Invalid since FIN has been received. Ignore text. */
1015 return cp_continue;
1016 case st_listen:
1017 case st_syn_sent:
1018 case st_syn_received:
1019 case st_closed:
1020 assert(false);
1021 }
1022
1023 /*
1024 * Process segment text
1025 */
1026 assert(seq_no_segment_ready(conn, seg));
1027
1028 /* Trim anything outside our receive window */
1029 tcp_conn_trim_seg_to_wnd(conn, seg);
1030
1031 /* Determine how many bytes to copy */
1032 text_size = tcp_segment_text_size(seg);
1033 xfer_size = min(text_size, conn->rcv_buf_size - conn->rcv_buf_used);
1034
1035 /* Copy data to receive buffer */
1036 tcp_segment_text_copy(seg, conn->rcv_buf + conn->rcv_buf_used,
1037 xfer_size);
1038 conn->rcv_buf_used += xfer_size;
1039
1040 /* Signal to the receive function that new data has arrived */
1041 fibril_condvar_broadcast(&conn->rcv_buf_cv);
1042 if (conn->cb != NULL && conn->cb->recv_data != NULL)
1043 conn->cb->recv_data(conn, conn->cb_arg);
1044
1045 log_msg(LOG_DEFAULT, LVL_DEBUG, "Received %zu bytes of data.", xfer_size);
1046
1047 /* Advance RCV.NXT */
1048 conn->rcv_nxt += xfer_size;
1049
1050 /* Update receive window. XXX Not an efficient strategy. */
1051 conn->rcv_wnd -= xfer_size;
1052
1053 /* Send ACK */
1054 if (xfer_size > 0)
1055 tcp_tqueue_ctrl_seg(conn, CTL_ACK);
1056
1057 if (xfer_size < seg->len) {
1058 /* Trim part of segment which we just received */
1059 tcp_conn_trim_seg_to_wnd(conn, seg);
1060 } else {
1061 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: Nothing left in segment, dropping "
1062 "(xfer_size=%zu, SEG.LEN=%" PRIu32 ", seg->ctrl=%u)",
1063 conn->name, xfer_size, seg->len, (unsigned int) seg->ctrl);
1064 /* Nothing left in segment */
1065 tcp_segment_delete(seg);
1066 return cp_done;
1067 }
1068
1069 return cp_continue;
1070}
1071
1072/** Process segment FIN field.
1073 *
1074 * @param conn Connection
1075 * @param seg Segment
1076 * @return cp_done if we are done with this segment, cp_continue
1077 * if not
1078 */
1079static cproc_t tcp_conn_seg_proc_fin(tcp_conn_t *conn, tcp_segment_t *seg)
1080{
1081 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_conn_seg_proc_fin(%p, %p)",
1082 conn->name, conn, seg);
1083 log_msg(LOG_DEFAULT, LVL_DEBUG, " seg->len=%zu, seg->ctl=%u", (size_t) seg->len,
1084 (unsigned) seg->ctrl);
1085
1086 /* Only process FIN if no text is left in segment. */
1087 if (tcp_segment_text_size(seg) == 0 && (seg->ctrl & CTL_FIN) != 0) {
1088 log_msg(LOG_DEFAULT, LVL_DEBUG, " - FIN found in segment.");
1089
1090 /* Send ACK */
1091 tcp_tqueue_ctrl_seg(conn, CTL_ACK);
1092
1093 conn->rcv_nxt++;
1094 conn->rcv_wnd--;
1095
1096 /* Change connection state */
1097 switch (conn->cstate) {
1098 case st_listen:
1099 case st_syn_sent:
1100 case st_closed:
1101 /* Connection not synchronized */
1102 assert(false);
1103 case st_syn_received:
1104 case st_established:
1105 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: FIN received -> Close-Wait",
1106 conn->name);
1107 tcp_conn_state_set(conn, st_close_wait);
1108 break;
1109 case st_fin_wait_1:
1110 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: FIN received -> Closing",
1111 conn->name);
1112 tcp_conn_state_set(conn, st_closing);
1113 break;
1114 case st_fin_wait_2:
1115 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: FIN received -> Time-Wait",
1116 conn->name);
1117 tcp_conn_state_set(conn, st_time_wait);
1118 /* Start the Time-Wait timer */
1119 tcp_conn_tw_timer_set(conn);
1120 break;
1121 case st_close_wait:
1122 case st_closing:
1123 case st_last_ack:
1124 /* Do nothing */
1125 break;
1126 case st_time_wait:
1127 /* Restart the Time-Wait timer */
1128 tcp_conn_tw_timer_set(conn);
1129 break;
1130 }
1131
1132 /* Add FIN to the receive buffer */
1133 conn->rcv_buf_fin = true;
1134 fibril_condvar_broadcast(&conn->rcv_buf_cv);
1135 if (conn->cb != NULL && conn->cb->recv_data != NULL)
1136 conn->cb->recv_data(conn, conn->cb_arg);
1137
1138 tcp_segment_delete(seg);
1139 return cp_done;
1140 }
1141
1142 return cp_continue;
1143}
1144
1145/** Process incoming segment.
1146 *
1147 * We are in connection state where segments are processed in order
1148 * of sequence number. This processes one segment taken from the
1149 * connection incoming segments queue.
1150 *
1151 * @param conn Connection
1152 * @param seg Segment
1153 */
1154static void tcp_conn_seg_process(tcp_conn_t *conn, tcp_segment_t *seg)
1155{
1156 log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_seg_process(%p, %p)", conn, seg);
1157 tcp_segment_dump(seg);
1158
1159 /* Check whether segment is acceptable */
1160 /* XXX Permit valid ACKs, URGs and RSTs */
1161/* if (!seq_no_segment_acceptable(conn, seg)) {
1162 log_msg(LOG_DEFAULT, LVL_WARN, "Segment not acceptable, dropping.");
1163 if ((seg->ctrl & CTL_RST) == 0) {
1164 tcp_tqueue_ctrl_seg(conn, CTL_ACK);
1165 }
1166 return;
1167 }
1168*/
1169
1170 if (tcp_conn_seg_proc_rst(conn, seg) == cp_done)
1171 return;
1172
1173 if (tcp_conn_seg_proc_sp(conn, seg) == cp_done)
1174 return;
1175
1176 if (tcp_conn_seg_proc_syn(conn, seg) == cp_done)
1177 return;
1178
1179 if (tcp_conn_seg_proc_ack(conn, seg) == cp_done)
1180 return;
1181
1182 if (tcp_conn_seg_proc_urg(conn, seg) == cp_done)
1183 return;
1184
1185 if (tcp_conn_seg_proc_text(conn, seg) == cp_done)
1186 return;
1187
1188 if (tcp_conn_seg_proc_fin(conn, seg) == cp_done)
1189 return;
1190
1191 /*
1192 * If anything is left from the segment, insert it back into the
1193 * incoming segments queue.
1194 */
1195 if (seg->len > 0) {
1196 log_msg(LOG_DEFAULT, LVL_DEBUG, "Re-insert segment %p. seg->len=%zu",
1197 seg, (size_t) seg->len);
1198 tcp_iqueue_insert_seg(&conn->incoming, seg);
1199 } else {
1200 tcp_segment_delete(seg);
1201 }
1202}
1203
1204/** Segment arrived on a connection.
1205 *
1206 * @param conn Connection
1207 * @param seg Segment
1208 */
1209void tcp_conn_segment_arrived(tcp_conn_t *conn, tcp_segment_t *seg)
1210{
1211 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_conn_segment_arrived(%p)",
1212 conn->name, seg);
1213
1214 switch (conn->cstate) {
1215 case st_listen:
1216 tcp_conn_sa_listen(conn, seg); break;
1217 case st_syn_sent:
1218 tcp_conn_sa_syn_sent(conn, seg); break;
1219 case st_syn_received:
1220 case st_established:
1221 case st_fin_wait_1:
1222 case st_fin_wait_2:
1223 case st_close_wait:
1224 case st_closing:
1225 case st_last_ack:
1226 case st_time_wait:
1227 /* Process segments in order of sequence number */
1228 tcp_conn_sa_queue(conn, seg); break;
1229 case st_closed:
1230 log_msg(LOG_DEFAULT, LVL_DEBUG, "state=%d", (int) conn->cstate);
1231 assert(false);
1232 }
1233}
1234
1235/** Time-Wait timeout handler.
1236 *
1237 * @param arg Connection
1238 */
1239static void tw_timeout_func(void *arg)
1240{
1241 tcp_conn_t *conn = (tcp_conn_t *) arg;
1242
1243 log_msg(LOG_DEFAULT, LVL_DEBUG, "tw_timeout_func(%p)", conn);
1244
1245 tcp_conn_lock(conn);
1246
1247 if (conn->cstate == st_closed) {
1248 log_msg(LOG_DEFAULT, LVL_DEBUG, "Connection already closed.");
1249 tcp_conn_unlock(conn);
1250 tcp_conn_delref(conn);
1251 return;
1252 }
1253
1254 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: TW Timeout -> Closed", conn->name);
1255 tcp_conn_remove(conn);
1256 tcp_conn_state_set(conn, st_closed);
1257
1258 tcp_conn_unlock(conn);
1259 tcp_conn_delref(conn);
1260
1261 log_msg(LOG_DEFAULT, LVL_DEBUG, "tw_timeout_func(%p) end", conn);
1262}
1263
1264/** Start or restart the Time-Wait timeout.
1265 *
1266 * @param conn Connection
1267 */
1268void tcp_conn_tw_timer_set(tcp_conn_t *conn)
1269{
1270 log_msg(LOG_DEFAULT, LVL_DEBUG2, "tcp_conn_tw_timer_set() begin");
1271 tcp_conn_addref(conn);
1272 fibril_timer_set_locked(conn->tw_timer, TIME_WAIT_TIMEOUT,
1273 tw_timeout_func, (void *)conn);
1274 log_msg(LOG_DEFAULT, LVL_DEBUG2, "tcp_conn_tw_timer_set() end");
1275}
1276
1277/** Clear the Time-Wait timeout.
1278 *
1279 * @param conn Connection
1280 */
1281void tcp_conn_tw_timer_clear(tcp_conn_t *conn)
1282{
1283 log_msg(LOG_DEFAULT, LVL_DEBUG2, "tcp_conn_tw_timer_clear() begin");
1284 if (fibril_timer_clear_locked(conn->tw_timer) == fts_active)
1285 tcp_conn_delref(conn);
1286 log_msg(LOG_DEFAULT, LVL_DEBUG2, "tcp_conn_tw_timer_clear() end");
1287}
1288
1289/** Trim segment to the receive window.
1290 *
1291 * @param conn Connection
1292 * @param seg Segment
1293 */
1294void tcp_conn_trim_seg_to_wnd(tcp_conn_t *conn, tcp_segment_t *seg)
1295{
1296 uint32_t left, right;
1297
1298 seq_no_seg_trim_calc(conn, seg, &left, &right);
1299 tcp_segment_trim(seg, left, right);
1300}
1301
1302/** Handle unexpected segment received on an endpoint pair.
1303 *
1304 * We reply with an RST unless the received segment has RST.
1305 *
1306 * @param sp Endpoint pair which received the segment
1307 * @param seg Unexpected segment
1308 */
1309void tcp_unexpected_segment(inet_ep2_t *epp, tcp_segment_t *seg)
1310{
1311 log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_unexpected_segment(%p, %p)", epp,
1312 seg);
1313
1314 if ((seg->ctrl & CTL_RST) == 0)
1315 tcp_reply_rst(epp, seg);
1316}
1317
1318/** Compute flipped endpoint pair for response.
1319 *
1320 * Flipped endpoint pair has local and remote endpoints exchanged.
1321 *
1322 * @param epp Endpoint pair
1323 * @param fepp Place to store flipped endpoint pair
1324 */
1325void tcp_ep2_flipped(inet_ep2_t *epp, inet_ep2_t *fepp)
1326{
1327 fepp->local = epp->remote;
1328 fepp->remote = epp->local;
1329}
1330
1331/** Send RST in response to an incoming segment.
1332 *
1333 * @param epp Endpoint pair which received the segment
1334 * @param seg Incoming segment
1335 */
1336void tcp_reply_rst(inet_ep2_t *epp, tcp_segment_t *seg)
1337{
1338 tcp_segment_t *rseg;
1339
1340 log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_reply_rst(%p, %p)", epp, seg);
1341
1342 rseg = tcp_segment_make_rst(seg);
1343 tcp_transmit_segment(epp, rseg);
1344}
1345
1346/**
1347 * @}
1348 */
Note: See TracBrowser for help on using the repository browser.