Index: uspace/srv/net/tcp/test/conn.c
===================================================================
--- uspace/srv/net/tcp/test/conn.c	(revision 9520af7e6dd61442acc104857653aae6a31c1197)
+++ uspace/srv/net/tcp/test/conn.c	(revision 4c363fa28181030ea38d909efb8e3f758d7d1b29)
@@ -57,4 +57,7 @@
 	tcp_rqueue_init(&test_rqueue_cb);
 	tcp_rqueue_fibril_start();
+
+	/* Enable internal loopback */
+	tcp_conn_lb = tcp_lb_segment;
 }
 
@@ -118,6 +121,4 @@
 	int rc;
 
-	tcp_conn_lb = tcp_lb_segment;
-
 	inet_ep2_init(&epp);
 	inet_addr(&epp.local.addr, 127, 0, 0, 1);
@@ -146,3 +147,101 @@
 }
 
+/** Test establishing a connection */
+PCUT_TEST(conn_establish)
+{
+	tcp_conn_t *cconn, *sconn;
+	inet_ep2_t cepp, sepp;
+	int rc;
+
+	/* Client EPP */
+	inet_ep2_init(&cepp);
+	inet_addr(&cepp.local.addr, 127, 0, 0, 1);
+	inet_addr(&cepp.remote.addr, 127, 0, 0, 1);
+	cepp.remote.port = inet_port_user_lo;
+
+	/* Server EPP */
+	inet_ep2_init(&sepp);
+	inet_addr(&sepp.local.addr, 127, 0, 0, 1);
+	sepp.local.port = inet_port_user_lo;
+
+	/* Client side of the connection */
+	cconn = tcp_conn_new(&cepp);
+	PCUT_ASSERT_NOT_NULL(cconn);
+
+	rc = tcp_conn_add(cconn);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(st_listen, cconn->cstate);
+	PCUT_ASSERT_FALSE(tcp_conn_got_syn(cconn));
+
+	/* Server side of the connection */
+	sconn = tcp_conn_new(&sepp);
+	PCUT_ASSERT_NOT_NULL(sconn);
+
+	rc = tcp_conn_add(sconn);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(st_listen, sconn->cstate);
+	PCUT_ASSERT_FALSE(tcp_conn_got_syn(sconn));
+
+	/* Start establishing the connection */
+
+	tcp_conn_lock(cconn);
+	tcp_conn_sync(cconn);
+	PCUT_ASSERT_INT_EQUALS(st_syn_sent, cconn->cstate);
+	PCUT_ASSERT_FALSE(tcp_conn_got_syn(cconn));
+
+	/* Wait for client-side state to transition */
+	while (cconn->cstate == st_syn_sent)
+                fibril_condvar_wait(&cconn->cstate_cv, &cconn->lock);
+
+	PCUT_ASSERT_INT_EQUALS(st_established, cconn->cstate);
+	PCUT_ASSERT_TRUE(tcp_conn_got_syn(cconn));
+	tcp_conn_unlock(cconn);
+
+	/* Wait for server-side state to transition */
+	tcp_conn_lock(sconn);
+	while (sconn->cstate == st_listen || sconn->cstate == st_syn_received)
+                fibril_condvar_wait(&sconn->cstate_cv, &sconn->lock);
+
+	PCUT_ASSERT_INT_EQUALS(st_established, sconn->cstate);
+	PCUT_ASSERT_TRUE(tcp_conn_got_syn(sconn));
+
+	/* Verify counters */
+	PCUT_ASSERT_EQUALS(cconn->iss + 1, cconn->snd_nxt);
+	PCUT_ASSERT_EQUALS(cconn->iss + 1, cconn->snd_una);
+	PCUT_ASSERT_EQUALS(sconn->iss + 1, sconn->snd_nxt);
+	PCUT_ASSERT_EQUALS(sconn->iss + 1, sconn->snd_una);
+
+	tcp_conn_unlock(sconn);
+
+	tcp_conn_lock(cconn);
+	tcp_conn_reset(cconn);
+	tcp_conn_unlock(cconn);
+	tcp_conn_delete(cconn);
+
+	tcp_conn_lock(sconn);
+	tcp_conn_reset(sconn);
+	tcp_conn_unlock(sconn);
+	tcp_conn_delete(sconn);
+}
+
+PCUT_TEST(ep2_flipped)
+{
+	inet_ep2_t a, fa;
+
+	inet_addr(&a.local.addr, 1, 2, 3, 4);
+	a.local.port = 1234;
+	inet_addr(&a.remote.addr, 5, 6, 7, 8);
+	a.remote.port = 5678;
+
+	tcp_ep2_flipped(&a, &fa);
+
+	PCUT_ASSERT_INT_EQUALS(a.local.port, fa.remote.port);
+	PCUT_ASSERT_INT_EQUALS(a.remote.port, fa.local.port);
+
+	PCUT_ASSERT_TRUE(inet_addr_compare(&a.local.addr, &fa.remote.addr));
+	PCUT_ASSERT_TRUE(inet_addr_compare(&a.remote.addr, &fa.local.addr));
+}
+
 PCUT_EXPORT(conn);
Index: uspace/srv/net/tcp/test/main.c
===================================================================
--- uspace/srv/net/tcp/test/main.c	(revision 9520af7e6dd61442acc104857653aae6a31c1197)
+++ uspace/srv/net/tcp/test/main.c	(revision 4c363fa28181030ea38d909efb8e3f758d7d1b29)
@@ -64,4 +64,5 @@
 PCUT_IMPORT(seq_no);
 PCUT_IMPORT(tqueue);
+PCUT_IMPORT(ucall);
 
 PCUT_MAIN()
Index: uspace/srv/net/tcp/test/ucall.c
===================================================================
--- uspace/srv/net/tcp/test/ucall.c	(revision 4c363fa28181030ea38d909efb8e3f758d7d1b29)
+++ uspace/srv/net/tcp/test/ucall.c	(revision 4c363fa28181030ea38d909efb8e3f758d7d1b29)
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2017 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - 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.
+ * - 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.
+ */
+
+#include <errno.h>
+#include <inet/endpoint.h>
+#include <io/log.h>
+#include <pcut/pcut.h>
+
+#include "../conn.h"
+#include "../rqueue.h"
+#include "../ucall.h"
+
+PCUT_INIT
+
+PCUT_TEST_SUITE(ucall);
+
+static void test_cstate_change(tcp_conn_t *, void *, tcp_cstate_t);
+static void test_conns_establish(tcp_conn_t **, tcp_conn_t **);
+static void test_conns_tear_down(tcp_conn_t *, tcp_conn_t *);
+
+static tcp_rqueue_cb_t test_rqueue_cb = {
+	.seg_received = tcp_as_segment_arrived
+};
+
+static tcp_cb_t test_conn_cb = {
+	.cstate_change = test_cstate_change
+};
+
+static tcp_conn_status_t cconn_status;
+static tcp_conn_status_t sconn_status;
+
+static FIBRIL_MUTEX_INITIALIZE(cst_lock);
+static FIBRIL_CONDVAR_INITIALIZE(cst_cv);
+
+PCUT_TEST_BEFORE
+{
+	int rc;
+
+	/* We will be calling functions that perform logging */
+	rc = log_init("test-tcp");
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = tcp_conns_init();
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	tcp_rqueue_init(&test_rqueue_cb);
+	tcp_rqueue_fibril_start();
+
+	/* Enable internal loopback */
+	tcp_conn_lb = tcp_lb_segment;
+}
+
+PCUT_TEST_AFTER
+{
+	tcp_rqueue_fini();
+	tcp_conns_fini();
+}
+
+/** Test creating a listening passive connection and then deleting it. */
+PCUT_TEST(listen_delete)
+{
+	tcp_conn_t *conn;
+	inet_ep2_t epp;
+	tcp_error_t trc;
+	tcp_conn_status_t cstatus;
+
+	inet_ep2_init(&epp);
+	inet_addr(&epp.local.addr, 127, 0, 0, 1);
+	epp.local.port = inet_port_user_lo;
+
+	conn = NULL;
+	trc = tcp_uc_open(&epp, ap_passive, tcp_open_nonblock, &conn);
+	PCUT_ASSERT_INT_EQUALS(TCP_EOK, trc);
+	PCUT_ASSERT_NOT_NULL(conn);
+
+	tcp_uc_status(conn, &cstatus);
+	PCUT_ASSERT_INT_EQUALS(st_listen, cstatus.cstate);
+
+	trc = tcp_uc_close(conn);
+	PCUT_ASSERT_INT_EQUALS(TCP_EOK, trc);
+	tcp_uc_delete(conn);
+}
+
+/** Test trying to connect to endpoint that sends RST back */
+PCUT_TEST(connect_rst)
+{
+	tcp_conn_t *conn;
+	inet_ep2_t epp;
+	tcp_error_t trc;
+
+	inet_ep2_init(&epp);
+	inet_addr(&epp.local.addr, 127, 0, 0, 1);
+	inet_addr(&epp.remote.addr, 127, 0, 0, 1);
+	epp.remote.port = inet_port_user_lo;
+
+	conn = NULL;
+	trc = tcp_uc_open(&epp, ap_active, 0, &conn);
+	PCUT_ASSERT_INT_EQUALS(TCP_ERESET, trc);
+	PCUT_ASSERT_NULL(conn);
+}
+
+/** Test establishing a connection */
+PCUT_TEST(conn_establish)
+{
+	tcp_conn_t *cconn, *sconn;
+
+	test_conns_establish(&cconn, &sconn);
+	test_conns_tear_down(cconn, sconn);
+}
+
+/** Test establishing and then closing down a connection first on one side,
+ * then on_the other. */
+PCUT_TEST(conn_est_close_seq)
+{
+	tcp_conn_t *cconn, *sconn;
+	tcp_error_t trc;
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "conn_est_close_seq: establish");
+	/* Establish */
+	test_conns_establish(&cconn, &sconn);
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "conn_est_close_seq: close cconn");
+	/* Close client side */
+	trc = tcp_uc_close(cconn);
+	PCUT_ASSERT_INT_EQUALS(TCP_EOK, trc);
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "conn_est_close_seq: wait cconn = fin-wait-2");
+	/* Wait for cconn to go to Fin-Wait-2 */
+	fibril_mutex_lock(&cst_lock);
+	tcp_uc_status(cconn, &cconn_status);
+	while (cconn_status.cstate != st_fin_wait_2)
+		fibril_condvar_wait(&cst_cv, &cst_lock);
+	fibril_mutex_unlock(&cst_lock);
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "conn_est_close_seq: wait sconn = close-wait");
+	/* Wait for sconn to go to Close-Wait */
+	fibril_mutex_lock(&cst_lock);
+	tcp_uc_status(sconn, &sconn_status);
+	while (sconn_status.cstate != st_close_wait)
+		fibril_condvar_wait(&cst_cv, &cst_lock);
+	fibril_mutex_unlock(&cst_lock);
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "conn_est_close_seq: close sconn");
+	/* Close server side */
+	trc = tcp_uc_close(sconn);
+	PCUT_ASSERT_INT_EQUALS(TCP_EOK, trc);
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "conn_est_close_seq: wait cconn = time-wait");
+	/* Wait for cconn to go to Time-Wait */
+	fibril_mutex_lock(&cst_lock);
+	tcp_uc_status(cconn, &cconn_status);
+	while (cconn_status.cstate != st_time_wait)
+		fibril_condvar_wait(&cst_cv, &cst_lock);
+	fibril_mutex_unlock(&cst_lock);
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "conn_est_close_seq: wait sconn = closed");
+	/* Wait for sconn to go to Closed */
+	fibril_mutex_lock(&cst_lock);
+	tcp_uc_status(sconn, &sconn_status);
+	while (sconn_status.cstate != st_closed) {
+		log_msg(LOG_DEFAULT, LVL_NOTE, "conn_est_close_seq: sconn.status == %d", sconn_status.cstate);
+		fibril_condvar_wait(&cst_cv, &cst_lock);
+	}
+	fibril_mutex_unlock(&cst_lock);
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "conn_est_close_seq: tear down");
+	/* Tear down */
+	test_conns_tear_down(cconn, sconn);
+}
+
+/** Test establishing and then simultaneously closing down a connection. */
+PCUT_TEST(conn_est_close_simult)
+{
+	tcp_conn_t *cconn, *sconn;
+	tcp_error_t trc;
+
+	/* Establish */
+	test_conns_establish(&cconn, &sconn);
+
+	/* Close both sides simultaneously */
+	trc = tcp_uc_close(cconn);
+	PCUT_ASSERT_INT_EQUALS(TCP_EOK, trc);
+	trc = tcp_uc_close(sconn);
+	PCUT_ASSERT_INT_EQUALS(TCP_EOK, trc);
+
+	/* Wait for cconn to go to Time-Wait */
+	fibril_mutex_lock(&cst_lock);
+	tcp_uc_status(cconn, &cconn_status);
+	while (cconn_status.cstate != st_time_wait)
+		fibril_condvar_wait(&cst_cv, &cst_lock);
+	fibril_mutex_unlock(&cst_lock);
+
+	/*
+	 * Wait for sconn to go to Closed or Time-Wait. The connection
+	 * goes to Closed if we managed to call tcp_uc_close() before
+	 * sconn received FIN. Otherwise it goes to Time-Wait.
+	 *
+	 * XXX We may want to add delay to the loopback here to be
+	 * absolutely sure that we go to Closing -> Time-Wait.
+	 */
+	fibril_mutex_lock(&cst_lock);
+	tcp_uc_status(sconn, &sconn_status);
+	while (sconn_status.cstate != st_time_wait &&
+	    sconn_status.cstate != st_closed)
+		fibril_condvar_wait(&cst_cv, &cst_lock);
+	fibril_mutex_unlock(&cst_lock);
+
+	/* Tear down */
+	test_conns_tear_down(cconn, sconn);
+}
+
+static void test_cstate_change(tcp_conn_t *conn, void *arg,
+    tcp_cstate_t old_state)
+{
+	tcp_conn_status_t *status = (tcp_conn_status_t *)arg;
+
+	fibril_mutex_lock(&cst_lock);
+	tcp_uc_status(conn, status);
+	fibril_mutex_unlock(&cst_lock);
+	fibril_condvar_broadcast(&cst_cv);
+}
+
+/** Establish client-server connection */
+static void test_conns_establish(tcp_conn_t **rcconn, tcp_conn_t **rsconn)
+{
+	tcp_conn_t *cconn, *sconn;
+	inet_ep2_t cepp, sepp;
+	tcp_conn_status_t cstatus;
+	tcp_error_t trc;
+
+	/* Client EPP */
+	inet_ep2_init(&cepp);
+	inet_addr(&cepp.local.addr, 127, 0, 0, 1);
+	inet_addr(&cepp.remote.addr, 127, 0, 0, 1);
+	cepp.remote.port = inet_port_user_lo;
+
+	/* Server EPP */
+	inet_ep2_init(&sepp);
+	inet_addr(&sepp.local.addr, 127, 0, 0, 1);
+	sepp.local.port = inet_port_user_lo;
+
+	/* Server side of the connection */
+	sconn = NULL;
+	trc = tcp_uc_open(&sepp, ap_passive, tcp_open_nonblock, &sconn);
+	PCUT_ASSERT_INT_EQUALS(TCP_EOK, trc);
+	PCUT_ASSERT_NOT_NULL(sconn);
+
+	tcp_uc_set_cb(sconn, &test_conn_cb, &sconn_status);
+	PCUT_ASSERT_EQUALS(&sconn_status, tcp_uc_get_userptr(sconn));
+
+	tcp_uc_status(sconn, &cstatus);
+	PCUT_ASSERT_INT_EQUALS(st_listen, cstatus.cstate);
+
+	/* Client side of the connection */
+
+	cconn = NULL;
+	trc = tcp_uc_open(&cepp, ap_active, 0, &cconn);
+	PCUT_ASSERT_INT_EQUALS(TCP_EOK, trc);
+	PCUT_ASSERT_NOT_NULL(cconn);
+
+	tcp_uc_set_cb(cconn, &test_conn_cb, &cconn_status);
+	PCUT_ASSERT_EQUALS(&cconn_status, tcp_uc_get_userptr(cconn));
+
+	/* The client side of the connection should be established by now */
+
+	tcp_uc_status(cconn, &cstatus);
+	PCUT_ASSERT_INT_EQUALS(st_established, cstatus.cstate);
+
+	/* Need to wait for server side */
+
+	fibril_mutex_lock(&cst_lock);
+	tcp_uc_status(sconn, &sconn_status);
+	while (sconn_status.cstate != st_established)
+		fibril_condvar_wait(&cst_cv, &cst_lock);
+	fibril_mutex_unlock(&cst_lock);
+
+	*rcconn = cconn;
+	*rsconn = sconn;
+}
+
+/* Tear down client-server connection. */
+static void test_conns_tear_down(tcp_conn_t *cconn, tcp_conn_t *sconn)
+{
+	tcp_uc_abort(cconn);
+	tcp_uc_delete(cconn);
+
+	tcp_uc_abort(sconn);
+	tcp_uc_delete(sconn);
+}
+
+PCUT_EXPORT(ucall);
