Index: uspace/srv/net/tl/tcp/conn.c
===================================================================
--- uspace/srv/net/tl/tcp/conn.c	(revision d9ce0493d431882059cb2de7db01b19ae6c871a6)
+++ uspace/srv/net/tl/tcp/conn.c	(revision 8c7a0540795c67f9ed8a2831e5830db5daa2cf50)
@@ -36,4 +36,5 @@
 
 #include <adt/list.h>
+#include <bool.h>
 #include <errno.h>
 #include <io/log.h>
@@ -74,4 +75,5 @@
 	conn->rcv_buf_size = RCV_BUF_SIZE;
 	conn->rcv_buf_used = 0;
+	conn->rcv_buf_fin = false;
 
 	conn->rcv_buf = calloc(1, conn->rcv_buf_size);
@@ -84,4 +86,5 @@
 	conn->snd_buf_size = SND_BUF_SIZE;
 	conn->snd_buf_used = 0;
+	conn->snd_buf_fin = false;
 	conn->snd_buf = calloc(1, conn->snd_buf_size);
 	if (conn->snd_buf == NULL) {
@@ -697,19 +700,14 @@
 	conn->rcv_wnd -= xfer_size;
 
-	/* XXX Signal user that some data arrived. */
-
 	/* Send ACK */
 	if (xfer_size > 0)
 		tcp_tqueue_ctrl_seg(conn, CTL_ACK);
 
-	/* Anything left in the segment? (text, FIN) */
 	if (xfer_size < seg->len) {
-		/*
-		 * Some text or control remains. Insert remainder back
-		 * into the incoming queue.
-		 */
+		/* Trim part of segment which we just received */
 		tcp_conn_trim_seg_to_wnd(conn, seg);
-		tcp_iqueue_insert_seg(&conn->incoming, seg);
-
+	} else {
+		/* Nothing left in segment */
+		tcp_segment_delete(seg);
 		return cp_done;
 	}
@@ -727,4 +725,25 @@
 static cproc_t tcp_conn_seg_proc_fin(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	log_msg(LVL_DEBUG, "tcp_conn_seg_proc_fin(%p, %p)", conn, seg);
+
+	/* Only process FIN if no text is left in segment. */
+	if (tcp_segment_text_size(seg) == 0 && (seg->ctrl & CTL_FIN) != 0) {
+		log_msg(LVL_DEBUG, " - FIN found in segment.");
+
+		conn->rcv_nxt++;
+		conn->rcv_wnd--;
+
+		/* TODO Change connection state */
+
+		/* Add FIN to the receive buffer */
+		fibril_mutex_lock(&conn->rcv_buf_lock);
+		conn->rcv_buf_fin = true;
+		fibril_condvar_broadcast(&conn->rcv_buf_cv);
+		fibril_mutex_unlock(&conn->rcv_buf_lock);
+
+		tcp_segment_delete(seg);
+		return cp_done;
+	}
+
 	return cp_continue;
 }
@@ -775,5 +794,12 @@
 		return;
 
-	tcp_segment_delete(seg);
+	/*
+	 * If anything is left from the segment, insert it back into the
+	 * incoming segments queue.
+	 */
+	if (seg->len > 0)
+		tcp_iqueue_insert_seg(&conn->incoming, seg);
+	else
+		tcp_segment_delete(seg);
 }
 
Index: uspace/srv/net/tl/tcp/segment.c
===================================================================
--- uspace/srv/net/tl/tcp/segment.c	(revision d9ce0493d431882059cb2de7db01b19ae6c871a6)
+++ uspace/srv/net/tl/tcp/segment.c	(revision 8c7a0540795c67f9ed8a2831e5830db5daa2cf50)
@@ -99,5 +99,5 @@
 	tcp_segment_t *seg;
 
-	assert(size > 0);
+	assert(ctrl != 0 || size > 0);
 
 	seg = tcp_segment_new();
Index: uspace/srv/net/tl/tcp/state.c
===================================================================
--- uspace/srv/net/tl/tcp/state.c	(revision d9ce0493d431882059cb2de7db01b19ae6c871a6)
+++ uspace/srv/net/tl/tcp/state.c	(revision 8c7a0540795c67f9ed8a2831e5830db5daa2cf50)
@@ -115,7 +115,16 @@
 
 	/* Wait for data to become available */
-	while (conn->rcv_buf_used == 0) {
+	while (conn->rcv_buf_used == 0 && !conn->rcv_buf_fin) {
 		log_msg(LVL_DEBUG, "tcp_uc_receive() - wait for data");
 		fibril_condvar_wait(&conn->rcv_buf_cv, &conn->rcv_buf_lock);
+	}
+
+	if (conn->rcv_buf_used == 0) {
+		/* End of data, peer closed connection. */
+		/* XXX How should RECEIVE signal end of data? */
+		assert(conn->rcv_buf_fin);
+		*rcvd = 0;
+		*xflags = 0;
+		return;
 	}
 
@@ -147,4 +156,7 @@
 {
 	log_msg(LVL_DEBUG, "tcp_uc_close()");
+
+	conn->snd_buf_fin = true;
+	tcp_tqueue_new_data(conn);
 }
 
Index: uspace/srv/net/tl/tcp/tcp_type.h
===================================================================
--- uspace/srv/net/tl/tcp/tcp_type.h	(revision d9ce0493d431882059cb2de7db01b19ae6c871a6)
+++ uspace/srv/net/tl/tcp/tcp_type.h	(revision 8c7a0540795c67f9ed8a2831e5830db5daa2cf50)
@@ -37,4 +37,5 @@
 
 #include <adt/list.h>
+#include <bool.h>
 #include <fibril_synch.h>
 #include <sys/types.h>
@@ -114,6 +115,11 @@
 	/** Receive buffer size */
 	size_t rcv_buf_size;
+	/** Receive buffer number of bytes used */
 	size_t rcv_buf_used;
+	/** Receive buffer contains FIN */
+	bool rcv_buf_fin;
+	/** Receive buffer lock */
 	fibril_mutex_t rcv_buf_lock;
+	/** Receive buffer CV. Broadcast when new data is inserted */
 	fibril_condvar_t rcv_buf_cv;
 
@@ -122,5 +128,8 @@
 	/** Send buffer size */
 	size_t snd_buf_size;
+	/** Send buffer number of bytes used */
 	size_t snd_buf_used;
+	/** Send buffer contains FIN */
+	bool snd_buf_fin;
 
 	/** Send unacknowledged */
Index: uspace/srv/net/tl/tcp/test.c
===================================================================
--- uspace/srv/net/tl/tcp/test.c	(revision d9ce0493d431882059cb2de7db01b19ae6c871a6)
+++ uspace/srv/net/tl/tcp/test.c	(revision 8c7a0540795c67f9ed8a2831e5830db5daa2cf50)
@@ -63,4 +63,8 @@
 		printf("User receive...\n");
 		tcp_uc_receive(conn, rcv_buf, RCV_BUF_SIZE, &rcvd, &xflags);
+		if (rcvd == 0) {
+			printf("End of data reached.\n");
+			break;
+		}
 		rcv_buf[rcvd] = '\0';
 		printf("User received %zu bytes '%s'.\n", rcvd, rcv_buf);
@@ -68,4 +72,6 @@
 		async_usleep(1000*1000*2);
 	}
+
+	printf("test_srv() terminating\n");
 }
 
@@ -80,5 +86,5 @@
 	sock.port = 80;
 	sock.addr.ipv4 = 0x7f000001;
-	
+
 	async_usleep(1000*1000*3);
 	tcp_uc_open(1024, &sock, ap_active, &conn);
@@ -86,4 +92,7 @@
 	async_usleep(1000*1000*10);
 	tcp_uc_send(conn, (void *)msg, str_size(msg), 0);
+
+	async_usleep(1000*1000*3);
+	tcp_uc_close(conn);
 }
 
Index: uspace/srv/net/tl/tcp/tqueue.c
===================================================================
--- uspace/srv/net/tl/tcp/tqueue.c	(revision d9ce0493d431882059cb2de7db01b19ae6c871a6)
+++ uspace/srv/net/tl/tcp/tqueue.c	(revision 8c7a0540795c67f9ed8a2831e5830db5daa2cf50)
@@ -92,5 +92,9 @@
 {
 	size_t avail_wnd;
+	size_t xfer_seqlen;
+	size_t snd_buf_seqlen;
 	size_t data_size;
+	tcp_control_t ctrl;
+
 	tcp_segment_t *seg;
 
@@ -99,15 +103,25 @@
 	/* Number of free sequence numbers in send window */
 	avail_wnd = (conn->snd_una + conn->snd_wnd) - conn->snd_nxt;
+	snd_buf_seqlen = conn->snd_buf_used + (conn->snd_buf_fin ? 1 : 0);
 
-	data_size = min(conn->snd_buf_used, avail_wnd);
-	log_msg(LVL_DEBUG, "conn->snd_buf_used = %zu, SND.WND = %zu, "
-	    "data_size = %zu", conn->snd_buf_used, conn->snd_wnd, data_size);
+	xfer_seqlen = min(snd_buf_seqlen, avail_wnd);
+	log_msg(LVL_DEBUG, "snd_buf_seqlen = %zu, SND.WND = %zu, "
+	    "xfer_seqlen = %zu", snd_buf_seqlen, conn->snd_wnd,
+	    xfer_seqlen);
 
-	if (data_size == 0)
+	if (xfer_seqlen == 0)
 		return;
 
 	/* XXX Do not always send immediately */
 
-	seg = tcp_segment_make_data(0, conn->snd_buf, data_size);
+	data_size = xfer_seqlen - (conn->snd_buf_fin ? 1 : 0);
+	if (conn->snd_buf_fin && data_size + 1 == xfer_seqlen) {
+		/* We are sending out FIN */
+		ctrl = CTL_FIN;
+	} else {
+		ctrl = 0;
+	}
+
+	seg = tcp_segment_make_data(ctrl, conn->snd_buf, data_size);
 	if (seg == NULL) {
 		log_msg(LVL_ERROR, "Memory allocation failure.");
@@ -119,4 +133,5 @@
 	    conn->snd_buf_used - data_size);
 	conn->snd_buf_used -= data_size;
+	conn->snd_buf_fin = false;
 
 	tcp_tqueue_seg(conn, seg);
