diff --git a/repos/libports/run/netty.run b/repos/libports/run/netty.run
new file mode 100644
index 0000000000..29f45583d6
--- /dev/null
+++ b/repos/libports/run/netty.run
@@ -0,0 +1,163 @@
+assert_spec linux
+
+set build_components {
+ core init
+ drivers/timer drivers/nic server/ram_fs server/vfs
+ test/netty
+ lib/vfs/lxip
+}
+
+source ${genode_dir}/repos/base/run/platform_drv.inc
+append_platform_drv_build_components
+
+build $build_components
+
+create_boot_directory
+
+set config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+
+if { false } { append config {
+
+
+
+
+
+ 33
+
+
+
+ 44
+ 10.0.2.55:8888
+
+
+
+
+ 10.0.2.55:8888
+ 10.0.2.1:13001
+ REQUEST
+
+
+
+ }
+} else { append config {
+
+
+
+
+
+
+
+
+
+
+
+ }
+}
+append config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+append_platform_drv_config
+
+append config {
+
+}
+
+install_config $config
+
+set boot_modules {
+ core init timer nic_drv ram_fs vfs
+ ld.lib.so libc.lib.so libm.lib.so
+ libc_resolv.lib.so stdcxx.lib.so libc_pipe.lib.so
+ vfs_lxip.lib.so lxip.lib.so
+ test-netty
+}
+
+append_platform_drv_boot_modules
+
+build_boot_image $boot_modules
+
+run_genode_until forever
+
+# vi: set ft=tcl :
diff --git a/repos/libports/src/test/netty/main.cc b/repos/libports/src/test/netty/main.cc
new file mode 100644
index 0000000000..01ceef3648
--- /dev/null
+++ b/repos/libports/src/test/netty/main.cc
@@ -0,0 +1,363 @@
+/*
+ * \brief Network echo test
+ * \author Christian Helmuth
+ * \date 2015-09-21
+ */
+
+/*
+ * Copyright (C) 2015-2017 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU General Public License version 2.
+ */
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* Libc includes */
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+
+typedef Genode::String<32> String;
+
+
+#define DIE(step) \
+ do { \
+ Genode::error("dying..."); \
+ perror(step); \
+ exit(1); \
+ } while (0)
+
+
+static void print(Genode::Output &output, sockaddr_in const &addr)
+{
+ print(output, (ntohl(addr.sin_addr.s_addr) >> 24) & 0xff);
+ output.out_string(".");
+ print(output, (ntohl(addr.sin_addr.s_addr) >> 16) & 0xff);
+ output.out_string(".");
+ print(output, (ntohl(addr.sin_addr.s_addr) >> 8) & 0xff);
+ output.out_string(".");
+ print(output, (ntohl(addr.sin_addr.s_addr) >> 0) & 0xff);
+ output.out_string(":");
+ print(output, ntohs(addr.sin_port));
+}
+
+
+enum Test_mode { TEST_READER_WRITER, TEST_TRADITIONAL };
+
+static Test_mode const test_mode = TEST_TRADITIONAL;
+
+
+/***************************************
+ ** Multi-threaded reader-writer test **
+ ***************************************/
+
+class Worker : public Genode::Thread_deprecated<0x4000>
+{
+ protected:
+
+ char _name[128];
+ unsigned _id;
+ int _sd;
+ char _buf[4096];
+
+ char const *_init_name(char const *type, unsigned id)
+ {
+ snprintf(_name, sizeof(_name), "%s.%u", type, id);
+ return _name;
+ }
+
+ public:
+
+ Worker(char const *type, unsigned id, int sd)
+ :
+ Genode::Thread_deprecated<0x4000>(_init_name(type, id)),
+ _id(id), _sd(sd)
+ { }
+};
+
+
+struct Reader : Worker
+{
+ Reader(unsigned id, int sd) : Worker("reader", id, sd) { }
+
+ void entry() override
+ {
+ Timer::Connection timer;
+
+ while (true) {
+// timer.msleep(100);
+
+ int const ret = ::read(_sd, _buf, sizeof(_buf));
+
+ if (ret == -1)
+ break;
+ }
+
+ Genode::log(Genode::Cstring(_name), " finished errno=", errno);
+ }
+};
+
+
+struct Writer : Worker
+{
+ Writer(unsigned id, int sd) : Worker("writer", id, sd) { }
+
+ void entry() override
+ {
+ Timer::Connection timer;
+
+ size_t size = 0;
+
+ if (0) {
+ size = sizeof(_buf);
+ memset(_buf, 'x', size - 1);
+ } else {
+ size = strlen(_name) + 1;
+ strcpy(_buf, _name);
+ }
+ _buf[size - 1] = '\n';
+
+ while (true) {
+// timer.msleep(10 + _id*5);
+
+ int const ret = ::write(_sd, _buf, size);
+
+ if (ret == -1)
+ break;
+ }
+
+ Genode::log(Genode::Cstring(_name), " finished errno=", errno);
+ }
+};
+
+
+static void test_reader_writer(int cd)
+{
+ static Reader r0(0, cd);
+ static Reader r1(1, cd);
+ static Reader r2(2, cd);
+ static Writer w0(0, cd);
+ static Writer w1(1, cd);
+ static Writer w2(2, cd);
+
+ r0.start();
+ r1.start();
+ r2.start();
+ w0.start();
+ w1.start();
+ w2.start();
+
+ Genode::sleep_forever();
+}
+
+
+/****************************************
+ ** Traditional (blocking) socket test **
+ ****************************************/
+
+static size_t test_traditional(int cd, bool const use_read_write)
+{
+ size_t count = 0;
+ int ret = 0;
+ static char data[64*1024];
+
+ while (true) {
+ if (use_read_write) {
+ ret = read(cd, data, sizeof(data));
+ if (ret == -1) DIE("read");
+ } else {
+ ret = recv(cd, data, sizeof(data), 0);
+ if (ret == -1) DIE("recv");
+ }
+
+ /* EOF */
+ if (ret == 0) return count;
+
+ if (use_read_write) {
+ ret = write(cd, data, ret);
+ if (ret == -1) DIE("write");
+ } else {
+ ret = send(cd, data, ret, 0);
+ if (ret == -1) DIE("send");
+ }
+
+ count += ret;
+ }
+}
+
+
+static void test_getnames(int sd)
+{
+ int err = 0;
+
+ sockaddr_in addr;
+ socklen_t addr_len;
+
+ memset(&addr, 0, sizeof(addr));
+ addr_len = sizeof(addr);
+
+ err = getsockname(sd, (sockaddr *)&addr, &addr_len);
+ if (err == -1) DIE("getsockname");
+
+ Genode::log("sock ", addr);
+
+ memset(&addr, 0, sizeof(addr));
+ addr_len = sizeof(addr);
+
+ err = getpeername(sd, (sockaddr *)&addr, &addr_len);
+ if (err == -1) DIE("getpeername");
+
+ Genode::log("peer ", addr);
+}
+
+
+static void server(Genode::Xml_node const config)
+{
+ int ret = 0;
+
+ Genode::log("Let's serve");
+
+ int sd = socket(AF_INET, SOCK_STREAM, 0);
+
+ Genode::log("sd=", sd);
+ if (sd == -1) DIE("socket");
+
+ unsigned const port = config.attribute_value("port", 8080U);
+ bool const use_read_write = config.attribute_value("read_write", false);
+
+ sockaddr_in const addr { 0, AF_INET, htons(port), { INADDR_ANY } };
+ sockaddr const *paddr = reinterpret_cast(&addr);
+
+ if (1) {
+ int const on = 1;
+ ret = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+ }
+
+ ret = bind(sd, paddr, sizeof(addr));
+ if (ret == -1) DIE("bind");
+
+ ret = listen(sd, SOMAXCONN);
+ if (ret == -1) DIE("listen");
+
+ bool const loop = true;
+ do {
+ Genode::log("accepting connections on ", port);
+
+ sockaddr_in caddr;
+ socklen_t scaddr = sizeof(caddr);
+ sockaddr *pcaddr = reinterpret_cast(&caddr);
+ int cd = accept(sd, pcaddr, &scaddr);
+ Genode::log("cd=", cd);
+ if (cd == -1) DIE("accept");
+
+ test_getnames(cd);
+
+ if (0) {
+ test_reader_writer(cd);
+ } else {
+ size_t const count = test_traditional(cd, use_read_write);
+ Genode::log("echoed ", count, " bytes");
+ }
+
+ ret = shutdown(cd, SHUT_RDWR);
+ if (ret == -1) DIE("shutdown");
+
+ ret = close(cd);
+ if (ret == -1) DIE("close");
+
+ } while (loop);
+}
+
+
+static void client(Genode::Xml_node const config)
+{
+ int ret = 0;
+
+ Genode::log("Let's connect");
+
+ int sd = socket(AF_INET, SOCK_STREAM, 0);
+
+ Genode::log("sd=", sd);
+ if (sd == -1) DIE("socket");
+
+ String const ip(config.attribute_value("ip", String("10.0.2.1")));
+ unsigned const port(config.attribute_value("port", 8080U));
+
+ Genode::log("Connecting to %s:%u", ip.string(), port);
+
+ sockaddr_in const addr { 0, AF_INET, htons(port), { inet_addr(ip.string()) } };
+ sockaddr const *paddr = reinterpret_cast(&addr);
+
+ ret = connect(sd, paddr, sizeof(addr));
+ if (ret == -1) DIE("connect");
+
+ Genode::log("connected");
+
+ static char data[1*1024*1024];
+ memset(data, 'X', sizeof(data));
+
+ /* wait for go */
+ char go;
+ ret = recv(sd, &go, sizeof(go), 0);
+ if (ret == -1) DIE("recv");
+
+ /* EOF */
+ if (ret == 0) DIE("EOF");
+
+ ret = send(sd, data, sizeof(data), 0);
+ if (ret == -1) DIE("send");
+
+ ret = shutdown(sd, SHUT_RDWR);
+ if (ret == -1) DIE("shutdown");
+
+ ret = close(sd);
+ if (ret == -1) DIE("close");
+}
+
+
+struct Main
+{
+ String mode { "server" };
+
+ Main(Genode::Env &env)
+ {
+ Libc::with_libc([&] () {
+ Genode::Xml_node config { "" };
+
+ /* parse mode configuration */
+ try {
+ static Genode::Attached_rom_dataspace rom(env, "config");
+
+ config = rom.xml();
+ } catch (Genode::Rom_connection::Rom_connection_failed) { }
+
+ mode = config.attribute_value("mode", mode);
+
+ if (mode == "server") {
+ server(config);
+ } else if (mode == "client") {
+ client(config);
+ } else {
+ Genode::error("unknown mode '", mode.string(), "'");
+ exit(__LINE__);
+ }
+ });
+ }
+};
+
+
+void Libc::Component::construct(Libc::Env &env) { static Main inst(env); }
diff --git a/repos/libports/src/test/netty/target.mk b/repos/libports/src/test/netty/target.mk
new file mode 100644
index 0000000000..32f949039c
--- /dev/null
+++ b/repos/libports/src/test/netty/target.mk
@@ -0,0 +1,3 @@
+TARGET = test-netty
+SRC_CC = main.cc
+LIBS = stdcxx libc_pipe libc_resolv