Index: uspace/srv/net/tl/tcp/conn.c
===================================================================
--- uspace/srv/net/tl/tcp/conn.c	(revision c5808b41dc6756c7b856c1e8da68d2286d0056ab)
+++ uspace/srv/net/tl/tcp/conn.c	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
@@ -152,4 +152,5 @@
 	conn->irs = seg->seq;
 
+
 	log_msg(LVL_DEBUG, "rcv_nxt=%u", conn->rcv_nxt);
 
@@ -161,4 +162,13 @@
 	conn->snd_nxt = conn->iss;
 	conn->snd_una = conn->iss;
+
+	/*
+	 * Surprisingly the spec does not deal with initial window setting.
+	 * Set SND.WND = SEG.WND and set SND.WL1 so that next segment
+	 * will always be accepted as new window setting.
+	 */
+	conn->snd_wnd = seg->wnd;
+	conn->snd_wl1 = seg->seq;
+	conn->snd_wl2 = seg->seq;
 
 	conn->cstate = st_syn_received;
@@ -201,5 +211,5 @@
 	if ((seg->ctrl & CTL_ACK) != 0) {
 		conn->snd_una = seg->ack;
-		/* XXX process retransmission queue */
+		tcp_tqueue_remove_acked(conn);
 	}
 
@@ -240,4 +250,5 @@
 static cproc_t tcp_conn_seg_proc_rst(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	/* TODO */
 	return cp_continue;
 }
@@ -245,4 +256,5 @@
 static cproc_t tcp_conn_seg_proc_sp(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	/* TODO */
 	return cp_continue;
 }
@@ -250,4 +262,5 @@
 static cproc_t tcp_conn_seg_proc_syn(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	/* TODO */
 	return cp_continue;
 }
@@ -259,4 +272,6 @@
 		log_msg(LVL_WARN, "Segment ACK not acceptable, sending RST.");
 		tcp_reply_rst(&conn->ident, seg);
+		tcp_segment_delete(seg);
+		return cp_done;
 	}
 
@@ -273,4 +288,25 @@
 static cproc_t tcp_conn_seg_proc_ack_est(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	if (!seq_no_ack_acceptable(conn, seg->ack)) {
+		if (!seq_no_ack_duplicate(conn, seg->ack)) {
+			/* Not acceptable, not duplicate. Send ACK and drop. */
+			tcp_tqueue_ctrl_seg(conn, CTL_ACK);
+			tcp_segment_delete(seg);
+			return cp_done;
+		}
+	} else {
+		/* Update SND.UNA */
+		conn->snd_una = seg->ack;
+
+		/* Prune acked segments from retransmission queue */
+		tcp_tqueue_remove_acked(conn);
+	}
+
+	if (seq_no_new_wnd_update(conn, seg)) {
+		conn->snd_wnd = seg->wnd;
+		conn->snd_wl1 = seg->seq;
+		conn->snd_wl2 = seg->ack;
+	}
+
 	return cp_continue;
 }
@@ -278,4 +314,8 @@
 static cproc_t tcp_conn_seg_proc_ack_fw1(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	if (tcp_conn_seg_proc_ack_est(conn, seg) == cp_done)
+		return cp_done;
+
+	/* TODO */
 	return cp_continue;
 }
@@ -283,4 +323,8 @@
 static cproc_t tcp_conn_seg_proc_ack_fw2(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	if (tcp_conn_seg_proc_ack_est(conn, seg) == cp_done)
+		return cp_done;
+
+	/* TODO */
 	return cp_continue;
 }
@@ -288,9 +332,14 @@
 static cproc_t tcp_conn_seg_proc_ack_cw(tcp_conn_t *conn, tcp_segment_t *seg)
 {
-	return cp_continue;
+	/* The same processing as in Established state */
+	return tcp_conn_seg_proc_ack_est(conn, seg);
 }
 
 static cproc_t tcp_conn_seg_proc_ack_cls(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	if (tcp_conn_seg_proc_ack_est(conn, seg) == cp_done)
+		return cp_done;
+
+	/* TODO */
 	return cp_continue;
 }
@@ -298,4 +347,8 @@
 static cproc_t tcp_conn_seg_proc_ack_la(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	if (tcp_conn_seg_proc_ack_est(conn, seg) == cp_done)
+		return cp_done;
+
+	/* TODO */
 	return cp_continue;
 }
@@ -303,4 +356,5 @@
 static cproc_t tcp_conn_seg_proc_ack_tw(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	/* Nothing to do */
 	return cp_continue;
 }
@@ -312,5 +366,5 @@
 	if ((seg->ctrl & CTL_ACK) == 0) {
 		log_msg(LVL_WARN, "Segment has no ACK. Dropping.");
-		/* XXX Free segment */
+		tcp_segment_delete(seg);
 		return cp_done;
 	}
@@ -349,4 +403,24 @@
 static cproc_t tcp_conn_seg_proc_text(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	switch (conn->cstate) {
+	case st_established:
+	case st_fin_wait_1:
+	case st_fin_wait_2:
+		/* OK */
+		break;
+	case st_close_wait:
+	case st_closing:
+	case st_last_ack:
+	case st_time_wait:
+		/* Invalid since FIN has been received. Ignore text. */
+		return cp_continue;
+	case st_listen:
+	case st_syn_sent:
+	case st_syn_received:
+	case st_closed:
+		assert(false);
+	}
+
+	/* TODO Process segment text */
 	return cp_continue;
 }
Index: uspace/srv/net/tl/tcp/iqueue.c
===================================================================
--- uspace/srv/net/tl/tcp/iqueue.c	(revision c5808b41dc6756c7b856c1e8da68d2286d0056ab)
+++ uspace/srv/net/tl/tcp/iqueue.c	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
@@ -40,4 +40,5 @@
 #include <stdlib.h>
 #include "iqueue.h"
+#include "segment.h"
 #include "seq_no.h"
 #include "tcp_type.h"
@@ -85,5 +86,5 @@
 
 		list_remove(&iqe->link);
-		/* XXX free segment */
+		tcp_segment_delete(iqe->seg);
 
          	link = list_first(&iqueue->list);
Index: uspace/srv/net/tl/tcp/segment.c
===================================================================
--- uspace/srv/net/tl/tcp/segment.c	(revision c5808b41dc6756c7b856c1e8da68d2286d0056ab)
+++ uspace/srv/net/tl/tcp/segment.c	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
@@ -45,4 +45,9 @@
 }
 
+void tcp_segment_delete(tcp_segment_t *seg)
+{
+	free(seg);
+}
+
 /** Create a control segment. */
 tcp_segment_t *tcp_segment_make_ctrl(tcp_control_t ctrl)
Index: uspace/srv/net/tl/tcp/segment.h
===================================================================
--- uspace/srv/net/tl/tcp/segment.h	(revision c5808b41dc6756c7b856c1e8da68d2286d0056ab)
+++ uspace/srv/net/tl/tcp/segment.h	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
@@ -39,4 +39,5 @@
 
 extern tcp_segment_t *tcp_segment_new(void);
+extern void tcp_segment_delete(tcp_segment_t *);
 extern tcp_segment_t *tcp_segment_make_ctrl(tcp_control_t);
 extern tcp_segment_t *tcp_segment_make_rst(tcp_segment_t *);
Index: uspace/srv/net/tl/tcp/seq_no.c
===================================================================
--- uspace/srv/net/tl/tcp/seq_no.c	(revision c5808b41dc6756c7b856c1e8da68d2286d0056ab)
+++ uspace/srv/net/tl/tcp/seq_no.c	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
@@ -68,4 +68,51 @@
 }
 
+/** Determine wheter ack is duplicate.
+ *
+ * ACK is duplicate if it refers to a sequence number that has
+ * aleady been acked (SEG.ACK < SND.UNA).
+ */
+bool seq_no_ack_duplicate(tcp_conn_t *conn, uint32_t seg_ack)
+{
+	uint32_t diff;
+
+	/*
+	 * There does not seem to be a three-point comparison
+	 * equivalent of SEG.ACK < SND.UNA. Thus we do it
+	 * on a best-effort basis, based on the difference.
+	 * [-2^31, 0) means less-than, [0, 2^31) means greater-than.
+	 */
+	diff = seg_ack - conn->snd_una;
+	return (diff & (0x1 << 31)) != 0;
+}
+
+/** Determine segment has new window update.
+ *
+ * Window update is new if either SND.WL1 < SEG.SEQ or
+ * (SND.WL1 = SEG.SEQ and SND.WL2 <= SEG.ACK).
+ */
+bool seq_no_new_wnd_update(tcp_conn_t *conn, tcp_segment_t *seg)
+{
+	bool n_seq, n_ack;
+
+	assert(seq_no_segment_acceptable(conn, seg));
+
+	/*
+	 * We make use of the fact that the peer should not ACK anything
+	 * beyond our send window (we surely haven't sent that yet)
+	 * as we should have filtered those acks out.
+	 * We use SND.UNA+SND.WND as the third point of comparison.
+	 */
+
+	n_seq = seq_no_lt_le(conn->snd_wl1, seg->seq,
+	    conn->snd_una + conn->snd_wnd);
+
+	n_ack = conn->snd_wl1 == seg->seq &&
+	    seq_no_le_lt(conn->snd_wl2, seg->ack,
+	    conn->snd_una + conn->snd_wnd + 1);
+
+	return n_seq || n_ack;
+}
+
 /** Determine if segment is ready for processing.
  *
Index: uspace/srv/net/tl/tcp/seq_no.h
===================================================================
--- uspace/srv/net/tl/tcp/seq_no.h	(revision c5808b41dc6756c7b856c1e8da68d2286d0056ab)
+++ uspace/srv/net/tl/tcp/seq_no.h	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
@@ -40,4 +40,6 @@
 
 extern bool seq_no_ack_acceptable(tcp_conn_t *, uint32_t);
+extern bool seq_no_ack_duplicate(tcp_conn_t *, uint32_t);
+extern bool seq_no_new_wnd_update(tcp_conn_t *, tcp_segment_t *);
 extern bool seq_no_segment_acked(tcp_conn_t *, tcp_segment_t *, uint32_t);
 extern bool seq_no_syn_acked(tcp_conn_t *);
Index: uspace/srv/net/tl/tcp/tcp_type.h
===================================================================
--- uspace/srv/net/tl/tcp/tcp_type.h	(revision c5808b41dc6756c7b856c1e8da68d2286d0056ab)
+++ uspace/srv/net/tl/tcp/tcp_type.h	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
@@ -148,4 +148,6 @@
 	/** Segment length in sequence space */
 	uint32_t len;
+	/** Segment window */
+	uint32_t wnd;
 	/** Segment urgent pointer */
 	uint32_t up;
Index: uspace/srv/net/tl/tcp/tqueue.c
===================================================================
--- uspace/srv/net/tl/tcp/tqueue.c	(revision c5808b41dc6756c7b856c1e8da68d2286d0056ab)
+++ uspace/srv/net/tl/tcp/tqueue.c	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
@@ -64,4 +64,13 @@
 }
 
+/** Remove ACKed segments from retransmission queue.
+ *
+ * This should be called when SND.UNA is updated due to incoming ACK.
+ */
+void tcp_tqueue_remove_acked(tcp_conn_t *conn)
+{
+	(void) conn;
+}
+
 void tcp_transmit_segment(tcp_sockpair_t *sp, tcp_segment_t *seg)
 {
Index: uspace/srv/net/tl/tcp/tqueue.h
===================================================================
--- uspace/srv/net/tl/tcp/tqueue.h	(revision c5808b41dc6756c7b856c1e8da68d2286d0056ab)
+++ uspace/srv/net/tl/tcp/tqueue.h	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
@@ -41,4 +41,5 @@
 extern void tcp_tqueue_ctrl_seg(tcp_conn_t *, tcp_control_t);
 extern void tcp_tqueue_seg(tcp_conn_t *, tcp_segment_t *);
+extern void tcp_tqueue_remove_acked(tcp_conn_t *);
 extern void tcp_transmit_segment(tcp_sockpair_t *, tcp_segment_t *);
 extern void tcp_header_setup(tcp_conn_t *, tcp_segment_t *, tcp_header_t *);
