diff --git a/docs/c-api/net.h/struct_mg_connection.md b/docs/c-api/net.h/struct_mg_connection.md
index 8c5a8b1048949d6ce1082e3b26c1259bf0d0d1d9..be69d16fd60f8d04c70a1a1c1febd3a93e026be2 100644
--- a/docs/c-api/net.h/struct_mg_connection.md
+++ b/docs/c-api/net.h/struct_mg_connection.md
@@ -48,12 +48,11 @@ signature: |
   #define MG_F_IS_WEBSOCKET (1 << 8)       /* Websocket specific */
   
   /* Flags that are settable by user */
-  #define MG_F_SEND_AND_CLOSE (1 << 10)       /* Push remaining data and close  */
-  #define MG_F_CLOSE_IMMEDIATELY (1 << 11)    /* Disconnect */
-  #define MG_F_WEBSOCKET_NO_DEFRAG (1 << 12)  /* Websocket specific */
-  #define MG_F_DELETE_CHUNK (1 << 13)         /* HTTP specific */
-  #define MG_F_ENABLE_BROADCAST (1 << 14)     /* Allow broadcast address usage */
-  #define MG_F_TUN_DO_NOT_RECONNECT (1 << 15) /* Don't reconnect tunnel */
+  #define MG_F_SEND_AND_CLOSE (1 << 10)      /* Push remaining data and close  */
+  #define MG_F_CLOSE_IMMEDIATELY (1 << 11)   /* Disconnect */
+  #define MG_F_WEBSOCKET_NO_DEFRAG (1 << 12) /* Websocket specific */
+  #define MG_F_DELETE_CHUNK (1 << 13)        /* HTTP specific */
+  #define MG_F_ENABLE_BROADCAST (1 << 14)    /* Allow broadcast address usage */
   
   #define MG_F_USER_1 (1 << 20) /* Flags left for application */
   #define MG_F_USER_2 (1 << 21)
diff --git a/mongoose.c b/mongoose.c
index e47801bbdb808387df90d3d9c0f494c6aef8d19e..5dbb9b3f7eed40625b80d6cadcfbe9d9df2c53f1 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -2146,93 +2146,6 @@ size_t mg_match_prefix(const char *pattern, int pattern_len, const char *str) {
 
 #endif /* EXCLUDE_COMMON */
 #ifdef MG_MODULE_LINES
-#line 1 "mongoose/src/tun.h"
-#endif
-/*
- * Copyright (c) 2014-2016 Cesanta Software Limited
- * All rights reserved
- */
-
-#ifndef CS_MONGOOSE_SRC_TUN_H_
-#define CS_MONGOOSE_SRC_TUN_H_
-
-#if MG_ENABLE_TUN
-
-/* Amalgamated: #include "mongoose/src/net.h" */
-/* Amalgamated: #include "common/mg_str.h" */
-
-#ifndef MG_TUN_RECONNECT_INTERVAL
-#define MG_TUN_RECONNECT_INTERVAL 1
-#endif
-
-#define MG_TUN_PROTO_NAME "mg_tun"
-
-#define MG_TUN_DATA_FRAME 0x0
-#define MG_TUN_F_END_STREAM 0x1
-
-/*
- * MG TUN frame format is loosely based on HTTP/2.
- * However since the communication happens via WebSocket
- * there is no need to encode the frame length, since that's
- * solved by WebSocket framing.
- *
- * TODO(mkm): Detailed description of the protocol.
- */
-struct mg_tun_frame {
-  uint8_t type;
-  uint8_t flags;
-  uint32_t stream_id; /* opaque stream identifier */
-  struct mg_str body;
-};
-
-struct mg_tun_ssl_opts {
-#if MG_ENABLE_SSL
-  const char *ssl_cert;
-  const char *ssl_key;
-  const char *ssl_ca_cert;
-#else
-  int dummy; /* some compilers don't like empty structs */
-#endif
-};
-
-struct mg_tun_client {
-  struct mg_mgr *mgr;
-  struct mg_iface *iface;
-  const char *disp_url;
-  struct mg_tun_ssl_opts ssl;
-
-  uint32_t last_stream_id; /* stream id of most recently accepted connection */
-
-  struct mg_connection *disp;
-  struct mg_connection *listener;
-  struct mg_connection *reconnect;
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-struct mg_connection *mg_tun_bind_opt(struct mg_mgr *mgr,
-                                      const char *dispatcher,
-                                      MG_CB(mg_event_handler_t handler,
-                                            void *user_data),
-                                      struct mg_bind_opts opts);
-
-int mg_tun_parse_frame(void *data, size_t len, struct mg_tun_frame *frame);
-
-void mg_tun_send_frame(struct mg_connection *ws, uint32_t stream_id,
-                       uint8_t type, uint8_t flags, struct mg_str msg);
-
-void mg_tun_destroy_client(struct mg_tun_client *client);
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-
-#endif /* MG_ENABLE_TUN */
-
-#endif /* CS_MONGOOSE_SRC_TUN_H_ */
-#ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/net.c"
 #endif
 /*
@@ -2257,7 +2170,6 @@ void mg_tun_destroy_client(struct mg_tun_client *client);
 /* Amalgamated: #include "mongoose/src/dns.h" */
 /* Amalgamated: #include "mongoose/src/internal.h" */
 /* Amalgamated: #include "mongoose/src/resolv.h" */
-/* Amalgamated: #include "mongoose/src/tun.h" */
 /* Amalgamated: #include "mongoose/src/util.h" */
 
 #define MG_MAX_HOST_LEN 200
@@ -3053,13 +2965,6 @@ struct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,
 
   MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);
 
-#if MG_ENABLE_TUN
-  if (mg_strncmp(mg_mk_str(address), mg_mk_str("ws://"), 5) == 0 ||
-      mg_strncmp(mg_mk_str(address), mg_mk_str("wss://"), 6) == 0) {
-    return mg_tun_bind_opt(mgr, address, MG_CB(callback, user_data), opts);
-  }
-#endif
-
   if (mg_parse_address(address, &sa, &proto, host, sizeof(host)) <= 0) {
     MG_SET_PTRPTR(opts.error_string, "cannot parse address");
     return NULL;
@@ -3280,39 +3185,6 @@ extern const struct mg_iface_vtable mg_socket_iface_vtable;
 
 #endif /* CS_MONGOOSE_SRC_NET_IF_SOCKET_H_ */
 #ifdef MG_MODULE_LINES
-#line 1 "mongoose/src/net_if_tun.h"
-#endif
-/*
- * Copyright (c) 2014-2016 Cesanta Software Limited
- * All rights reserved
- */
-
-#ifndef CS_MONGOOSE_SRC_NET_IF_TUN_H_
-#define CS_MONGOOSE_SRC_NET_IF_TUN_H_
-
-#if MG_ENABLE_TUN
-
-/* Amalgamated: #include "mongoose/src/net_if.h" */
-
-struct mg_tun_client;
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-extern const struct mg_iface_vtable mg_tun_iface_vtable;
-
-struct mg_connection *mg_tun_if_find_conn(struct mg_tun_client *client,
-                                          uint32_t stream_id);
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-
-#endif /* MG_ENABLE_TUN */
-
-#endif /* CS_MONGOOSE_SRC_NET_IF_TUN_H_ */
-#ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/net_if_socks.h"
 #endif
 /*
@@ -3343,15 +3215,11 @@ extern const struct mg_iface_vtable mg_socks_iface_vtable;
 /* Amalgamated: #include "mongoose/src/net_if.h" */
 /* Amalgamated: #include "mongoose/src/internal.h" */
 /* Amalgamated: #include "mongoose/src/net_if_socket.h" */
-/* Amalgamated: #include "mongoose/src/net_if_tun.h" */
 
 extern const struct mg_iface_vtable mg_default_iface_vtable;
 
 const struct mg_iface_vtable *mg_ifaces[] = {
     &mg_default_iface_vtable,
-#if MG_ENABLE_TUN
-    &mg_tun_iface_vtable,
-#endif
 };
 
 int mg_num_ifaces = (int) (sizeof(mg_ifaces) / sizeof(mg_ifaces[0]));
@@ -4350,180 +4218,6 @@ struct mg_iface *mg_socks_mk_iface(struct mg_mgr *mgr, const char *proxy_addr) {
 
 #endif
 #ifdef MG_MODULE_LINES
-#line 1 "mongoose/src/net_if_tun.c"
-#endif
-/*
- * Copyright (c) 2014-2016 Cesanta Software Limited
- * All rights reserved
- */
-
-#if MG_ENABLE_TUN
-
-/* Amalgamated: #include "common/cs_dbg.h" */
-/* Amalgamated: #include "common/cs_time.h" */
-/* Amalgamated: #include "mongoose/src/internal.h" */
-/* Amalgamated: #include "mongoose/src/net_if_tun.h" */
-/* Amalgamated: #include "mongoose/src/tun.h" */
-/* Amalgamated: #include "mongoose/src/util.h" */
-
-#define MG_TCP_RECV_BUFFER_SIZE 1024
-#define MG_UDP_RECV_BUFFER_SIZE 1500
-
-void mg_tun_if_connect_tcp(struct mg_connection *nc,
-                           const union socket_address *sa) {
-  (void) nc;
-  (void) sa;
-}
-
-void mg_tun_if_connect_udp(struct mg_connection *nc) {
-  (void) nc;
-}
-
-int mg_tun_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
-  (void) nc;
-  (void) sa;
-  return 0;
-}
-
-int mg_tun_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
-  (void) nc;
-  (void) sa;
-  return -1;
-}
-
-void mg_tun_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) {
-  struct mg_tun_client *client = (struct mg_tun_client *) nc->iface->data;
-  uint32_t stream_id = (uint32_t)(uintptr_t) nc->mgr_data;
-  struct mg_str msg = {(char *) buf, len};
-#if MG_ENABLE_HEXDUMP
-  char hex[512];
-  mg_hexdump(buf, len, hex, sizeof(hex));
-  LOG(LL_DEBUG, ("sending to stream 0x%x:\n%s", (unsigned int) stream_id, hex));
-#endif
-
-  mg_tun_send_frame(client->disp, stream_id, MG_TUN_DATA_FRAME, 0, msg);
-}
-
-void mg_tun_if_udp_send(struct mg_connection *nc, const void *buf, size_t len) {
-  (void) nc;
-  (void) buf;
-  (void) len;
-}
-
-void mg_tun_if_recved(struct mg_connection *nc, size_t len) {
-  (void) nc;
-  (void) len;
-}
-
-int mg_tun_if_create_conn(struct mg_connection *nc) {
-  (void) nc;
-  return 1;
-}
-
-void mg_tun_if_destroy_conn(struct mg_connection *nc) {
-  struct mg_tun_client *client = (struct mg_tun_client *) nc->iface->data;
-
-  if (nc->flags & MG_F_LISTENING) {
-    mg_tun_destroy_client(client);
-  } else if (client->disp) {
-    uint32_t stream_id = (uint32_t)(uintptr_t) nc->mgr_data;
-    struct mg_str msg = {NULL, 0};
-
-    LOG(LL_DEBUG, ("closing 0x%x:", (unsigned int) stream_id));
-    mg_tun_send_frame(client->disp, stream_id, MG_TUN_DATA_FRAME,
-                      MG_TUN_F_END_STREAM, msg);
-  }
-}
-
-/* Associate a socket to a connection. */
-void mg_tun_if_sock_set(struct mg_connection *nc, sock_t sock) {
-  (void) nc;
-  (void) sock;
-}
-
-void mg_tun_if_init(struct mg_iface *iface) {
-  (void) iface;
-}
-
-void mg_tun_if_free(struct mg_iface *iface) {
-  (void) iface;
-}
-
-void mg_tun_if_add_conn(struct mg_connection *nc) {
-  nc->sock = INVALID_SOCKET;
-}
-
-void mg_tun_if_remove_conn(struct mg_connection *nc) {
-  (void) nc;
-}
-
-time_t mg_tun_if_poll(struct mg_iface *iface, int timeout_ms) {
-  (void) iface;
-  (void) timeout_ms;
-  return (time_t) cs_time();
-}
-
-void mg_tun_if_get_conn_addr(struct mg_connection *nc, int remote,
-                             union socket_address *sa) {
-  (void) nc;
-  (void) remote;
-  (void) sa;
-}
-
-struct mg_connection *mg_tun_if_find_conn(struct mg_tun_client *client,
-                                          uint32_t stream_id) {
-  struct mg_connection *nc = NULL;
-
-  for (nc = client->mgr->active_connections; nc != NULL; nc = nc->next) {
-    if (nc->iface != client->iface || (nc->flags & MG_F_LISTENING)) {
-      continue;
-    }
-    if (stream_id == (uint32_t)(uintptr_t) nc->mgr_data) {
-      return nc;
-    }
-  }
-
-  if (stream_id > client->last_stream_id) {
-    /* create a new connection */
-    LOG(LL_DEBUG, ("new stream 0x%x, accepting", (unsigned int) stream_id));
-    nc = mg_if_accept_new_conn(client->listener);
-    nc->mgr_data = (void *) (uintptr_t) stream_id;
-    client->last_stream_id = stream_id;
-  } else {
-    LOG(LL_DEBUG,
-        ("Ignoring stream 0x%x (last_stream_id 0x%x)", (unsigned int) stream_id,
-         (unsigned int) client->last_stream_id));
-  }
-
-  return nc;
-}
-
-/* clang-format off */
-#define MG_TUN_IFACE_VTABLE                                             \
-  {                                                                     \
-    mg_tun_if_init,                                                     \
-    mg_tun_if_free,                                                     \
-    mg_tun_if_add_conn,                                                 \
-    mg_tun_if_remove_conn,                                              \
-    mg_tun_if_poll,                                                     \
-    mg_tun_if_listen_tcp,                                               \
-    mg_tun_if_listen_udp,                                               \
-    mg_tun_if_connect_tcp,                                              \
-    mg_tun_if_connect_udp,                                              \
-    mg_tun_if_tcp_send,                                                 \
-    mg_tun_if_udp_send,                                                 \
-    mg_tun_if_recved,                                                   \
-    mg_tun_if_create_conn,                                              \
-    mg_tun_if_destroy_conn,                                             \
-    mg_tun_if_sock_set,                                                 \
-    mg_tun_if_get_conn_addr,                                            \
-  }
-/* clang-format on */
-
-const struct mg_iface_vtable mg_tun_iface_vtable = MG_TUN_IFACE_VTABLE;
-
-#endif /* MG_ENABLE_TUN */
-#ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/ssl_if_openssl.c"
 #endif
 /*
@@ -12439,324 +12133,6 @@ int mg_set_protocol_coap(struct mg_connection *nc) {
 
 #endif /* MG_ENABLE_COAP */
 #ifdef MG_MODULE_LINES
-#line 1 "mongoose/src/tun.c"
-#endif
-/*
- * Copyright (c) 2014 Cesanta Software Limited
- * All rights reserved
- */
-
-#if MG_ENABLE_TUN
-
-/* Amalgamated: #include "common/cs_dbg.h" */
-/* Amalgamated: #include "mongoose/src/http.h" */
-/* Amalgamated: #include "mongoose/src/internal.h" */
-/* Amalgamated: #include "mongoose/src/net.h" */
-/* Amalgamated: #include "mongoose/src/net_if_tun.h" */
-/* Amalgamated: #include "mongoose/src/tun.h" */
-/* Amalgamated: #include "mongoose/src/util.h" */
-
-static void mg_tun_reconnect(struct mg_tun_client *client, int timeout);
-
-static void mg_tun_init_client(struct mg_tun_client *client, struct mg_mgr *mgr,
-                               struct mg_iface *iface, const char *dispatcher,
-                               struct mg_tun_ssl_opts ssl) {
-  client->mgr = mgr;
-  client->iface = iface;
-  client->disp_url = dispatcher;
-  client->last_stream_id = 0;
-  client->ssl = ssl;
-
-  client->disp = NULL;      /* will be set by mg_tun_reconnect */
-  client->listener = NULL;  /* will be set by mg_do_bind */
-  client->reconnect = NULL; /* will be set by mg_tun_reconnect */
-}
-
-void mg_tun_log_frame(struct mg_tun_frame *frame) {
-  LOG(LL_DEBUG, ("Got TUN frame: type=0x%x, flags=0x%x stream_id=0x%x, "
-                 "len=%d",
-                 frame->type, frame->flags, (unsigned int) frame->stream_id,
-                 (int) frame->body.len));
-#if MG_ENABLE_HEXDUMP
-  {
-    char hex[512];
-    mg_hexdump(frame->body.p, frame->body.len, hex, sizeof(hex) - 1);
-    hex[sizeof(hex) - 1] = '\0';
-    LOG(LL_DEBUG, ("body:\n%s", hex));
-  }
-#else
-  LOG(LL_DEBUG, ("body: '%.*s'", (int) frame->body.len, frame->body.p));
-#endif
-}
-
-static void mg_tun_close_all(struct mg_tun_client *client) {
-  struct mg_connection *nc;
-  for (nc = client->mgr->active_connections; nc != NULL; nc = nc->next) {
-    if (nc->iface == client->iface && !(nc->flags & MG_F_LISTENING)) {
-      LOG(LL_DEBUG, ("Closing tunneled connection %p", nc));
-      nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-      /* mg_close_conn(nc); */
-    }
-  }
-}
-
-static void mg_tun_client_handler(struct mg_connection *nc, int ev,
-                                  void *ev_data MG_UD_ARG(void *user_data)) {
-#if !MG_ENABLE_CALLBACK_USERDATA
-  void *user_data = nc->user_data;
-#else
-  (void) nc;
-#endif
-  struct mg_tun_client *client = (struct mg_tun_client *) user_data;
-
-  switch (ev) {
-    case MG_EV_CONNECT: {
-      int err = *(int *) ev_data;
-
-      if (err) {
-        LOG(LL_ERROR, ("Cannot connect to the tunnel dispatcher: %d", err));
-      } else {
-        LOG(LL_INFO, ("Connected to the tunnel dispatcher"));
-      }
-      break;
-    }
-    case MG_EV_HTTP_REPLY: {
-      struct http_message *hm = (struct http_message *) ev_data;
-
-      if (hm->resp_code != 200) {
-        LOG(LL_ERROR,
-            ("Tunnel dispatcher reply non-OK status code %d", hm->resp_code));
-      }
-      break;
-    }
-    case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
-      LOG(LL_INFO, ("Tunnel dispatcher handshake done"));
-      break;
-    }
-    case MG_EV_WEBSOCKET_FRAME: {
-      struct websocket_message *wm = (struct websocket_message *) ev_data;
-      struct mg_connection *tc;
-      struct mg_tun_frame frame;
-
-      if (mg_tun_parse_frame(wm->data, wm->size, &frame) == -1) {
-        LOG(LL_ERROR, ("Got invalid tun frame dropping"));
-        break;
-      }
-
-      mg_tun_log_frame(&frame);
-
-      tc = mg_tun_if_find_conn(client, frame.stream_id);
-      if (tc == NULL) {
-        if (frame.body.len > 0) {
-          LOG(LL_DEBUG, ("Got frame after receiving end has been closed"));
-        }
-        break;
-      }
-      if (frame.body.len > 0) {
-        mg_if_recv_tcp_cb(tc, (void *) frame.body.p, frame.body.len,
-                          0 /* own */);
-      }
-      if (frame.flags & MG_TUN_F_END_STREAM) {
-        LOG(LL_DEBUG, ("Closing tunneled connection because got end of stream "
-                       "from other end"));
-        tc->flags |= MG_F_CLOSE_IMMEDIATELY;
-        mg_close_conn(tc);
-      }
-      break;
-    }
-    case MG_EV_CLOSE: {
-      LOG(LL_DEBUG, ("Closing all tunneled connections"));
-      /*
-       * The client might have been already freed when the listening socket is
-       * closed.
-       */
-      if (client != NULL) {
-        mg_tun_close_all(client);
-        client->disp = NULL;
-        LOG(LL_INFO, ("Dispatcher connection is no more, reconnecting"));
-        /* TODO(mkm): implement exp back off */
-        mg_tun_reconnect(client, MG_TUN_RECONNECT_INTERVAL);
-      }
-      break;
-    }
-    default:
-      break;
-  }
-}
-
-static void mg_tun_do_reconnect(struct mg_tun_client *client) {
-  struct mg_connection *dc;
-  struct mg_connect_opts opts;
-  memset(&opts, 0, sizeof(opts));
-#if MG_ENABLE_SSL
-  opts.ssl_cert = client->ssl.ssl_cert;
-  opts.ssl_key = client->ssl.ssl_key;
-  opts.ssl_ca_cert = client->ssl.ssl_ca_cert;
-#endif
-  /* HTTP/Websocket listener */
-  if ((dc = mg_connect_ws_opt(client->mgr, MG_CB(mg_tun_client_handler, client),
-                              opts, client->disp_url, MG_TUN_PROTO_NAME,
-                              NULL)) == NULL) {
-    LOG(LL_ERROR,
-        ("Cannot connect to WS server on addr [%s]\n", client->disp_url));
-    return;
-  }
-
-  client->disp = dc;
-#if !MG_ENABLE_CALLBACK_USERDATA
-  dc->user_data = client;
-#endif
-}
-
-void mg_tun_reconnect_ev_handler(struct mg_connection *nc, int ev,
-                                 void *ev_data MG_UD_ARG(void *user_data)) {
-#if !MG_ENABLE_CALLBACK_USERDATA
-  void *user_data = nc->user_data;
-#else
-  (void) nc;
-#endif
-  struct mg_tun_client *client = (struct mg_tun_client *) user_data;
-  (void) ev_data;
-
-  switch (ev) {
-    case MG_EV_TIMER:
-      if (!(client->listener->flags & MG_F_TUN_DO_NOT_RECONNECT)) {
-        mg_tun_do_reconnect(client);
-      } else {
-        /* Reconnecting is suppressed, we'll check again at the next poll */
-        mg_tun_reconnect(client, 0);
-      }
-      break;
-  }
-}
-
-static void mg_tun_reconnect(struct mg_tun_client *client, int timeout) {
-  if (client->reconnect == NULL) {
-    client->reconnect = mg_add_sock(client->mgr, INVALID_SOCKET,
-                                    MG_CB(mg_tun_reconnect_ev_handler, client));
-#if !MG_ENABLE_CALLBACK_USERDATA
-    client->reconnect->user_data = client;
-#endif
-  }
-  client->reconnect->ev_timer_time = mg_time() + timeout;
-}
-
-static struct mg_tun_client *mg_tun_create_client(struct mg_mgr *mgr,
-                                                  const char *dispatcher,
-                                                  struct mg_tun_ssl_opts ssl) {
-  struct mg_tun_client *client = NULL;
-  struct mg_iface *iface = mg_find_iface(mgr, &mg_tun_iface_vtable, NULL);
-  if (iface == NULL) {
-    LOG(LL_ERROR, ("The tun feature requires the manager to have a tun "
-                   "interface enabled"));
-    return NULL;
-  }
-
-  client = (struct mg_tun_client *) MG_MALLOC(sizeof(*client));
-  mg_tun_init_client(client, mgr, iface, dispatcher, ssl);
-  iface->data = client;
-
-  /*
-   * We need to give application a chance to set MG_F_TUN_DO_NOT_RECONNECT on a
-   * listening connection right after mg_tun_bind_opt() returned it, so we
-   * should use mg_tun_reconnect() here, instead of mg_tun_do_reconnect()
-   */
-  mg_tun_reconnect(client, 0);
-  return client;
-}
-
-void mg_tun_destroy_client(struct mg_tun_client *client) {
-  /*
-   *  NOTE:
-   * `client` is NULL in case of OOM
-   * `client->disp` is NULL if connection failed
-   * `client->iface is NULL is `mg_find_iface` failed
-   */
-
-  if (client != NULL && client->disp != NULL) {
-    /* the dispatcher connection handler will in turn close all tunnels */
-    client->disp->flags |= MG_F_CLOSE_IMMEDIATELY;
-    /* this is used as a signal to other tun handlers that the party is over */
-    client->disp->user_data = NULL;
-  }
-
-  if (client != NULL && client->reconnect != NULL) {
-    client->reconnect->flags |= MG_F_CLOSE_IMMEDIATELY;
-  }
-
-  if (client != NULL && client->iface != NULL) {
-    client->iface->data = NULL;
-  }
-
-  MG_FREE(client);
-}
-
-static struct mg_connection *mg_tun_do_bind(struct mg_tun_client *client,
-                                            MG_CB(mg_event_handler_t handler,
-                                                  void *user_data),
-                                            struct mg_bind_opts opts) {
-  struct mg_connection *lc;
-  opts.iface = client->iface;
-  lc = mg_bind_opt(client->mgr, ":1234" /* dummy port */,
-                   MG_CB(handler, user_data), opts);
-  client->listener = lc;
-  return lc;
-}
-
-struct mg_connection *mg_tun_bind_opt(struct mg_mgr *mgr,
-                                      const char *dispatcher,
-                                      MG_CB(mg_event_handler_t handler,
-                                            void *user_data),
-                                      struct mg_bind_opts opts) {
-#if MG_ENABLE_SSL
-  struct mg_tun_ssl_opts ssl = {opts.ssl_cert, opts.ssl_key, opts.ssl_ca_cert};
-#else
-  struct mg_tun_ssl_opts ssl = {0};
-#endif
-  struct mg_tun_client *client = mg_tun_create_client(mgr, dispatcher, ssl);
-  if (client == NULL) {
-    return NULL;
-  }
-#if MG_ENABLE_SSL
-  /* these options don't make sense in the local mouth of the tunnel */
-  opts.ssl_cert = NULL;
-  opts.ssl_key = NULL;
-  opts.ssl_ca_cert = NULL;
-#endif
-  return mg_tun_do_bind(client, MG_CB(handler, user_data), opts);
-}
-
-int mg_tun_parse_frame(void *data, size_t len, struct mg_tun_frame *frame) {
-  const size_t header_size = sizeof(uint32_t) + sizeof(uint8_t) * 2;
-  if (len < header_size) {
-    return -1;
-  }
-
-  frame->type = *(uint8_t *) (data);
-  frame->flags = *(uint8_t *) ((char *) data + 1);
-  memcpy(&frame->stream_id, (char *) data + 2, sizeof(uint32_t));
-  frame->stream_id = ntohl(frame->stream_id);
-  frame->body.p = (char *) data + header_size;
-  frame->body.len = len - header_size;
-  return 0;
-}
-
-void mg_tun_send_frame(struct mg_connection *ws, uint32_t stream_id,
-                       uint8_t type, uint8_t flags, struct mg_str msg) {
-  stream_id = htonl(stream_id);
-  {
-    struct mg_str parts[] = {
-        {(char *) &type, sizeof(type)},
-        {(char *) &flags, sizeof(flags)},
-        {(char *) &stream_id, sizeof(stream_id)},
-        {msg.p, msg.len} /* vc6 doesn't like just `msg` here */};
-    mg_send_websocket_framev(ws, WEBSOCKET_OP_BINARY, parts,
-                             sizeof(parts) / sizeof(parts[0]));
-  }
-}
-
-#endif /* MG_ENABLE_TUN */
-#ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/sntp.c"
 #endif
 /*
diff --git a/mongoose.h b/mongoose.h
index b6905447066df169808f307cf54f858664926606..64676ba4318ccaca123ebd998b87e30b80930a6b 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -3157,10 +3157,6 @@ struct {								\
   (CS_PLATFORM == CS_P_WINDOWS || CS_PLATFORM == CS_P_UNIX)
 #endif
 
-#ifndef MG_ENABLE_TUN
-#define MG_ENABLE_TUN MG_ENABLE_HTTP_WEBSOCKET
-#endif
-
 #ifndef MG_ENABLE_SNTP
 #define MG_ENABLE_SNTP 0
 #endif
@@ -3519,12 +3515,11 @@ struct mg_connection {
 #define MG_F_IS_WEBSOCKET (1 << 8)       /* Websocket specific */
 
 /* Flags that are settable by user */
-#define MG_F_SEND_AND_CLOSE (1 << 10)       /* Push remaining data and close  */
-#define MG_F_CLOSE_IMMEDIATELY (1 << 11)    /* Disconnect */
-#define MG_F_WEBSOCKET_NO_DEFRAG (1 << 12)  /* Websocket specific */
-#define MG_F_DELETE_CHUNK (1 << 13)         /* HTTP specific */
-#define MG_F_ENABLE_BROADCAST (1 << 14)     /* Allow broadcast address usage */
-#define MG_F_TUN_DO_NOT_RECONNECT (1 << 15) /* Don't reconnect tunnel */
+#define MG_F_SEND_AND_CLOSE (1 << 10)      /* Push remaining data and close  */
+#define MG_F_CLOSE_IMMEDIATELY (1 << 11)   /* Disconnect */
+#define MG_F_WEBSOCKET_NO_DEFRAG (1 << 12) /* Websocket specific */
+#define MG_F_DELETE_CHUNK (1 << 13)        /* HTTP specific */
+#define MG_F_ENABLE_BROADCAST (1 << 14)    /* Allow broadcast address usage */
 
 #define MG_F_USER_1 (1 << 20) /* Flags left for application */
 #define MG_F_USER_2 (1 << 21)
diff --git a/src/features.h b/src/features.h
index a0bd14de0e005086301279c3a45db44494ba166e..515242d276ab67c30c71510891eae966419182d4 100644
--- a/src/features.h
+++ b/src/features.h
@@ -153,10 +153,6 @@
   (CS_PLATFORM == CS_P_WINDOWS || CS_PLATFORM == CS_P_UNIX)
 #endif
 
-#ifndef MG_ENABLE_TUN
-#define MG_ENABLE_TUN MG_ENABLE_HTTP_WEBSOCKET
-#endif
-
 #ifndef MG_ENABLE_SNTP
 #define MG_ENABLE_SNTP 0
 #endif
diff --git a/src/modules.mk b/src/modules.mk
index f68f4adbef9f742f70a1b82be98ebf1e5ae03ffb..93c3539d46cddd6a66cad354b78ea6c618b37096 100644
--- a/src/modules.mk
+++ b/src/modules.mk
@@ -59,15 +59,12 @@ SOURCES = $(COMMON)/mg_mem.h \
           $(COMMON)/mbuf.c \
           $(COMMON)/mg_str.c \
           $(COMMON)/str_util.c \
-          tun.h \
           net.c \
           net_if_socket.h \
-          net_if_tun.h \
           net_if_socks.h \
           net_if.c \
           net_if_socket.c \
           net_if_socks.c \
-          net_if_tun.c \
           ssl_if_openssl.c \
           ssl_if_mbedtls.c \
           uri.c \
@@ -83,7 +80,6 @@ SOURCES = $(COMMON)/mg_mem.h \
           dns_server.c \
           resolv.c \
           coap.c \
-          tun.c \
           sntp.c \
           socks.c \
           $(COMMON)/platforms/cc3200/cc3200_libc.c \
diff --git a/src/net.c b/src/net.c
index efd6d6204f3697ef76032be94d46f5eff2dd0b51..e7496b72f30581d011684c5a9c1cda31534d438d 100644
--- a/src/net.c
+++ b/src/net.c
@@ -20,7 +20,6 @@
 #include "mongoose/src/dns.h"
 #include "mongoose/src/internal.h"
 #include "mongoose/src/resolv.h"
-#include "mongoose/src/tun.h"
 #include "mongoose/src/util.h"
 
 #define MG_MAX_HOST_LEN 200
@@ -816,13 +815,6 @@ struct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,
 
   MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);
 
-#if MG_ENABLE_TUN
-  if (mg_strncmp(mg_mk_str(address), mg_mk_str("ws://"), 5) == 0 ||
-      mg_strncmp(mg_mk_str(address), mg_mk_str("wss://"), 6) == 0) {
-    return mg_tun_bind_opt(mgr, address, MG_CB(callback, user_data), opts);
-  }
-#endif
-
   if (mg_parse_address(address, &sa, &proto, host, sizeof(host)) <= 0) {
     MG_SET_PTRPTR(opts.error_string, "cannot parse address");
     return NULL;
diff --git a/src/net.h b/src/net.h
index dab38eeb8fbd861bd77fe4c3e59dbe82bfa4cebd..1ecb869c30174efdbec4f692615e0f4b66bd8cf2 100644
--- a/src/net.h
+++ b/src/net.h
@@ -141,12 +141,11 @@ struct mg_connection {
 #define MG_F_IS_WEBSOCKET (1 << 8)       /* Websocket specific */
 
 /* Flags that are settable by user */
-#define MG_F_SEND_AND_CLOSE (1 << 10)       /* Push remaining data and close  */
-#define MG_F_CLOSE_IMMEDIATELY (1 << 11)    /* Disconnect */
-#define MG_F_WEBSOCKET_NO_DEFRAG (1 << 12)  /* Websocket specific */
-#define MG_F_DELETE_CHUNK (1 << 13)         /* HTTP specific */
-#define MG_F_ENABLE_BROADCAST (1 << 14)     /* Allow broadcast address usage */
-#define MG_F_TUN_DO_NOT_RECONNECT (1 << 15) /* Don't reconnect tunnel */
+#define MG_F_SEND_AND_CLOSE (1 << 10)      /* Push remaining data and close  */
+#define MG_F_CLOSE_IMMEDIATELY (1 << 11)   /* Disconnect */
+#define MG_F_WEBSOCKET_NO_DEFRAG (1 << 12) /* Websocket specific */
+#define MG_F_DELETE_CHUNK (1 << 13)        /* HTTP specific */
+#define MG_F_ENABLE_BROADCAST (1 << 14)    /* Allow broadcast address usage */
 
 #define MG_F_USER_1 (1 << 20) /* Flags left for application */
 #define MG_F_USER_2 (1 << 21)
diff --git a/src/net_if.c b/src/net_if.c
index 1c979666b87962f50239cdb6a8696618735d7b7b..fee459949efe1519e102f24bdbd2e0caec911c3a 100644
--- a/src/net_if.c
+++ b/src/net_if.c
@@ -1,15 +1,11 @@
 #include "mongoose/src/net_if.h"
 #include "mongoose/src/internal.h"
 #include "mongoose/src/net_if_socket.h"
-#include "mongoose/src/net_if_tun.h"
 
 extern const struct mg_iface_vtable mg_default_iface_vtable;
 
 const struct mg_iface_vtable *mg_ifaces[] = {
     &mg_default_iface_vtable,
-#if MG_ENABLE_TUN
-    &mg_tun_iface_vtable,
-#endif
 };
 
 int mg_num_ifaces = (int) (sizeof(mg_ifaces) / sizeof(mg_ifaces[0]));
diff --git a/src/net_if_tun.c b/src/net_if_tun.c
deleted file mode 100644
index 9785eb8503a38bda49dc0a808d88b19d4eb33b0f..0000000000000000000000000000000000000000
--- a/src/net_if_tun.c
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (c) 2014-2016 Cesanta Software Limited
- * All rights reserved
- */
-
-#if MG_ENABLE_TUN
-
-#include "common/cs_dbg.h"
-#include "common/cs_time.h"
-#include "mongoose/src/internal.h"
-#include "mongoose/src/net_if_tun.h"
-#include "mongoose/src/tun.h"
-#include "mongoose/src/util.h"
-
-#define MG_TCP_RECV_BUFFER_SIZE 1024
-#define MG_UDP_RECV_BUFFER_SIZE 1500
-
-void mg_tun_if_connect_tcp(struct mg_connection *nc,
-                           const union socket_address *sa) {
-  (void) nc;
-  (void) sa;
-}
-
-void mg_tun_if_connect_udp(struct mg_connection *nc) {
-  (void) nc;
-}
-
-int mg_tun_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
-  (void) nc;
-  (void) sa;
-  return 0;
-}
-
-int mg_tun_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
-  (void) nc;
-  (void) sa;
-  return -1;
-}
-
-void mg_tun_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) {
-  struct mg_tun_client *client = (struct mg_tun_client *) nc->iface->data;
-  uint32_t stream_id = (uint32_t)(uintptr_t) nc->mgr_data;
-  struct mg_str msg = {(char *) buf, len};
-#if MG_ENABLE_HEXDUMP
-  char hex[512];
-  mg_hexdump(buf, len, hex, sizeof(hex));
-  LOG(LL_DEBUG, ("sending to stream 0x%x:\n%s", (unsigned int) stream_id, hex));
-#endif
-
-  mg_tun_send_frame(client->disp, stream_id, MG_TUN_DATA_FRAME, 0, msg);
-}
-
-void mg_tun_if_udp_send(struct mg_connection *nc, const void *buf, size_t len) {
-  (void) nc;
-  (void) buf;
-  (void) len;
-}
-
-void mg_tun_if_recved(struct mg_connection *nc, size_t len) {
-  (void) nc;
-  (void) len;
-}
-
-int mg_tun_if_create_conn(struct mg_connection *nc) {
-  (void) nc;
-  return 1;
-}
-
-void mg_tun_if_destroy_conn(struct mg_connection *nc) {
-  struct mg_tun_client *client = (struct mg_tun_client *) nc->iface->data;
-
-  if (nc->flags & MG_F_LISTENING) {
-    mg_tun_destroy_client(client);
-  } else if (client->disp) {
-    uint32_t stream_id = (uint32_t)(uintptr_t) nc->mgr_data;
-    struct mg_str msg = {NULL, 0};
-
-    LOG(LL_DEBUG, ("closing 0x%x:", (unsigned int) stream_id));
-    mg_tun_send_frame(client->disp, stream_id, MG_TUN_DATA_FRAME,
-                      MG_TUN_F_END_STREAM, msg);
-  }
-}
-
-/* Associate a socket to a connection. */
-void mg_tun_if_sock_set(struct mg_connection *nc, sock_t sock) {
-  (void) nc;
-  (void) sock;
-}
-
-void mg_tun_if_init(struct mg_iface *iface) {
-  (void) iface;
-}
-
-void mg_tun_if_free(struct mg_iface *iface) {
-  (void) iface;
-}
-
-void mg_tun_if_add_conn(struct mg_connection *nc) {
-  nc->sock = INVALID_SOCKET;
-}
-
-void mg_tun_if_remove_conn(struct mg_connection *nc) {
-  (void) nc;
-}
-
-time_t mg_tun_if_poll(struct mg_iface *iface, int timeout_ms) {
-  (void) iface;
-  (void) timeout_ms;
-  return (time_t) cs_time();
-}
-
-void mg_tun_if_get_conn_addr(struct mg_connection *nc, int remote,
-                             union socket_address *sa) {
-  (void) nc;
-  (void) remote;
-  (void) sa;
-}
-
-struct mg_connection *mg_tun_if_find_conn(struct mg_tun_client *client,
-                                          uint32_t stream_id) {
-  struct mg_connection *nc = NULL;
-
-  for (nc = client->mgr->active_connections; nc != NULL; nc = nc->next) {
-    if (nc->iface != client->iface || (nc->flags & MG_F_LISTENING)) {
-      continue;
-    }
-    if (stream_id == (uint32_t)(uintptr_t) nc->mgr_data) {
-      return nc;
-    }
-  }
-
-  if (stream_id > client->last_stream_id) {
-    /* create a new connection */
-    LOG(LL_DEBUG, ("new stream 0x%x, accepting", (unsigned int) stream_id));
-    nc = mg_if_accept_new_conn(client->listener);
-    nc->mgr_data = (void *) (uintptr_t) stream_id;
-    client->last_stream_id = stream_id;
-  } else {
-    LOG(LL_DEBUG,
-        ("Ignoring stream 0x%x (last_stream_id 0x%x)", (unsigned int) stream_id,
-         (unsigned int) client->last_stream_id));
-  }
-
-  return nc;
-}
-
-/* clang-format off */
-#define MG_TUN_IFACE_VTABLE                                             \
-  {                                                                     \
-    mg_tun_if_init,                                                     \
-    mg_tun_if_free,                                                     \
-    mg_tun_if_add_conn,                                                 \
-    mg_tun_if_remove_conn,                                              \
-    mg_tun_if_poll,                                                     \
-    mg_tun_if_listen_tcp,                                               \
-    mg_tun_if_listen_udp,                                               \
-    mg_tun_if_connect_tcp,                                              \
-    mg_tun_if_connect_udp,                                              \
-    mg_tun_if_tcp_send,                                                 \
-    mg_tun_if_udp_send,                                                 \
-    mg_tun_if_recved,                                                   \
-    mg_tun_if_create_conn,                                              \
-    mg_tun_if_destroy_conn,                                             \
-    mg_tun_if_sock_set,                                                 \
-    mg_tun_if_get_conn_addr,                                            \
-  }
-/* clang-format on */
-
-const struct mg_iface_vtable mg_tun_iface_vtable = MG_TUN_IFACE_VTABLE;
-
-#endif /* MG_ENABLE_TUN */
diff --git a/src/net_if_tun.h b/src/net_if_tun.h
deleted file mode 100644
index f9830cc0569939efa01d92d10ec4046cd56c8eea..0000000000000000000000000000000000000000
--- a/src/net_if_tun.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (c) 2014-2016 Cesanta Software Limited
- * All rights reserved
- */
-
-#ifndef CS_MONGOOSE_SRC_NET_IF_TUN_H_
-#define CS_MONGOOSE_SRC_NET_IF_TUN_H_
-
-#if MG_ENABLE_TUN
-
-#include "mongoose/src/net_if.h"
-
-struct mg_tun_client;
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-extern const struct mg_iface_vtable mg_tun_iface_vtable;
-
-struct mg_connection *mg_tun_if_find_conn(struct mg_tun_client *client,
-                                          uint32_t stream_id);
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-
-#endif /* MG_ENABLE_TUN */
-
-#endif /* CS_MONGOOSE_SRC_NET_IF_TUN_H_ */
diff --git a/src/tun.c b/src/tun.c
deleted file mode 100644
index da52ce765f45223d9c43cb3137b45dc60f2ad441..0000000000000000000000000000000000000000
--- a/src/tun.c
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (c) 2014 Cesanta Software Limited
- * All rights reserved
- */
-
-#if MG_ENABLE_TUN
-
-#include "common/cs_dbg.h"
-#include "mongoose/src/http.h"
-#include "mongoose/src/internal.h"
-#include "mongoose/src/net.h"
-#include "mongoose/src/net_if_tun.h"
-#include "mongoose/src/tun.h"
-#include "mongoose/src/util.h"
-
-static void mg_tun_reconnect(struct mg_tun_client *client, int timeout);
-
-static void mg_tun_init_client(struct mg_tun_client *client, struct mg_mgr *mgr,
-                               struct mg_iface *iface, const char *dispatcher,
-                               struct mg_tun_ssl_opts ssl) {
-  client->mgr = mgr;
-  client->iface = iface;
-  client->disp_url = dispatcher;
-  client->last_stream_id = 0;
-  client->ssl = ssl;
-
-  client->disp = NULL;      /* will be set by mg_tun_reconnect */
-  client->listener = NULL;  /* will be set by mg_do_bind */
-  client->reconnect = NULL; /* will be set by mg_tun_reconnect */
-}
-
-void mg_tun_log_frame(struct mg_tun_frame *frame) {
-  LOG(LL_DEBUG, ("Got TUN frame: type=0x%x, flags=0x%x stream_id=0x%x, "
-                 "len=%d",
-                 frame->type, frame->flags, (unsigned int) frame->stream_id,
-                 (int) frame->body.len));
-#if MG_ENABLE_HEXDUMP
-  {
-    char hex[512];
-    mg_hexdump(frame->body.p, frame->body.len, hex, sizeof(hex) - 1);
-    hex[sizeof(hex) - 1] = '\0';
-    LOG(LL_DEBUG, ("body:\n%s", hex));
-  }
-#else
-  LOG(LL_DEBUG, ("body: '%.*s'", (int) frame->body.len, frame->body.p));
-#endif
-}
-
-static void mg_tun_close_all(struct mg_tun_client *client) {
-  struct mg_connection *nc;
-  for (nc = client->mgr->active_connections; nc != NULL; nc = nc->next) {
-    if (nc->iface == client->iface && !(nc->flags & MG_F_LISTENING)) {
-      LOG(LL_DEBUG, ("Closing tunneled connection %p", nc));
-      nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-      /* mg_close_conn(nc); */
-    }
-  }
-}
-
-static void mg_tun_client_handler(struct mg_connection *nc, int ev,
-                                  void *ev_data MG_UD_ARG(void *user_data)) {
-#if !MG_ENABLE_CALLBACK_USERDATA
-  void *user_data = nc->user_data;
-#else
-  (void) nc;
-#endif
-  struct mg_tun_client *client = (struct mg_tun_client *) user_data;
-
-  switch (ev) {
-    case MG_EV_CONNECT: {
-      int err = *(int *) ev_data;
-
-      if (err) {
-        LOG(LL_ERROR, ("Cannot connect to the tunnel dispatcher: %d", err));
-      } else {
-        LOG(LL_INFO, ("Connected to the tunnel dispatcher"));
-      }
-      break;
-    }
-    case MG_EV_HTTP_REPLY: {
-      struct http_message *hm = (struct http_message *) ev_data;
-
-      if (hm->resp_code != 200) {
-        LOG(LL_ERROR,
-            ("Tunnel dispatcher reply non-OK status code %d", hm->resp_code));
-      }
-      break;
-    }
-    case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
-      LOG(LL_INFO, ("Tunnel dispatcher handshake done"));
-      break;
-    }
-    case MG_EV_WEBSOCKET_FRAME: {
-      struct websocket_message *wm = (struct websocket_message *) ev_data;
-      struct mg_connection *tc;
-      struct mg_tun_frame frame;
-
-      if (mg_tun_parse_frame(wm->data, wm->size, &frame) == -1) {
-        LOG(LL_ERROR, ("Got invalid tun frame dropping"));
-        break;
-      }
-
-      mg_tun_log_frame(&frame);
-
-      tc = mg_tun_if_find_conn(client, frame.stream_id);
-      if (tc == NULL) {
-        if (frame.body.len > 0) {
-          LOG(LL_DEBUG, ("Got frame after receiving end has been closed"));
-        }
-        break;
-      }
-      if (frame.body.len > 0) {
-        mg_if_recv_tcp_cb(tc, (void *) frame.body.p, frame.body.len,
-                          0 /* own */);
-      }
-      if (frame.flags & MG_TUN_F_END_STREAM) {
-        LOG(LL_DEBUG, ("Closing tunneled connection because got end of stream "
-                       "from other end"));
-        tc->flags |= MG_F_CLOSE_IMMEDIATELY;
-        mg_close_conn(tc);
-      }
-      break;
-    }
-    case MG_EV_CLOSE: {
-      LOG(LL_DEBUG, ("Closing all tunneled connections"));
-      /*
-       * The client might have been already freed when the listening socket is
-       * closed.
-       */
-      if (client != NULL) {
-        mg_tun_close_all(client);
-        client->disp = NULL;
-        LOG(LL_INFO, ("Dispatcher connection is no more, reconnecting"));
-        /* TODO(mkm): implement exp back off */
-        mg_tun_reconnect(client, MG_TUN_RECONNECT_INTERVAL);
-      }
-      break;
-    }
-    default:
-      break;
-  }
-}
-
-static void mg_tun_do_reconnect(struct mg_tun_client *client) {
-  struct mg_connection *dc;
-  struct mg_connect_opts opts;
-  memset(&opts, 0, sizeof(opts));
-#if MG_ENABLE_SSL
-  opts.ssl_cert = client->ssl.ssl_cert;
-  opts.ssl_key = client->ssl.ssl_key;
-  opts.ssl_ca_cert = client->ssl.ssl_ca_cert;
-#endif
-  /* HTTP/Websocket listener */
-  if ((dc = mg_connect_ws_opt(client->mgr, MG_CB(mg_tun_client_handler, client),
-                              opts, client->disp_url, MG_TUN_PROTO_NAME,
-                              NULL)) == NULL) {
-    LOG(LL_ERROR,
-        ("Cannot connect to WS server on addr [%s]\n", client->disp_url));
-    return;
-  }
-
-  client->disp = dc;
-#if !MG_ENABLE_CALLBACK_USERDATA
-  dc->user_data = client;
-#endif
-}
-
-void mg_tun_reconnect_ev_handler(struct mg_connection *nc, int ev,
-                                 void *ev_data MG_UD_ARG(void *user_data)) {
-#if !MG_ENABLE_CALLBACK_USERDATA
-  void *user_data = nc->user_data;
-#else
-  (void) nc;
-#endif
-  struct mg_tun_client *client = (struct mg_tun_client *) user_data;
-  (void) ev_data;
-
-  switch (ev) {
-    case MG_EV_TIMER:
-      if (!(client->listener->flags & MG_F_TUN_DO_NOT_RECONNECT)) {
-        mg_tun_do_reconnect(client);
-      } else {
-        /* Reconnecting is suppressed, we'll check again at the next poll */
-        mg_tun_reconnect(client, 0);
-      }
-      break;
-  }
-}
-
-static void mg_tun_reconnect(struct mg_tun_client *client, int timeout) {
-  if (client->reconnect == NULL) {
-    client->reconnect = mg_add_sock(client->mgr, INVALID_SOCKET,
-                                    MG_CB(mg_tun_reconnect_ev_handler, client));
-#if !MG_ENABLE_CALLBACK_USERDATA
-    client->reconnect->user_data = client;
-#endif
-  }
-  client->reconnect->ev_timer_time = mg_time() + timeout;
-}
-
-static struct mg_tun_client *mg_tun_create_client(struct mg_mgr *mgr,
-                                                  const char *dispatcher,
-                                                  struct mg_tun_ssl_opts ssl) {
-  struct mg_tun_client *client = NULL;
-  struct mg_iface *iface = mg_find_iface(mgr, &mg_tun_iface_vtable, NULL);
-  if (iface == NULL) {
-    LOG(LL_ERROR, ("The tun feature requires the manager to have a tun "
-                   "interface enabled"));
-    return NULL;
-  }
-
-  client = (struct mg_tun_client *) MG_MALLOC(sizeof(*client));
-  mg_tun_init_client(client, mgr, iface, dispatcher, ssl);
-  iface->data = client;
-
-  /*
-   * We need to give application a chance to set MG_F_TUN_DO_NOT_RECONNECT on a
-   * listening connection right after mg_tun_bind_opt() returned it, so we
-   * should use mg_tun_reconnect() here, instead of mg_tun_do_reconnect()
-   */
-  mg_tun_reconnect(client, 0);
-  return client;
-}
-
-void mg_tun_destroy_client(struct mg_tun_client *client) {
-  /*
-   *  NOTE:
-   * `client` is NULL in case of OOM
-   * `client->disp` is NULL if connection failed
-   * `client->iface is NULL is `mg_find_iface` failed
-   */
-
-  if (client != NULL && client->disp != NULL) {
-    /* the dispatcher connection handler will in turn close all tunnels */
-    client->disp->flags |= MG_F_CLOSE_IMMEDIATELY;
-    /* this is used as a signal to other tun handlers that the party is over */
-    client->disp->user_data = NULL;
-  }
-
-  if (client != NULL && client->reconnect != NULL) {
-    client->reconnect->flags |= MG_F_CLOSE_IMMEDIATELY;
-  }
-
-  if (client != NULL && client->iface != NULL) {
-    client->iface->data = NULL;
-  }
-
-  MG_FREE(client);
-}
-
-static struct mg_connection *mg_tun_do_bind(struct mg_tun_client *client,
-                                            MG_CB(mg_event_handler_t handler,
-                                                  void *user_data),
-                                            struct mg_bind_opts opts) {
-  struct mg_connection *lc;
-  opts.iface = client->iface;
-  lc = mg_bind_opt(client->mgr, ":1234" /* dummy port */,
-                   MG_CB(handler, user_data), opts);
-  client->listener = lc;
-  return lc;
-}
-
-struct mg_connection *mg_tun_bind_opt(struct mg_mgr *mgr,
-                                      const char *dispatcher,
-                                      MG_CB(mg_event_handler_t handler,
-                                            void *user_data),
-                                      struct mg_bind_opts opts) {
-#if MG_ENABLE_SSL
-  struct mg_tun_ssl_opts ssl = {opts.ssl_cert, opts.ssl_key, opts.ssl_ca_cert};
-#else
-  struct mg_tun_ssl_opts ssl = {0};
-#endif
-  struct mg_tun_client *client = mg_tun_create_client(mgr, dispatcher, ssl);
-  if (client == NULL) {
-    return NULL;
-  }
-#if MG_ENABLE_SSL
-  /* these options don't make sense in the local mouth of the tunnel */
-  opts.ssl_cert = NULL;
-  opts.ssl_key = NULL;
-  opts.ssl_ca_cert = NULL;
-#endif
-  return mg_tun_do_bind(client, MG_CB(handler, user_data), opts);
-}
-
-int mg_tun_parse_frame(void *data, size_t len, struct mg_tun_frame *frame) {
-  const size_t header_size = sizeof(uint32_t) + sizeof(uint8_t) * 2;
-  if (len < header_size) {
-    return -1;
-  }
-
-  frame->type = *(uint8_t *) (data);
-  frame->flags = *(uint8_t *) ((char *) data + 1);
-  memcpy(&frame->stream_id, (char *) data + 2, sizeof(uint32_t));
-  frame->stream_id = ntohl(frame->stream_id);
-  frame->body.p = (char *) data + header_size;
-  frame->body.len = len - header_size;
-  return 0;
-}
-
-void mg_tun_send_frame(struct mg_connection *ws, uint32_t stream_id,
-                       uint8_t type, uint8_t flags, struct mg_str msg) {
-  stream_id = htonl(stream_id);
-  {
-    struct mg_str parts[] = {
-        {(char *) &type, sizeof(type)},
-        {(char *) &flags, sizeof(flags)},
-        {(char *) &stream_id, sizeof(stream_id)},
-        {msg.p, msg.len} /* vc6 doesn't like just `msg` here */};
-    mg_send_websocket_framev(ws, WEBSOCKET_OP_BINARY, parts,
-                             sizeof(parts) / sizeof(parts[0]));
-  }
-}
-
-#endif /* MG_ENABLE_TUN */
diff --git a/src/tun.h b/src/tun.h
deleted file mode 100644
index d305b0b3a6169f8112f4dccea0aeca0a845f7eb4..0000000000000000000000000000000000000000
--- a/src/tun.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (c) 2014-2016 Cesanta Software Limited
- * All rights reserved
- */
-
-#ifndef CS_MONGOOSE_SRC_TUN_H_
-#define CS_MONGOOSE_SRC_TUN_H_
-
-#if MG_ENABLE_TUN
-
-#include "mongoose/src/net.h"
-#include "common/mg_str.h"
-
-#ifndef MG_TUN_RECONNECT_INTERVAL
-#define MG_TUN_RECONNECT_INTERVAL 1
-#endif
-
-#define MG_TUN_PROTO_NAME "mg_tun"
-
-#define MG_TUN_DATA_FRAME 0x0
-#define MG_TUN_F_END_STREAM 0x1
-
-/*
- * MG TUN frame format is loosely based on HTTP/2.
- * However since the communication happens via WebSocket
- * there is no need to encode the frame length, since that's
- * solved by WebSocket framing.
- *
- * TODO(mkm): Detailed description of the protocol.
- */
-struct mg_tun_frame {
-  uint8_t type;
-  uint8_t flags;
-  uint32_t stream_id; /* opaque stream identifier */
-  struct mg_str body;
-};
-
-struct mg_tun_ssl_opts {
-#if MG_ENABLE_SSL
-  const char *ssl_cert;
-  const char *ssl_key;
-  const char *ssl_ca_cert;
-#else
-  int dummy; /* some compilers don't like empty structs */
-#endif
-};
-
-struct mg_tun_client {
-  struct mg_mgr *mgr;
-  struct mg_iface *iface;
-  const char *disp_url;
-  struct mg_tun_ssl_opts ssl;
-
-  uint32_t last_stream_id; /* stream id of most recently accepted connection */
-
-  struct mg_connection *disp;
-  struct mg_connection *listener;
-  struct mg_connection *reconnect;
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-struct mg_connection *mg_tun_bind_opt(struct mg_mgr *mgr,
-                                      const char *dispatcher,
-                                      MG_CB(mg_event_handler_t handler,
-                                            void *user_data),
-                                      struct mg_bind_opts opts);
-
-int mg_tun_parse_frame(void *data, size_t len, struct mg_tun_frame *frame);
-
-void mg_tun_send_frame(struct mg_connection *ws, uint32_t stream_id,
-                       uint8_t type, uint8_t flags, struct mg_str msg);
-
-void mg_tun_destroy_client(struct mg_tun_client *client);
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-
-#endif /* MG_ENABLE_TUN */
-
-#endif /* CS_MONGOOSE_SRC_TUN_H_ */
diff --git a/test/Makefile b/test/Makefile
index 96f463e290210b9d926e993ba46bb351d70f1bb4..ecdc5ec989d3dbc3bfdcdb39b9fd2c7fe68296d2 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -3,7 +3,7 @@
 PROG = unit_test
 REPO_ROOT ?= ../../
 SRC_DIR = ../src
-TEST_SOURCES = unit_test.c $(REPO_ROOT)/common/test_util.c $(REPO_ROOT)/tuna/dispatcher.c
+TEST_SOURCES = unit_test.c $(REPO_ROOT)/common/test_util.c
 AMALGAMATED_SOURCES = ../mongoose.c
 KRYPTON_PATH = $(REPO_ROOT)/krypton
 # or Krypton, or mbedTLS
diff --git a/test/unit_test.c b/test/unit_test.c
index 084afa18d6f2e044843eb285939ed49018b4835b..9fdd7b77682c9fbcc56f2b961abc2771a22e12ea 100644
--- a/test/unit_test.c
+++ b/test/unit_test.c
@@ -20,7 +20,6 @@
 #include "unit_test.h"
 #include "common/test_util.h"
 #include "common/cs_md5.h"
-#include "tuna/dispatcher.h"
 
 #if defined(_MSC_VER) && _MSC_VER >= 1900
 #include <crtdbg.h>
@@ -3816,44 +3815,6 @@ void tunnel_client_test_handler(struct mg_connection *nc, int ev,
   }
 }
 
-/*
- * NOTE(mkm): this test requires compiling the unit_test.c file with
- * //tuna/dispatcher.c .
- * Windows test runner doesn't use a makefile that's checked in
- * and I can't shave this yak now.
- * This doesn't mean the tunnel is not supposed to work on windows; there
- * is no fundamental reason it shouldn't, but obviously we should shave
- * the yak and make it testable there as well.
- */
-#ifndef _WIN32
-static const char *test_tunnel(void) {
-  struct mg_connection *client, *server;
-  struct mg_mgr mgr;
-  int sentinel = 0;
-  mg_mgr_init(&mgr, NULL);
-
-  mg_tund_bind(&mgr, "localhost:4321");
-
-  server = mg_bind(&mgr, "ws://localhost:4321", tunnel_server_test_handler);
-  mg_set_protocol_http_websocket(server);
-  /*
-   * Connection happens only at the next poll (because we need to give the app
-   * a chance to set MG_F_TUN_DO_NOT_RECONNECT), so let's poll once
-   */
-  mg_mgr_poll(&mgr, 1);
-  mg_mgr_poll(&mgr, 1);
-  client = mg_connect_http(&mgr, tunnel_client_test_handler,
-                           "http://localhost:4321/foo", NULL, NULL);
-  client->user_data = (void *) &sentinel;
-
-  poll_until(&mgr, 1, c_int_eq, &sentinel, (void *) 1);
-  ASSERT_EQ(sentinel, 1);
-
-  mg_mgr_free(&mgr);
-  return NULL;
-}
-#endif
-
 static const char *test_http_chunk(void) {
   struct mg_connection nc;
   init_test_connection(&nc);
@@ -5544,9 +5505,6 @@ static const char *run_tests(const char *filter, double *total_elapsed) {
   RUN_TEST(test_hexdump_file);
   RUN_TEST(test_basic_auth_helpers);
   RUN_TEST(test_http_auth);
-#ifndef _WIN32
-  RUN_TEST(test_tunnel);
-#endif
 #if MG_ENABLE_SSL
   RUN_TEST(test_ssl);
 #ifdef OPENSSL_VERSION_NUMBER