From 3e33e577079b4c44af15a53d5bbf2acfd723ac6a Mon Sep 17 00:00:00 2001
From: Deomid Ryabkov <rojer@cesanta.com>
Date: Tue, 27 Mar 2018 00:25:18 +0100
Subject: [PATCH] Mongoose net_if and ssl_if refactoring

A major cleanup, disentangling net_if and ssl_if.
Pulled a lot of common logic into the core and reduced size of net_if implementations.

CL: Mongoose net_if and ssl_if refactoring

PUBLISHED_FROM=29bd4dcb264a1fd96b3dd164e2d880e1c2c0921e
---
 mongoose.c                                  | 1703 ++++++++-----------
 mongoose.h                                  |   25 +-
 src/common/platforms/lwip/mg_lwip_ev_mgr.c  |   94 +-
 src/common/platforms/lwip/mg_lwip_net_if.c  |  242 +--
 src/common/platforms/lwip/mg_lwip_net_if.h  |    4 +-
 src/common/platforms/lwip/mg_lwip_ssl_if.c  |  197 ---
 src/common/platforms/platform_cc3220.h      |    2 +
 src/common/platforms/simplelink/sl_net_if.c |  170 +-
 src/common/platforms/simplelink/sl_ssl_if.c |  110 +-
 src/mg_modules.mk                           |    1 -
 src/mg_net.c                                |  370 ++--
 src/mg_net_if.h                             |   25 +-
 src/mg_net_if_socket.c                      |  259 +--
 src/mg_net_if_socks.c                       |  102 +-
 src/mg_resolv.c                             |   10 +-
 src/mg_ssl_if_mbedtls.c                     |   88 +-
 test/unit_test.c                            |    9 +-
 17 files changed, 1381 insertions(+), 2030 deletions(-)
 delete mode 100644 src/common/platforms/lwip/mg_lwip_ssl_if.c

diff --git a/mongoose.c b/mongoose.c
index 1630eb367..ce2ed4e66 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -2185,6 +2185,13 @@ size_t mg_match_prefix(const char *pattern, int pattern_len, const char *str) {
 
 #define MG_MAX_HOST_LEN 200
 
+#ifndef MG_TCP_IO_SIZE
+#define MG_TCP_IO_SIZE 1460
+#endif
+#ifndef MG_UDP_IO_SIZE
+#define MG_UDP_IO_SIZE 1460
+#endif
+
 #define MG_COPY_COMMON_CONNECTION_OPTIONS(dst, src) \
   memcpy(dst, src, sizeof(*dst));
 
@@ -2225,8 +2232,6 @@ MG_INTERNAL void mg_remove_conn(struct mg_connection *conn) {
 MG_INTERNAL void mg_call(struct mg_connection *nc,
                          mg_event_handler_t ev_handler, void *user_data, int ev,
                          void *ev_data) {
-  static int nesting_level = 0;
-  nesting_level++;
   if (ev_handler == NULL) {
     /*
      * If protocol handler is specified, call it. Otherwise, call user-specified
@@ -2235,7 +2240,7 @@ MG_INTERNAL void mg_call(struct mg_connection *nc,
     ev_handler = nc->proto_handler ? nc->proto_handler : nc->handler;
   }
   if (ev != MG_EV_POLL) {
-    DBG(("%p %s ev=%d ev_data=%p flags=%lu rmbl=%d smbl=%d", nc,
+    DBG(("%p %s ev=%d ev_data=%p flags=0x%lx rmbl=%d smbl=%d", nc,
          ev_handler == nc->handler ? "user" : "proto", ev, ev_data, nc->flags,
          (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
   }
@@ -2248,33 +2253,24 @@ MG_INTERNAL void mg_call(struct mg_connection *nc,
 #endif
   if (ev_handler != NULL) {
     unsigned long flags_before = nc->flags;
-    size_t recv_mbuf_before = nc->recv_mbuf.len, recved;
     ev_handler(nc, ev, ev_data MG_UD_ARG(user_data));
-    recved = (recv_mbuf_before - nc->recv_mbuf.len);
     /* Prevent user handler from fiddling with system flags. */
     if (ev_handler == nc->handler && nc->flags != flags_before) {
       nc->flags = (flags_before & ~_MG_CALLBACK_MODIFIABLE_FLAGS_MASK) |
                   (nc->flags & _MG_CALLBACK_MODIFIABLE_FLAGS_MASK);
     }
-    /* It's important to not double-count recved bytes, and since mg_call can be
-     * called recursively (e.g. proto_handler invokes user handler), we keep
-     * track of recursion and only report received bytes at the top level. */
-    if (nesting_level == 1 && recved > 0 && !(nc->flags & MG_F_UDP)) {
-      nc->iface->vtable->recved(nc, recved);
-    }
   }
   if (ev != MG_EV_POLL) {
-    DBG(("%p after %s flags=%lu rmbl=%d smbl=%d", nc,
+    DBG(("%p after %s flags=0x%lx rmbl=%d smbl=%d", nc,
          ev_handler == nc->handler ? "user" : "proto", nc->flags,
          (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
   }
-  nesting_level--;
 #if !MG_ENABLE_CALLBACK_USERDATA
   (void) user_data;
 #endif
 }
 
-void mg_if_timer(struct mg_connection *c, double now) {
+MG_INTERNAL void mg_timer(struct mg_connection *c, double now) {
   if (c->ev_timer_time > 0 && now >= c->ev_timer_time) {
     double old_value = c->ev_timer_time;
     c->ev_timer_time = 0;
@@ -2282,13 +2278,45 @@ void mg_if_timer(struct mg_connection *c, double now) {
   }
 }
 
-void mg_if_poll(struct mg_connection *nc, time_t now) {
-  if (!(nc->flags & MG_F_SSL) || (nc->flags & MG_F_SSL_HANDSHAKE_DONE)) {
-    mg_call(nc, NULL, nc->user_data, MG_EV_POLL, &now);
+MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) {
+  size_t avail;
+  if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0;
+  avail = conn->recv_mbuf_limit - conn->recv_mbuf.len;
+  return avail > max ? max : avail;
+}
+
+static int mg_do_recv(struct mg_connection *nc);
+
+int mg_if_poll(struct mg_connection *nc, double now) {
+  if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
+      (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) {
+    mg_close_conn(nc);
+    return 0;
+  }
+#if MG_ENABLE_SSL
+  if ((nc->flags & (MG_F_SSL | MG_F_LISTENING | MG_F_CONNECTING)) == MG_F_SSL) {
+    /* SSL library may have data to be delivered to the app in its buffers,
+     * drain them. */
+    int recved = 0;
+    do {
+      if (nc->flags & (MG_F_WANT_READ | MG_F_WANT_WRITE)) break;
+      if (recv_avail_size(nc, MG_TCP_IO_SIZE) <= 0) break;
+      recved = mg_do_recv(nc);
+    } while (recved > 0);
+  }
+#endif /* MG_ENABLE_SSL */
+  mg_timer(nc, now);
+  {
+    time_t now_t = (time_t) now;
+    mg_call(nc, NULL, nc->user_data, MG_EV_POLL, &now_t);
   }
+  return 1;
 }
 
 void mg_destroy_conn(struct mg_connection *conn, int destroy_if) {
+  if (conn->sock != INVALID_SOCKET) { /* Don't print timer-only conns */
+    LOG(LL_DEBUG, ("%p 0x%lx %d", conn, conn->flags, destroy_if));
+  }
   if (destroy_if) conn->iface->vtable->destroy_conn(conn);
   if (conn->proto_data != NULL && conn->proto_data_destructor != NULL) {
     conn->proto_data_destructor(conn->proto_data);
@@ -2304,7 +2332,6 @@ void mg_destroy_conn(struct mg_connection *conn, int destroy_if) {
 }
 
 void mg_close_conn(struct mg_connection *conn) {
-  DBG(("%p %lu %d", conn, conn->flags, conn->sock));
 #if MG_ENABLE_SSL
   if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) {
     mg_ssl_if_conn_close_notify(conn);
@@ -2341,15 +2368,6 @@ void mg_mgr_init_opt(struct mg_mgr *m, void *user_data,
   signal(SIGPIPE, SIG_IGN);
 #endif
 
-#if MG_ENABLE_SSL
-  {
-    static int init_done;
-    if (!init_done) {
-      mg_ssl_if_init();
-      init_done++;
-    }
-  }
-#endif
   {
     int i;
     if (opts.num_ifaces == 0) {
@@ -2372,6 +2390,15 @@ void mg_mgr_init_opt(struct mg_mgr *m, void *user_data,
   }
   DBG(("=================================="));
   DBG(("init mgr=%p", m));
+#if MG_ENABLE_SSL
+  {
+    static int init_done;
+    if (!init_done) {
+      mg_ssl_if_init();
+      init_done++;
+    }
+  }
+#endif
 }
 
 void mg_mgr_free(struct mg_mgr *m) {
@@ -2614,6 +2641,37 @@ MG_INTERNAL int mg_parse_address(const char *str, union socket_address *sa,
   return port < 0xffffUL && (ch == '\0' || ch == ',' || isspace(ch)) ? len : -1;
 }
 
+#if MG_ENABLE_SSL
+MG_INTERNAL void mg_ssl_handshake(struct mg_connection *nc) {
+  int err = 0;
+  int server_side = (nc->listener != NULL);
+  enum mg_ssl_if_result res;
+  if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) return;
+  res = mg_ssl_if_handshake(nc);
+  LOG(LL_DEBUG, ("%p %d res %d", nc, server_side, res));
+
+  if (res == MG_SSL_OK) {
+    nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
+    nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE);
+    if (server_side) {
+      mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
+    } else {
+      mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+    }
+  } else if (res == MG_SSL_WANT_READ) {
+    nc->flags |= MG_F_WANT_READ;
+  } else if (res == MG_SSL_WANT_WRITE) {
+    nc->flags |= MG_F_WANT_WRITE;
+  } else {
+    if (!server_side) {
+      err = res;
+      mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+    }
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+  }
+}
+#endif /* MG_ENABLE_SSL */
+
 struct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc) {
   struct mg_add_sock_opts opts;
   struct mg_connection *nc;
@@ -2627,95 +2685,125 @@ struct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc) {
   nc->iface = lc->iface;
   if (lc->flags & MG_F_SSL) nc->flags |= MG_F_SSL;
   mg_add_conn(nc->mgr, nc);
-  DBG(("%p %p %d %d", lc, nc, nc->sock, (int) nc->flags));
+  LOG(LL_DEBUG, ("%p %p %d %d", lc, nc, nc->sock, (int) nc->flags));
   return nc;
 }
 
 void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa,
                          size_t sa_len) {
-  (void) sa_len;
+  LOG(LL_DEBUG, ("%p %s://%s:%hu", nc, (nc->flags & MG_F_UDP ? "udp" : "tcp"),
+                 inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));
   nc->sa = *sa;
-  mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
+#if MG_ENABLE_SSL
+  if (nc->listener->flags & MG_F_SSL) {
+    nc->flags |= MG_F_SSL;
+    if (mg_ssl_if_conn_accept(nc, nc->listener) == MG_SSL_OK) {
+      mg_ssl_handshake(nc);
+    } else {
+      mg_close_conn(nc);
+    }
+  } else
+#endif
+  {
+    mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
+  }
+  (void) sa_len;
 }
 
 void mg_send(struct mg_connection *nc, const void *buf, int len) {
   nc->last_io_time = (time_t) mg_time();
-  if (nc->flags & MG_F_UDP) {
-    nc->iface->vtable->udp_send(nc, buf, len);
-  } else {
-    nc->iface->vtable->tcp_send(nc, buf, len);
-  }
+  mbuf_append(&nc->send_mbuf, buf, len);
 }
 
-void mg_if_sent_cb(struct mg_connection *nc, int num_sent) {
-  DBG(("%p %d", nc, num_sent));
-#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
-  if (nc->mgr && nc->mgr->hexdump_file != NULL) {
-    char *buf = nc->send_mbuf.buf;
-    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, num_sent, MG_EV_SEND);
+static int mg_recv_tcp(struct mg_connection *nc, char *buf, size_t len);
+static int mg_recv_udp(struct mg_connection *nc, char *buf, size_t len);
+
+static int mg_do_recv(struct mg_connection *nc) {
+  int res = 0;
+  char *buf = NULL;
+  size_t len = (nc->flags & MG_F_UDP ? MG_UDP_IO_SIZE : MG_TCP_IO_SIZE);
+  if ((nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_CONNECTING)) ||
+      ((nc->flags & MG_F_LISTENING) && !(nc->flags & MG_F_UDP))) {
+    return -1;
   }
-#endif
-  if (num_sent < 0) {
-    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+  len = recv_avail_size(nc, len);
+  if (len == 0) return -2;
+  if (nc->recv_mbuf.size < nc->recv_mbuf.len + len) {
+    mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.len + len);
+  }
+  buf = nc->recv_mbuf.buf + nc->recv_mbuf.len;
+  len = nc->recv_mbuf.size - nc->recv_mbuf.len;
+  if (nc->flags & MG_F_UDP) {
+    res = mg_recv_udp(nc, buf, len);
   } else {
-    mbuf_remove(&nc->send_mbuf, num_sent);
-    mbuf_trim(&nc->send_mbuf);
+    res = mg_recv_tcp(nc, buf, len);
   }
-  mg_call(nc, NULL, nc->user_data, MG_EV_SEND, &num_sent);
+  return res;
 }
 
-MG_INTERNAL void mg_recv_common(struct mg_connection *nc, void *buf, int len,
-                                int own) {
-  DBG(("%p %d %u", nc, len, (unsigned int) nc->recv_mbuf.len));
-
-#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
-  if (nc->mgr && nc->mgr->hexdump_file != NULL) {
-    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, len, MG_EV_RECV);
-  }
-#endif
+void mg_if_can_recv_cb(struct mg_connection *nc) {
+  mg_do_recv(nc);
+}
 
-  if (nc->flags & MG_F_CLOSE_IMMEDIATELY) {
-    DBG(("%p discarded %d bytes", nc, len));
-    /*
-     * This connection will not survive next poll. Do not deliver events,
-     * send data to /dev/null without acking.
-     */
-    if (own) {
-      MG_FREE(buf);
+static int mg_recv_tcp(struct mg_connection *nc, char *buf, size_t len) {
+  int n = 0;
+#if MG_ENABLE_SSL
+  if (nc->flags & MG_F_SSL) {
+    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
+      n = mg_ssl_if_read(nc, buf, len);
+      DBG(("%p <- %d bytes (SSL)", nc, n));
+      if (n < 0) {
+        if (n == MG_SSL_WANT_READ) {
+          nc->flags |= MG_F_WANT_READ;
+          n = 0;
+        } else {
+          nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+        }
+      } else if (n > 0) {
+        nc->flags &= ~MG_F_WANT_READ;
+      }
+    } else {
+      mg_ssl_handshake(nc);
     }
-    return;
+  } else
+#endif
+  {
+    n = nc->iface->vtable->tcp_recv(nc, buf, len);
+    DBG(("%p <- %d bytes", nc, n));
   }
-  nc->last_io_time = (time_t) mg_time();
-  if (!own) {
-    mbuf_append(&nc->recv_mbuf, buf, len);
-  } else if (nc->recv_mbuf.len == 0) {
-    /* Adopt buf as recv_mbuf's backing store. */
-    mbuf_free(&nc->recv_mbuf);
-    nc->recv_mbuf.buf = (char *) buf;
-    nc->recv_mbuf.size = nc->recv_mbuf.len = len;
-  } else {
-    mbuf_append(&nc->recv_mbuf, buf, len);
-    MG_FREE(buf);
+  if (n > 0) {
+    nc->recv_mbuf.len += n;
+    nc->last_io_time = (time_t) mg_time();
+#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
+    if (nc->mgr && nc->mgr->hexdump_file != NULL) {
+      mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_RECV);
+    }
+#endif
+    mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n);
+  } else if (n < 0) {
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
   }
-  mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &len);
-}
-
-void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len, int own) {
-  mg_recv_common(nc, buf, len, own);
+  return n;
 }
 
-void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
-                       union socket_address *sa, size_t sa_len) {
-  assert(nc->flags & MG_F_UDP);
-  DBG(("%p %u", nc, (unsigned int) len));
+static int mg_recv_udp(struct mg_connection *nc, char *buf, size_t len) {
+  int n = 0;
+  struct mg_connection *lc = nc;
+  union socket_address sa;
+  size_t sa_len = sizeof(sa);
+  n = nc->iface->vtable->udp_recv(lc, buf, len, &sa, &sa_len);
+  if (n < 0) {
+    lc->flags |= MG_F_CLOSE_IMMEDIATELY;
+    goto out;
+  }
   if (nc->flags & MG_F_LISTENING) {
-    struct mg_connection *lc = nc;
     /*
      * Do we have an existing connection for this source?
      * This is very inefficient for long connection lists.
      */
+    lc = nc;
     for (nc = mg_next(lc->mgr, NULL); nc != NULL; nc = mg_next(lc->mgr, nc)) {
-      if (memcmp(&nc->sa.sa, &sa->sa, sa_len) == 0 && nc->listener == lc) {
+      if (memcmp(&nc->sa.sa, &sa.sa, sa_len) == 0 && nc->listener == lc) {
         break;
       }
     }
@@ -2727,7 +2815,7 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
       if (nc != NULL) {
         nc->sock = lc->sock;
         nc->listener = lc;
-        nc->sa = *sa;
+        nc->sa = sa;
         nc->proto_handler = lc->proto_handler;
         nc->user_data = lc->user_data;
         nc->recv_mbuf_limit = lc->recv_mbuf_limit;
@@ -2746,18 +2834,88 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
         nc->flags |= MG_F_SEND_AND_CLOSE;
         mg_add_conn(lc->mgr, nc);
         mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
-      } else {
-        DBG(("OOM"));
-        /* No return here, we still need to drop on the floor */
       }
     }
   }
   if (nc != NULL) {
-    mg_recv_common(nc, buf, len, 1);
-  } else {
-    /* Drop on the floor. */
-    MG_FREE(buf);
+    DBG(("%p <- %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr),
+         ntohs(nc->sa.sin.sin_port)));
+    if (nc == lc) {
+      nc->recv_mbuf.len += n;
+    } else {
+      mbuf_append(&nc->recv_mbuf, buf, n);
+    }
+    mbuf_trim(&lc->recv_mbuf);
+    lc->last_io_time = nc->last_io_time = (time_t) mg_time();
+#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
+    if (nc->mgr && nc->mgr->hexdump_file != NULL) {
+      mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_RECV);
+    }
+#endif
+    mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n);
+  }
+
+out:
+  mbuf_free(&lc->recv_mbuf);
+  return n;
+}
+
+void mg_if_can_send_cb(struct mg_connection *nc) {
+  int n = 0;
+  const char *buf = nc->send_mbuf.buf;
+  size_t len = nc->send_mbuf.len;
+
+  if (nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_CONNECTING)) {
+    return;
+  }
+  if (!(nc->flags & MG_F_UDP)) {
+    if (nc->flags & MG_F_LISTENING) return;
+    if (len > MG_TCP_IO_SIZE) len = MG_TCP_IO_SIZE;
+  }
+#if MG_ENABLE_SSL
+  if (nc->flags & MG_F_SSL) {
+    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
+      if (len > 0) {
+        n = mg_ssl_if_write(nc, buf, len);
+        DBG(("%p -> %d bytes (SSL)", nc, n));
+      }
+      if (n < 0) {
+        if (n == MG_SSL_WANT_WRITE) {
+          nc->flags |= MG_F_WANT_WRITE;
+          n = 0;
+        } else {
+          nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+        }
+      } else {
+        nc->flags &= ~MG_F_WANT_WRITE;
+      }
+    } else {
+      mg_ssl_handshake(nc);
+    }
+  } else
+#endif
+  {
+    if (nc->flags & MG_F_UDP) {
+      n = nc->iface->vtable->udp_send(nc, buf, len);
+    } else {
+      n = nc->iface->vtable->tcp_send(nc, buf, len);
+    }
+    DBG(("%p -> %d bytes", nc, n));
+  }
+
+#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
+  if (n > 0 && nc->mgr && nc->mgr->hexdump_file != NULL) {
+    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_SEND);
+  }
+#endif
+  if (n < 0) {
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+  } else if (n > 0) {
+    nc->last_io_time = (time_t) mg_time();
+    mbuf_remove(&nc->send_mbuf, n);
+    mbuf_trim(&nc->send_mbuf);
   }
+  if (n != 0) mg_call(nc, NULL, nc->user_data, MG_EV_SEND, &n);
 }
 
 /*
@@ -2769,8 +2927,8 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
 MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,
                                                 int proto,
                                                 union socket_address *sa) {
-  DBG(("%p %s://%s:%hu", nc, proto == SOCK_DGRAM ? "udp" : "tcp",
-       inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));
+  LOG(LL_DEBUG, ("%p %s://%s:%hu", nc, proto == SOCK_DGRAM ? "udp" : "tcp",
+                 inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));
 
   nc->flags |= MG_F_CONNECTING;
   if (proto == SOCK_DGRAM) {
@@ -2783,12 +2941,21 @@ MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,
 }
 
 void mg_if_connect_cb(struct mg_connection *nc, int err) {
-  DBG(("%p connect, err=%d", nc, err));
+  LOG(LL_DEBUG,
+      ("%p %s://%s:%hu -> %d", nc, (nc->flags & MG_F_UDP ? "udp" : "tcp"),
+       inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port), err));
   nc->flags &= ~MG_F_CONNECTING;
   if (err != 0) {
     nc->flags |= MG_F_CLOSE_IMMEDIATELY;
   }
-  mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+#if MG_ENABLE_SSL
+  if (err == 0 && (nc->flags & MG_F_SSL)) {
+    mg_ssl_handshake(nc);
+  } else
+#endif
+  {
+    mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+  }
 }
 
 #if MG_ENABLE_ASYNC_RESOLVER
@@ -2878,7 +3045,8 @@ struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
 #endif
 
 #if MG_ENABLE_SSL
-  DBG(("%p %s %s,%s,%s", nc, address, (opts.ssl_cert ? opts.ssl_cert : "-"),
+  LOG(LL_DEBUG,
+      ("%p %s %s,%s,%s", nc, address, (opts.ssl_cert ? opts.ssl_cert : "-"),
        (opts.ssl_key ? opts.ssl_key : "-"),
        (opts.ssl_ca_cert ? opts.ssl_ca_cert : "-")));
 
@@ -3124,7 +3292,7 @@ double mg_set_timer(struct mg_connection *c, double timestamp) {
   DBG(("%p %p %d -> %lu", c, c->priv_2, (c->flags & MG_F_RESOLVING ? 1 : 0),
        (unsigned long) timestamp));
   if ((c->flags & MG_F_RESOLVING) && c->priv_2 != NULL) {
-    ((struct mg_connection *) c->priv_2)->ev_timer_time = timestamp;
+    mg_set_timer((struct mg_connection *) c->priv_2, timestamp);
   }
   return result;
 }
@@ -3278,14 +3446,8 @@ struct mg_iface *mg_find_iface(struct mg_mgr *mgr,
 /* Amalgamated: #include "mg_internal.h" */
 /* Amalgamated: #include "mg_util.h" */
 
-#define MG_TCP_RECV_BUFFER_SIZE 1024
-#define MG_UDP_RECV_BUFFER_SIZE 1500
-
 static sock_t mg_open_listening_socket(union socket_address *sa, int type,
                                        int proto);
-#if MG_ENABLE_SSL
-static void mg_ssl_begin(struct mg_connection *nc);
-#endif
 
 void mg_set_non_blocking_mode(sock_t sock) {
 #ifdef _WIN32
@@ -3354,27 +3516,48 @@ int mg_socket_if_listen_tcp(struct mg_connection *nc,
   return 0;
 }
 
-int mg_socket_if_listen_udp(struct mg_connection *nc,
-                            union socket_address *sa) {
+static int mg_socket_if_listen_udp(struct mg_connection *nc,
+                                   union socket_address *sa) {
   sock_t sock = mg_open_listening_socket(sa, SOCK_DGRAM, 0);
   if (sock == INVALID_SOCKET) return (mg_get_errno() ? mg_get_errno() : 1);
   mg_sock_set(nc, sock);
   return 0;
 }
 
-void mg_socket_if_tcp_send(struct mg_connection *nc, const void *buf,
-                           size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
+static int mg_socket_if_tcp_send(struct mg_connection *nc, const void *buf,
+                                 size_t len) {
+  int n = (int) MG_SEND_FUNC(nc->sock, buf, len, 0);
+  if (n < 0 && !mg_is_error()) n = 0;
+  return n;
 }
 
-void mg_socket_if_udp_send(struct mg_connection *nc, const void *buf,
-                           size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
+static int mg_socket_if_udp_send(struct mg_connection *nc, const void *buf,
+                                 size_t len) {
+  int n = sendto(nc->sock, buf, len, 0, &nc->sa.sa, sizeof(nc->sa.sin));
+  if (n < 0 && !mg_is_error()) n = 0;
+  return n;
 }
 
-void mg_socket_if_recved(struct mg_connection *nc, size_t len) {
-  (void) nc;
-  (void) len;
+static int mg_socket_if_tcp_recv(struct mg_connection *nc, void *buf,
+                                 size_t len) {
+  int n = (int) MG_RECV_FUNC(nc->sock, buf, len, 0);
+  if (n == 0) {
+    /* Orderly shutdown of the socket, try flushing output. */
+    nc->flags |= MG_F_SEND_AND_CLOSE;
+  } else if (n < 0 && !mg_is_error()) {
+    n = 0;
+  }
+  return n;
+}
+
+static int mg_socket_if_udp_recv(struct mg_connection *nc, void *buf,
+                                 size_t len, union socket_address *sa,
+                                 size_t *sa_len) {
+  socklen_t sa_len_st = *sa_len;
+  int n = recvfrom(nc->sock, buf, len, 0, &sa->sa, &sa_len_st);
+  *sa_len = sa_len_st;
+  if (n < 0 && !mg_is_error()) n = 0;
+  return n;
 }
 
 int mg_socket_if_create_conn(struct mg_connection *nc) {
@@ -3411,14 +3594,7 @@ static int mg_accept_conn(struct mg_connection *lc) {
   DBG(("%p conn from %s:%d", nc, inet_ntoa(sa.sin.sin_addr),
        ntohs(sa.sin.sin_port)));
   mg_sock_set(nc, sock);
-#if MG_ENABLE_SSL
-  if (lc->flags & MG_F_SSL) {
-    if (mg_ssl_if_conn_accept(nc, lc) != MG_SSL_OK) mg_close_conn(nc);
-  } else
-#endif
-  {
-    mg_if_accept_tcp_cb(nc, &sa, sa_len);
-  }
+  mg_if_accept_tcp_cb(nc, &sa, sa_len);
   return 1;
 }
 
@@ -3469,207 +3645,42 @@ static sock_t mg_open_listening_socket(union socket_address *sa, int type,
   return sock;
 }
 
-static void mg_write_to_socket(struct mg_connection *nc) {
-  struct mbuf *io = &nc->send_mbuf;
-  int n = 0;
-
-#if MG_LWIP
-  /* With LWIP we don't know if the socket is ready */
-  if (io->len == 0) return;
-#endif
-
-  assert(io->len > 0);
+#define _MG_F_FD_CAN_READ 1
+#define _MG_F_FD_CAN_WRITE 1 << 1
+#define _MG_F_FD_ERROR 1 << 2
 
-  if (nc->flags & MG_F_UDP) {
-    int n =
-        sendto(nc->sock, io->buf, io->len, 0, &nc->sa.sa, sizeof(nc->sa.sin));
-    DBG(("%p %d %d %d %s:%hu", nc, nc->sock, n, mg_get_errno(),
-         inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port)));
-    mg_if_sent_cb(nc, n);
-    return;
+void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
+  int worth_logging =
+      fd_flags != 0 || (nc->flags & (MG_F_WANT_READ | MG_F_WANT_WRITE));
+  if (worth_logging) {
+    DBG(("%p fd=%d fd_flags=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock,
+         fd_flags, nc->flags, (int) nc->recv_mbuf.len,
+         (int) nc->send_mbuf.len));
   }
 
-#if MG_ENABLE_SSL
-  if (nc->flags & MG_F_SSL) {
-    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
-      n = mg_ssl_if_write(nc, io->buf, io->len);
-      DBG(("%p %d bytes -> %d (SSL)", nc, n, nc->sock));
-      if (n < 0) {
-        if (n != MG_SSL_WANT_READ && n != MG_SSL_WANT_WRITE) {
-          nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+  if (!mg_if_poll(nc, now)) return;
+
+  if (nc->flags & MG_F_CONNECTING) {
+    if (fd_flags != 0) {
+      int err = 0;
+#if !defined(MG_ESP8266)
+      if (!(nc->flags & MG_F_UDP)) {
+        socklen_t len = sizeof(err);
+        int ret =
+            getsockopt(nc->sock, SOL_SOCKET, SO_ERROR, (char *) &err, &len);
+        if (ret != 0) {
+          err = 1;
+        } else if (err == EAGAIN || err == EWOULDBLOCK) {
+          err = 0;
         }
-        return;
-      } else {
-        /* Successful SSL operation, clear off SSL wait flags */
-        nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE);
       }
-    } else {
-      mg_ssl_begin(nc);
-      return;
-    }
-  } else
+#else
+      /*
+       * On ESP8266 we use blocking connect.
+       */
+      err = nc->err;
 #endif
-  {
-    n = (int) MG_SEND_FUNC(nc->sock, io->buf, io->len, 0);
-    DBG(("%p %d bytes -> %d", nc, n, nc->sock));
-  }
-
-  mg_if_sent_cb(nc, n);
-}
-
-MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) {
-  size_t avail;
-  if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0;
-  avail = conn->recv_mbuf_limit - conn->recv_mbuf.len;
-  return avail > max ? max : avail;
-}
-
-static void mg_handle_tcp_read(struct mg_connection *conn) {
-  int n = 0;
-  char *buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE);
-
-  if (buf == NULL) {
-    DBG(("OOM"));
-    return;
-  }
-
-#if MG_ENABLE_SSL
-  if (conn->flags & MG_F_SSL) {
-    if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) {
-      /* SSL library may have more bytes ready to read than we ask to read.
-       * Therefore, read in a loop until we read everything. Without the loop,
-       * we skip to the next select() cycle which can just timeout. */
-      while ((n = mg_ssl_if_read(conn, buf, MG_TCP_RECV_BUFFER_SIZE)) > 0) {
-        DBG(("%p %d bytes <- %d (SSL)", conn, n, conn->sock));
-        mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */);
-        buf = NULL;
-        if (conn->flags & MG_F_CLOSE_IMMEDIATELY) break;
-        /* buf has been freed, we need a new one. */
-        buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE);
-        if (buf == NULL) break;
-      }
-      MG_FREE(buf);
-      if (n < 0 && n != MG_SSL_WANT_READ) conn->flags |= MG_F_CLOSE_IMMEDIATELY;
-    } else {
-      MG_FREE(buf);
-      mg_ssl_begin(conn);
-      return;
-    }
-  } else
-#endif
-  {
-    n = (int) MG_RECV_FUNC(conn->sock, buf,
-                           recv_avail_size(conn, MG_TCP_RECV_BUFFER_SIZE), 0);
-    DBG(("%p %d bytes (PLAIN) <- %d", conn, n, conn->sock));
-    if (n > 0) {
-      mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */);
-    } else {
-      MG_FREE(buf);
-    }
-    if (n == 0) {
-      /* Orderly shutdown of the socket, try flushing output. */
-      conn->flags |= MG_F_SEND_AND_CLOSE;
-    } else if (n < 0 && mg_is_error()) {
-      conn->flags |= MG_F_CLOSE_IMMEDIATELY;
-    }
-  }
-}
-
-static int mg_recvfrom(struct mg_connection *nc, union socket_address *sa,
-                       socklen_t *sa_len, char **buf) {
-  int n;
-  *buf = (char *) MG_MALLOC(MG_UDP_RECV_BUFFER_SIZE);
-  if (*buf == NULL) {
-    DBG(("Out of memory"));
-    return -ENOMEM;
-  }
-  n = recvfrom(nc->sock, *buf, MG_UDP_RECV_BUFFER_SIZE, 0, &sa->sa, sa_len);
-  if (n <= 0) {
-    DBG(("%p recvfrom: %s", nc, strerror(mg_get_errno())));
-    MG_FREE(*buf);
-  }
-  return n;
-}
-
-static void mg_handle_udp_read(struct mg_connection *nc) {
-  char *buf = NULL;
-  union socket_address sa;
-  socklen_t sa_len = sizeof(sa);
-  int n = mg_recvfrom(nc, &sa, &sa_len, &buf);
-  DBG(("%p %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr),
-       ntohs(nc->sa.sin.sin_port)));
-  mg_if_recv_udp_cb(nc, buf, n, &sa, sa_len);
-}
-
-#if MG_ENABLE_SSL
-static void mg_ssl_begin(struct mg_connection *nc) {
-  int server_side = (nc->listener != NULL);
-  enum mg_ssl_if_result res = mg_ssl_if_handshake(nc);
-  DBG(("%p %d res %d", nc, server_side, res));
-
-  if (res == MG_SSL_OK) {
-    nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
-    nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE);
-
-    if (server_side) {
-      union socket_address sa;
-      socklen_t sa_len = sizeof(sa);
-      (void) getpeername(nc->sock, &sa.sa, &sa_len);
-      mg_if_accept_tcp_cb(nc, &sa, sa_len);
-    } else {
-      mg_if_connect_cb(nc, 0);
-    }
-  } else if (res != MG_SSL_WANT_READ && res != MG_SSL_WANT_WRITE) {
-    if (!server_side) {
-      mg_if_connect_cb(nc, res);
-    }
-    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-  }
-}
-#endif /* MG_ENABLE_SSL */
-
-#define _MG_F_FD_CAN_READ 1
-#define _MG_F_FD_CAN_WRITE 1 << 1
-#define _MG_F_FD_ERROR 1 << 2
-
-void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
-  int worth_logging =
-      fd_flags != 0 || (nc->flags & (MG_F_WANT_READ | MG_F_WANT_WRITE));
-  if (worth_logging) {
-    DBG(("%p fd=%d fd_flags=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock,
-         fd_flags, nc->flags, (int) nc->recv_mbuf.len,
-         (int) nc->send_mbuf.len));
-  }
-
-  if (nc->flags & MG_F_CONNECTING) {
-    if (fd_flags != 0) {
-      int err = 0;
-#if !defined(MG_ESP8266)
-      if (!(nc->flags & MG_F_UDP)) {
-        socklen_t len = sizeof(err);
-        int ret =
-            getsockopt(nc->sock, SOL_SOCKET, SO_ERROR, (char *) &err, &len);
-        if (ret != 0) {
-          err = 1;
-        } else if (err == EAGAIN || err == EWOULDBLOCK) {
-          err = 0;
-        }
-      }
-#else
-      /*
-       * On ESP8266 we use blocking connect.
-       */
-      err = nc->err;
-#endif
-#if MG_ENABLE_SSL
-      if ((nc->flags & MG_F_SSL) && err == 0) {
-        mg_ssl_begin(nc);
-      } else {
-        mg_if_connect_cb(nc, err);
-      }
-#else
       mg_if_connect_cb(nc, err);
-#endif
     } else if (nc->err != 0) {
       mg_if_connect_cb(nc, nc->err);
     }
@@ -3677,7 +3688,7 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
 
   if (fd_flags & _MG_F_FD_CAN_READ) {
     if (nc->flags & MG_F_UDP) {
-      mg_handle_udp_read(nc);
+      mg_if_can_recv_cb(nc);
     } else {
       if (nc->flags & MG_F_LISTENING) {
         /*
@@ -3687,22 +3698,16 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
          */
         mg_accept_conn(nc);
       } else {
-        mg_handle_tcp_read(nc);
+        mg_if_can_recv_cb(nc);
       }
     }
   }
 
-  if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
-    if ((fd_flags & _MG_F_FD_CAN_WRITE) && nc->send_mbuf.len > 0) {
-      mg_write_to_socket(nc);
-    }
-    mg_if_poll(nc, (time_t) now);
-    mg_if_timer(nc, now);
-  }
+  if (fd_flags & _MG_F_FD_CAN_WRITE) mg_if_can_send_cb(nc);
 
   if (worth_logging) {
-    DBG(("%p after fd=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock, nc->flags,
-         (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
+    DBG(("%p after fd=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock,
+         nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
   }
 }
 
@@ -3816,8 +3821,7 @@ time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) {
       }
 #endif
 
-      if (!(nc->flags & MG_F_WANT_WRITE) &&
-          nc->recv_mbuf.len < nc->recv_mbuf_limit &&
+      if (nc->recv_mbuf.len < nc->recv_mbuf_limit &&
           (!(nc->flags & MG_F_UDP) || nc->listener == NULL)) {
         mg_add_to_set(nc->sock, &read_set, &max_fd);
       }
@@ -3888,14 +3892,6 @@ time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) {
     mg_mgr_handle_conn(nc, fd_flags, now);
   }
 
-  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {
-    tmp = nc->next;
-    if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
-        (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) {
-      mg_close_conn(nc);
-    }
-  }
-
   return (time_t) now;
 }
 
@@ -4004,7 +4000,8 @@ void mg_socket_if_get_conn_addr(struct mg_connection *nc, int remote,
     mg_socket_if_connect_udp,                                           \
     mg_socket_if_tcp_send,                                              \
     mg_socket_if_udp_send,                                              \
-    mg_socket_if_recved,                                                \
+    mg_socket_if_tcp_recv,                                              \
+    mg_socket_if_udp_recv,                                              \
     mg_socket_if_create_conn,                                           \
     mg_socket_if_destroy_conn,                                          \
     mg_socket_if_sock_set,                                              \
@@ -4032,18 +4029,35 @@ struct socksdata {
   char *proxy_addr;        /* HOST:PORT of the socks5 proxy server */
   struct mg_connection *s; /* Respective connection to the server */
   struct mg_connection *c; /* Connection to the client */
-  struct mbuf tmp;         /* Temporary buffer for sent data */
 };
 
 static void socks_if_disband(struct socksdata *d) {
   LOG(LL_DEBUG, ("disbanding proxy %p %p", d->c, d->s));
-  if (d->c) d->c->flags |= MG_F_SEND_AND_CLOSE;
-  if (d->s) d->s->flags |= MG_F_SEND_AND_CLOSE;
-  d->c = d->s = NULL;
+  if (d->c) {
+    d->c->flags |= MG_F_SEND_AND_CLOSE;
+    d->c->user_data = NULL;
+    d->c = NULL;
+  }
+  if (d->s) {
+    d->s->flags |= MG_F_SEND_AND_CLOSE;
+    d->s->user_data = NULL;
+    d->s = NULL;
+  }
+}
+
+static void socks_if_relay(struct mg_connection *s) {
+  struct socksdata *d = (struct socksdata *) s->user_data;
+  if (d == NULL || d->c == NULL || !(s->flags & MG_SOCKS_CONNECT_DONE) ||
+      d->s == NULL) {
+    return;
+  }
+  if (s->recv_mbuf.len > 0) mg_if_can_recv_cb(d->c);
+  if (d->c->send_mbuf.len > 0 && s->send_mbuf.len == 0) mg_if_can_send_cb(d->c);
 }
 
 static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) {
   struct socksdata *d = (struct socksdata *) c->user_data;
+  if (d == NULL) return;
   if (ev == MG_EV_CONNECT) {
     int res = *(int *) ev_data;
     if (res == 0) {
@@ -4076,6 +4090,7 @@ static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) {
       memcpy(buf + 4, &d->c->sa.sin.sin_addr, 4);
       memcpy(buf + 8, &d->c->sa.sin.sin_port, 2);
       mg_send(c, buf, sizeof(buf));
+      LOG(LL_DEBUG, ("%p Sent connect request", c));
     }
     /* Process connect request */
     if ((c->flags & MG_SOCKS_HANDSHAKE_DONE) &&
@@ -4088,17 +4103,12 @@ static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) {
       }
       mbuf_remove(&c->recv_mbuf, 10);
       c->flags |= MG_SOCKS_CONNECT_DONE;
-      /* Connected. Move sent data from client, if any, to server */
-      if (d->s && d->c) {
-        mbuf_append(&d->s->send_mbuf, d->tmp.buf, d->tmp.len);
-        mbuf_free(&d->tmp);
-      }
-    }
-    /* All flags are set, we're in relay mode */
-    if ((c->flags & MG_SOCKS_CONNECT_DONE) && d->c && d->s) {
-      mbuf_append(&d->c->recv_mbuf, d->s->recv_mbuf.buf, d->s->recv_mbuf.len);
-      mbuf_remove(&d->s->recv_mbuf, d->s->recv_mbuf.len);
+      LOG(LL_DEBUG, ("%p Connect done %p", c, d->c));
+      mg_if_connect_cb(d->c, 0);
     }
+    socks_if_relay(c);
+  } else if (ev == MG_EV_SEND || ev == MG_EV_POLL) {
+    socks_if_relay(c);
   }
 }
 
@@ -4108,7 +4118,7 @@ static void mg_socks_if_connect_tcp(struct mg_connection *c,
   d->c = c;
   d->s = mg_connect(c->mgr, d->proxy_addr, socks_if_handler);
   d->s->user_data = d;
-  LOG(LL_DEBUG, ("%p %s", c, d->proxy_addr));
+  LOG(LL_DEBUG, ("%p %s %p %p", c, d->proxy_addr, d, d->s));
   (void) sa;
 }
 
@@ -4130,29 +4140,44 @@ static int mg_socks_if_listen_udp(struct mg_connection *c,
   return -1;
 }
 
-static void mg_socks_if_tcp_send(struct mg_connection *c, const void *buf,
-                                 size_t len) {
+static int mg_socks_if_tcp_send(struct mg_connection *c, const void *buf,
+                                size_t len) {
+  int res;
   struct socksdata *d = (struct socksdata *) c->iface->data;
-  LOG(LL_DEBUG, ("%p -> %p %d %d", c, buf, (int) len, (int) c->send_mbuf.len));
-  if (d && d->s && d->s->flags & MG_SOCKS_CONNECT_DONE) {
-    mbuf_append(&d->s->send_mbuf, d->tmp.buf, d->tmp.len);
-    mbuf_append(&d->s->send_mbuf, buf, len);
-    mbuf_free(&d->tmp);
-  } else {
-    mbuf_append(&d->tmp, buf, len);
-  }
+  if (d->s == NULL) return -1;
+  res = (int) mbuf_append(&d->s->send_mbuf, buf, len);
+  DBG(("%p -> %d -> %p", c, res, d->s));
+  return res;
 }
 
-static void mg_socks_if_udp_send(struct mg_connection *c, const void *buf,
-                                 size_t len) {
+static int mg_socks_if_udp_send(struct mg_connection *c, const void *buf,
+                                size_t len) {
   (void) c;
   (void) buf;
   (void) len;
+  return -1;
+}
+
+int mg_socks_if_tcp_recv(struct mg_connection *c, void *buf, size_t len) {
+  struct socksdata *d = (struct socksdata *) c->iface->data;
+  if (d->s == NULL) return -1;
+  if (len > d->s->recv_mbuf.len) len = d->s->recv_mbuf.len;
+  if (len > 0) {
+    memcpy(buf, d->s->recv_mbuf.buf, len);
+    mbuf_remove(&d->s->recv_mbuf, len);
+  }
+  DBG(("%p <- %d <- %p", c, (int) len, d->s));
+  return len;
 }
 
-static void mg_socks_if_recved(struct mg_connection *c, size_t len) {
+int mg_socks_if_udp_recv(struct mg_connection *c, void *buf, size_t len,
+                         union socket_address *sa, size_t *sa_len) {
   (void) c;
+  (void) buf;
   (void) len;
+  (void) sa;
+  (void) sa_len;
+  return -1;
 }
 
 static int mg_socks_if_create_conn(struct mg_connection *c) {
@@ -4181,7 +4206,6 @@ static void mg_socks_if_free(struct mg_iface *iface) {
   LOG(LL_DEBUG, ("%p", iface));
   if (d != NULL) {
     socks_if_disband(d);
-    mbuf_free(&d->tmp);
     MG_FREE(d->proxy_addr);
     MG_FREE(d);
     iface->data = NULL;
@@ -4212,14 +4236,15 @@ static void mg_socks_if_get_conn_addr(struct mg_connection *c, int remote,
 }
 
 const struct mg_iface_vtable mg_socks_iface_vtable = {
-    mg_socks_if_init,        mg_socks_if_free,
-    mg_socks_if_add_conn,    mg_socks_if_remove_conn,
-    mg_socks_if_poll,        mg_socks_if_listen_tcp,
-    mg_socks_if_listen_udp,  mg_socks_if_connect_tcp,
-    mg_socks_if_connect_udp, mg_socks_if_tcp_send,
-    mg_socks_if_udp_send,    mg_socks_if_recved,
-    mg_socks_if_create_conn, mg_socks_if_destroy_conn,
-    mg_socks_if_sock_set,    mg_socks_if_get_conn_addr,
+    mg_socks_if_init,          mg_socks_if_free,
+    mg_socks_if_add_conn,      mg_socks_if_remove_conn,
+    mg_socks_if_poll,          mg_socks_if_listen_tcp,
+    mg_socks_if_listen_udp,    mg_socks_if_connect_tcp,
+    mg_socks_if_connect_udp,   mg_socks_if_tcp_send,
+    mg_socks_if_udp_send,      mg_socks_if_tcp_recv,
+    mg_socks_if_udp_recv,      mg_socks_if_create_conn,
+    mg_socks_if_destroy_conn,  mg_socks_if_sock_set,
+    mg_socks_if_get_conn_addr,
 };
 
 struct mg_iface *mg_socks_mk_iface(struct mg_mgr *mgr, const char *proxy_addr) {
@@ -4639,9 +4664,12 @@ const char *mg_set_ssl(struct mg_connection *nc, const char *cert,
 
 #include <mbedtls/debug.h>
 #include <mbedtls/ecp.h>
+#include <mbedtls/net.h>
 #include <mbedtls/platform.h>
 #include <mbedtls/ssl.h>
+#include <mbedtls/ssl_internal.h>
 #include <mbedtls/x509_crt.h>
+#include <mbedtls/version.h>
 
 static void mg_ssl_mbed_log(void *ctx, int level, const char *file, int line,
                             const char *str) {
@@ -4651,6 +4679,8 @@ static void mg_ssl_mbed_log(void *ctx, int level, const char *file, int line,
       cs_level = LL_ERROR;
       break;
     case 2:
+      cs_level = LL_INFO;
+      break;
     case 3:
       cs_level = LL_DEBUG;
       break;
@@ -4670,12 +4700,14 @@ struct mg_ssl_if_ctx {
   mbedtls_pk_context *key;
   mbedtls_x509_crt *ca_cert;
   struct mbuf cipher_suites;
+  size_t saved_len;
 };
 
 /* Must be provided by the platform. ctx is struct mg_connection. */
 extern int mg_ssl_if_mbed_random(void *ctx, unsigned char *buf, size_t len);
 
 void mg_ssl_if_init() {
+  LOG(LL_INFO, ("%s", MBEDTLS_VERSION_STRING_FULL));
 }
 
 enum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc,
@@ -4796,40 +4828,41 @@ enum mg_ssl_if_result mg_ssl_if_conn_init(
   return MG_SSL_OK;
 }
 
-#if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL
-int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len);
-int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len);
-#else
-static int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len) {
+static int mg_ssl_if_mbed_send(void *ctx, const unsigned char *buf,
+                               size_t len) {
   struct mg_connection *nc = (struct mg_connection *) ctx;
-  int n = (int) MG_SEND_FUNC(nc->sock, buf, len, 0);
-  LOG(LL_DEBUG, ("%p %d -> %d", nc, (int) len, n));
-  if (n >= 0) return n;
-  n = mg_get_errno();
-  return ((n == EAGAIN || n == EINPROGRESS) ? MBEDTLS_ERR_SSL_WANT_WRITE : -1);
+  int n = nc->iface->vtable->tcp_send(nc, buf, len);
+  if (n > 0) return n;
+  if (n == 0) return MBEDTLS_ERR_SSL_WANT_WRITE;
+  return MBEDTLS_ERR_NET_SEND_FAILED;
 }
 
-static int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len) {
+static int mg_ssl_if_mbed_recv(void *ctx, unsigned char *buf, size_t len) {
   struct mg_connection *nc = (struct mg_connection *) ctx;
-  int n = (int) MG_RECV_FUNC(nc->sock, buf, len, 0);
-  LOG(LL_DEBUG, ("%p %d <- %d", nc, (int) len, n));
-  if (n >= 0) return n;
-  n = mg_get_errno();
-  return ((n == EAGAIN || n == EINPROGRESS) ? MBEDTLS_ERR_SSL_WANT_READ : -1);
+  int n = nc->iface->vtable->tcp_recv(nc, buf, len);
+  if (n > 0) return n;
+  if (n == 0) return MBEDTLS_ERR_SSL_WANT_READ;
+  return MBEDTLS_ERR_NET_RECV_FAILED;
 }
-#endif
 
 static enum mg_ssl_if_result mg_ssl_if_mbed_err(struct mg_connection *nc,
                                                 int ret) {
-  if (ret == MBEDTLS_ERR_SSL_WANT_READ) return MG_SSL_WANT_READ;
-  if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) return MG_SSL_WANT_WRITE;
-  if (ret !=
-      MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { /* CLOSE_NOTIFY = Normal shutdown */
-    LOG(LL_ERROR, ("%p SSL error: %d", nc, ret));
+  enum mg_ssl_if_result res = MG_SSL_OK;
+  if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
+    res = MG_SSL_WANT_READ;
+  } else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+    res = MG_SSL_WANT_WRITE;
+  } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
+    LOG(LL_DEBUG, ("%p TLS connection closed by peer", nc));
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+    res = MG_SSL_OK;
+  } else {
+    LOG(LL_ERROR, ("%p mbedTLS error: -0x%04x", nc, -ret));
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+    res = MG_SSL_ERROR;
   }
   nc->err = ret;
-  nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-  return MG_SSL_ERROR;
+  return res;
 }
 
 static void mg_ssl_if_mbed_free_certs_and_keys(struct mg_ssl_if_ctx *ctx) {
@@ -4860,7 +4893,8 @@ enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {
   int err;
   /* If bio is not yet set, do it now. */
   if (ctx->ssl->p_bio == NULL) {
-    mbedtls_ssl_set_bio(ctx->ssl, nc, ssl_socket_send, ssl_socket_recv, NULL);
+    mbedtls_ssl_set_bio(ctx->ssl, nc, mg_ssl_if_mbed_send, mg_ssl_if_mbed_recv,
+                        NULL);
   }
   err = mbedtls_ssl_handshake(ctx->ssl);
   if (err != 0) return mg_ssl_if_mbed_err(nc, err);
@@ -4886,20 +4920,35 @@ enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {
   return MG_SSL_OK;
 }
 
-int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t buf_size) {
+int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t len) {
   struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
-  int n = mbedtls_ssl_read(ctx->ssl, (unsigned char *) buf, buf_size);
-  DBG(("%p %d -> %d", nc, (int) buf_size, n));
+  int n = mbedtls_ssl_read(ctx->ssl, (unsigned char *) buf, len);
+  DBG(("%p %d -> %d", nc, (int) len, n));
   if (n < 0) return mg_ssl_if_mbed_err(nc, n);
   if (n == 0) nc->flags |= MG_F_CLOSE_IMMEDIATELY;
   return n;
 }
 
-int mg_ssl_if_write(struct mg_connection *nc, const void *data, size_t len) {
+int mg_ssl_if_write(struct mg_connection *nc, const void *buf, size_t len) {
   struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
-  int n = mbedtls_ssl_write(ctx->ssl, (const unsigned char *) data, len);
-  DBG(("%p %d -> %d", nc, (int) len, n));
-  if (n < 0) return mg_ssl_if_mbed_err(nc, n);
+  /* Per mbedTLS docs, if write returns WANT_READ or WANT_WRITE, the operation
+   * should be retried with the same data and length.
+   * Here we assume that the data being pushed will remain the same but the
+   * amount may grow between calls so we save the length that was used and
+   * retry. The assumption being that the data itself won't change and won't
+   * be removed. */
+  size_t l = len;
+  if (ctx->saved_len > 0 && ctx->saved_len < l) l = ctx->saved_len;
+  int n = mbedtls_ssl_write(ctx->ssl, (const unsigned char *) buf, l);
+  DBG(("%p %d,%d,%d -> %d", nc, (int) len, (int) ctx->saved_len, (int) l, n));
+  if (n < 0) {
+    if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE) {
+      ctx->saved_len = len;
+    }
+    return mg_ssl_if_mbed_err(nc, n);
+  } else if (n > 0) {
+    ctx->saved_len = 0;
+  }
   return n;
 }
 
@@ -11557,7 +11606,6 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev,
   time_t now = (time_t) mg_time();
   struct mg_resolve_async_request *req;
   struct mg_dns_message *msg;
-  int first = 0;
 #if !MG_ENABLE_CALLBACK_USERDATA
   void *user_data = nc->user_data;
 #endif
@@ -11571,17 +11619,16 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev,
   }
 
   switch (ev) {
-    case MG_EV_CONNECT:
-      /* don't depend on timer not being at epoch for sending out first req */
-      first = 1;
-    /* fallthrough */
     case MG_EV_POLL:
       if (req->retries > req->max_retries) {
         req->err = MG_RESOLVE_EXCEEDED_RETRY_COUNT;
         nc->flags |= MG_F_CLOSE_IMMEDIATELY;
         break;
       }
-      if (first || now - req->last_time >= req->timeout) {
+      if (nc->flags & MG_F_CONNECTING) break;
+    /* fallthrough */
+    case MG_EV_CONNECT:
+      if (req->retries == 0 || now - req->last_time >= req->timeout) {
         mg_send_dns_query(nc, req->name, req->query);
         req->last_time = now;
         req->retries++;
@@ -13752,7 +13799,7 @@ static sock_t mg_open_listening_socket(struct mg_connection *nc,
                                        union socket_address *sa, int type,
                                        int proto);
 
-void mg_set_non_blocking_mode(sock_t sock) {
+static void mg_set_non_blocking_mode(sock_t sock) {
   SlSockNonblocking_t opt;
 #if SL_MAJOR_VERSION_NUM < 2
   opt.NonblockingEnabled = 1;
@@ -13766,17 +13813,19 @@ static int mg_is_error(int n) {
   return (n < 0 && n != SL_ERROR_BSD_EALREADY && n != SL_ERROR_BSD_EAGAIN);
 }
 
-void mg_sl_if_connect_tcp(struct mg_connection *nc,
-                          const union socket_address *sa) {
+static void mg_sl_if_connect_tcp(struct mg_connection *nc,
+                                 const union socket_address *sa) {
   int proto = 0;
+#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK
   if (nc->flags & MG_F_SSL) proto = SL_SEC_SOCKET;
+#endif
   sock_t sock = sl_Socket(AF_INET, SOCK_STREAM, proto);
   if (sock < 0) {
     nc->err = sock;
     goto out;
   }
   mg_sock_set(nc, sock);
-#if MG_ENABLE_SSL
+#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK
   nc->err = sl_set_ssl_opts(sock, nc);
   if (nc->err != 0) goto out;
 #endif
@@ -13786,7 +13835,7 @@ out:
        ntohs(sa->sin.sin_port), nc->sock, proto, nc->err));
 }
 
-void mg_sl_if_connect_udp(struct mg_connection *nc) {
+static void mg_sl_if_connect_udp(struct mg_connection *nc) {
   sock_t sock = sl_Socket(AF_INET, SOCK_DGRAM, 0);
   if (sock < 0) {
     nc->err = sock;
@@ -13796,7 +13845,8 @@ void mg_sl_if_connect_udp(struct mg_connection *nc) {
   nc->err = 0;
 }
 
-int mg_sl_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
+static int mg_sl_if_listen_tcp(struct mg_connection *nc,
+                               union socket_address *sa) {
   int proto = 0;
   if (nc->flags & MG_F_SSL) proto = SL_SEC_SOCKET;
   sock_t sock = mg_open_listening_socket(nc, sa, SOCK_STREAM, proto);
@@ -13805,27 +13855,50 @@ int mg_sl_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
   return 0;
 }
 
-int mg_sl_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
+static int mg_sl_if_listen_udp(struct mg_connection *nc,
+                               union socket_address *sa) {
   sock_t sock = mg_open_listening_socket(nc, sa, SOCK_DGRAM, 0);
   if (sock == INVALID_SOCKET) return (errno ? errno : 1);
   mg_sock_set(nc, sock);
   return 0;
 }
 
-void mg_sl_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
+static int mg_sl_if_tcp_send(struct mg_connection *nc, const void *buf,
+                             size_t len) {
+  int n = (int) sl_Send(nc->sock, buf, len, 0);
+  if (n < 0 && !mg_is_error(n)) n = 0;
+  return n;
 }
 
-void mg_sl_if_udp_send(struct mg_connection *nc, const void *buf, size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
+static int mg_sl_if_udp_send(struct mg_connection *nc, const void *buf,
+                             size_t len) {
+  int n = sl_SendTo(nc->sock, buf, len, 0, &nc->sa.sa, sizeof(nc->sa.sin));
+  if (n < 0 && !mg_is_error(n)) n = 0;
+  return n;
 }
 
-void mg_sl_if_recved(struct mg_connection *nc, size_t len) {
-  (void) nc;
-  (void) len;
+static int mg_sl_if_tcp_recv(struct mg_connection *nc, void *buf, size_t len) {
+  int n = sl_Recv(nc->sock, buf, len, 0);
+  if (n == 0) {
+    /* Orderly shutdown of the socket, try flushing output. */
+    nc->flags |= MG_F_SEND_AND_CLOSE;
+  } else if (n < 0 && !mg_is_error(n)) {
+    n = 0;
+  }
+  return n;
 }
 
-int mg_sl_if_create_conn(struct mg_connection *nc) {
+static int mg_sl_if_udp_recv(struct mg_connection *nc, void *buf, size_t len,
+                             union socket_address *sa, size_t *sa_len) {
+  SlSocklen_t sa_len_t = *sa_len;
+  int n = sl_RecvFrom(nc->sock, buf, MG_UDP_RECV_BUFFER_SIZE, 0,
+                      (SlSockAddr_t *) sa, &sa_len_t);
+  *sa_len = sa_len_t;
+  if (n < 0 && !mg_is_error(n)) n = 0;
+  return n;
+}
+
+static int mg_sl_if_create_conn(struct mg_connection *nc) {
   (void) nc;
   return 1;
 }
@@ -13856,7 +13929,6 @@ static int mg_accept_conn(struct mg_connection *lc) {
   DBG(("%p conn from %s:%d", nc, inet_ntoa(sa.sin.sin_addr),
        ntohs(sa.sin.sin_port)));
   mg_sock_set(nc, sock);
-  if (nc->flags & MG_F_SSL) nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
   mg_if_accept_tcp_cb(nc, &sa, sa_len);
   return 1;
 }
@@ -13870,11 +13942,11 @@ static sock_t mg_open_listening_socket(struct mg_connection *nc,
       (sa->sa.sa_family == AF_INET) ? sizeof(sa->sin) : sizeof(sa->sin6);
   sock_t sock = sl_Socket(sa->sa.sa_family, type, proto);
   if (sock < 0) return sock;
+#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK
+  if ((r = sl_set_ssl_opts(sock, nc)) < 0) goto clean;
+#endif
   if ((r = sl_Bind(sock, &sa->sa, sa_len)) < 0) goto clean;
   if (type != SOCK_DGRAM) {
-#if MG_ENABLE_SSL
-    if ((r = sl_set_ssl_opts(sock, nc)) < 0) goto clean;
-#endif
     if ((r = sl_Listen(sock, SOMAXCONN)) < 0) goto clean;
   }
   mg_set_non_blocking_mode(sock);
@@ -13886,86 +13958,18 @@ clean:
   return sock;
 }
 
-static void mg_write_to_socket(struct mg_connection *nc) {
-  struct mbuf *io = &nc->send_mbuf;
-  int n = 0;
-
-  if (nc->flags & MG_F_UDP) {
-    n = sl_SendTo(nc->sock, io->buf, io->len, 0, &nc->sa.sa,
-                  sizeof(nc->sa.sin));
-    DBG(("%p %d %d %d %s:%hu", nc, nc->sock, n, errno,
-         inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port)));
-  } else {
-    n = (int) sl_Send(nc->sock, io->buf, io->len, 0);
-    DBG(("%p %d bytes -> %d", nc, n, nc->sock));
-  }
-
-  if (n > 0) {
-    mg_if_sent_cb(nc, n);
-  } else if (n < 0 && mg_is_error(n)) {
-    /* Something went wrong, drop the connection. */
-    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-  }
-}
-
-MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) {
-  size_t avail;
-  if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0;
-  avail = conn->recv_mbuf_limit - conn->recv_mbuf.len;
-  return avail > max ? max : avail;
-}
-
-static void mg_handle_tcp_read(struct mg_connection *conn) {
-  int n = 0;
-  char *buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE);
-
-  if (buf == NULL) {
-    DBG(("OOM"));
-    return;
-  }
-
-  n = (int) sl_Recv(conn->sock, buf,
-                    recv_avail_size(conn, MG_TCP_RECV_BUFFER_SIZE), 0);
-  DBG(("%p %d bytes <- %d", conn, n, conn->sock));
-  if (n > 0) {
-    mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */);
-  } else {
-    MG_FREE(buf);
-  }
-  if (n == 0) {
-    /* Orderly shutdown of the socket, try flushing output. */
-    conn->flags |= MG_F_SEND_AND_CLOSE;
-  } else if (mg_is_error(n)) {
-    conn->flags |= MG_F_CLOSE_IMMEDIATELY;
-  }
-}
-
-static void mg_handle_udp_read(struct mg_connection *nc) {
-  char *buf = (char *) MG_MALLOC(MG_UDP_RECV_BUFFER_SIZE);
-  if (buf == NULL) return;
-  union socket_address sa;
-  socklen_t sa_len = sizeof(sa);
-  int n = sl_RecvFrom(nc->sock, buf, MG_UDP_RECV_BUFFER_SIZE, 0,
-                      (SlSockAddr_t *) &sa, &sa_len);
-  DBG(("%p %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr),
-       ntohs(nc->sa.sin.sin_port)));
-  if (n > 0) {
-    mg_if_recv_udp_cb(nc, buf, n, &sa, sa_len);
-  } else {
-    MG_FREE(buf);
-  }
-}
-
 #define _MG_F_FD_CAN_READ 1
 #define _MG_F_FD_CAN_WRITE 1 << 1
 #define _MG_F_FD_ERROR 1 << 2
 
 void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
-  DBG(("%p fd=%d fd_flags=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock,
+  DBG(("%p fd=%d fd_flags=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock,
        fd_flags, nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
 
+  if (!mg_if_poll(nc, now)) return;
+
   if (nc->flags & MG_F_CONNECTING) {
-    if (nc->flags & MG_F_UDP || nc->err != SL_ERROR_BSD_EALREADY) {
+    if ((nc->flags & MG_F_UDP) || nc->err != SL_ERROR_BSD_EALREADY) {
       mg_if_connect_cb(nc, nc->err);
     } else {
       /* In SimpleLink, to get status of non-blocking connect() we need to wait
@@ -13987,9 +13991,6 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
             ) {
           nc->err = 0;
         }
-        if (nc->flags & MG_F_SSL && nc->err == 0) {
-          nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
-        }
         mg_if_connect_cb(nc, nc->err);
       }
     }
@@ -13999,28 +14000,21 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
 
   if (fd_flags & _MG_F_FD_CAN_READ) {
     if (nc->flags & MG_F_UDP) {
-      mg_handle_udp_read(nc);
+      mg_if_can_recv_cb(nc);
     } else {
       if (nc->flags & MG_F_LISTENING) {
         mg_accept_conn(nc);
       } else {
-        mg_handle_tcp_read(nc);
+        mg_if_can_recv_cb(nc);
       }
     }
   }
 
-  if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
-    if ((fd_flags & _MG_F_FD_CAN_WRITE) && nc->send_mbuf.len > 0) {
-      mg_write_to_socket(nc);
-    }
-
-    if (!(fd_flags & (_MG_F_FD_CAN_READ | _MG_F_FD_CAN_WRITE))) {
-      mg_if_poll(nc, now);
-    }
-    mg_if_timer(nc, now);
+  if (fd_flags & _MG_F_FD_CAN_WRITE) {
+    mg_if_can_send_cb(nc);
   }
 
-  DBG(("%p after fd=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock, nc->flags,
+  DBG(("%p after fd=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock, nc->flags,
        (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
 }
 
@@ -14141,14 +14135,6 @@ time_t mg_sl_if_poll(struct mg_iface *iface, int timeout_ms) {
     mg_mgr_handle_conn(nc, fd_flags, now);
   }
 
-  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {
-    tmp = nc->next;
-    if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
-        (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) {
-      mg_close_conn(nc);
-    }
-  }
-
   return now;
 }
 
@@ -14199,7 +14185,8 @@ void sl_restart_cb(struct mg_mgr *mgr) {
     mg_sl_if_connect_udp,                                               \
     mg_sl_if_tcp_send,                                                  \
     mg_sl_if_udp_send,                                                  \
-    mg_sl_if_recved,                                                    \
+    mg_sl_if_tcp_recv,                                                  \
+    mg_sl_if_udp_recv,                                                  \
     mg_sl_if_create_conn,                                               \
     mg_sl_if_destroy_conn,                                              \
     mg_sl_if_sock_set,                                                  \
@@ -14272,6 +14259,31 @@ enum mg_ssl_if_result mg_ssl_if_conn_init(
   return MG_SSL_OK;
 }
 
+enum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc,
+                                            struct mg_connection *lc) {
+  /* SimpleLink does everything for us, nothing for us to do. */
+  (void) nc;
+  (void) lc;
+  return MG_SSL_OK;
+}
+
+enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {
+  /* SimpleLink has already performed the handshake, nothing to do. */
+  return MG_SSL_OK;
+}
+
+int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t len) {
+  /* SimpelLink handles TLS, so this is just a pass-through. */
+  int n = nc->iface->vtable->tcp_recv(nc, buf, len);
+  if (n == 0) nc->flags |= MG_F_WANT_READ;
+  return n;
+}
+
+int mg_ssl_if_write(struct mg_connection *nc, const void *buf, size_t len) {
+  /* SimpelLink handles TLS, so this is just a pass-through. */
+  return nc->iface->vtable->tcp_send(nc, buf, len);
+}
+
 void mg_ssl_if_conn_close_notify(struct mg_connection *nc) {
   /* Nothing to do */
   (void) nc;
@@ -14372,57 +14384,60 @@ int sl_set_ssl_opts(int sock, struct mg_connection *nc) {
   const struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
   DBG(("%p ssl ctx: %p", nc, ctx));
 
-  if (ctx != NULL) {
-    DBG(("%p %s,%s,%s,%s", nc, (ctx->ssl_cert ? ctx->ssl_cert : "-"),
-         (ctx->ssl_key ? ctx->ssl_cert : "-"),
-         (ctx->ssl_ca_cert ? ctx->ssl_ca_cert : "-"),
-         (ctx->ssl_server_name ? ctx->ssl_server_name : "-")));
-    if (ctx->ssl_cert != NULL && ctx->ssl_key != NULL) {
-      char *ssl_cert = sl_pem2der(ctx->ssl_cert);
-      char *ssl_key = sl_pem2der(ctx->ssl_key);
-      if (ssl_cert != NULL && ssl_key != NULL) {
-        err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
-                            SL_SO_SECURE_FILES_CERTIFICATE_FILE_NAME, ssl_cert,
-                            strlen(ssl_cert));
-        LOG(LL_INFO, ("CERTIFICATE_FILE_NAME %s -> %d", ssl_cert, err));
+  if (ctx == NULL) return 0;
+  DBG(("%p %s,%s,%s,%s", nc, (ctx->ssl_cert ? ctx->ssl_cert : "-"),
+       (ctx->ssl_key ? ctx->ssl_cert : "-"),
+       (ctx->ssl_ca_cert ? ctx->ssl_ca_cert : "-"),
+       (ctx->ssl_server_name ? ctx->ssl_server_name : "-")));
+  if (ctx->ssl_cert != NULL && ctx->ssl_key != NULL) {
+    char *ssl_cert = sl_pem2der(ctx->ssl_cert), *ssl_key = NULL;
+    if (ssl_cert != NULL) {
+      err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
+                          SL_SO_SECURE_FILES_CERTIFICATE_FILE_NAME, ssl_cert,
+                          strlen(ssl_cert));
+      MG_FREE(ssl_cert);
+      LOG(LL_DEBUG, ("CERTIFICATE_FILE_NAME %s -> %d", ssl_cert, err));
+      ssl_key = sl_pem2der(ctx->ssl_key);
+      if (ssl_key != NULL) {
         err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
                             SL_SO_SECURE_FILES_PRIVATE_KEY_FILE_NAME, ssl_key,
                             strlen(ssl_key));
-        LOG(LL_INFO, ("PRIVATE_KEY_FILE_NAME %s -> %d", ssl_key, err));
+        MG_FREE(ssl_key);
+        LOG(LL_DEBUG, ("PRIVATE_KEY_FILE_NAME %s -> %d", ssl_key, err));
       } else {
         err = -1;
       }
-      MG_FREE(ssl_cert);
-      MG_FREE(ssl_key);
-      if (err != 0) return err;
-    }
-    if (ctx->ssl_ca_cert != NULL) {
-      if (ctx->ssl_ca_cert[0] != '\0') {
-        char *ssl_ca_cert = sl_pem2der(ctx->ssl_ca_cert);
-        if (ssl_ca_cert != NULL) {
-          err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
-                              SL_SO_SECURE_FILES_CA_FILE_NAME, ssl_ca_cert,
-                              strlen(ssl_ca_cert));
-          LOG(LL_INFO, ("CA_FILE_NAME %s -> %d", ssl_ca_cert, err));
-        } else {
-          err = -1;
-        }
-        MG_FREE(ssl_ca_cert);
-        if (err != 0) return err;
+    } else {
+      err = -1;
+    }
+    if (err != 0) return err;
+  }
+  if (ctx->ssl_ca_cert != NULL) {
+    if (ctx->ssl_ca_cert[0] != '\0') {
+      char *ssl_ca_cert = sl_pem2der(ctx->ssl_ca_cert);
+      if (ssl_ca_cert != NULL) {
+        err =
+            sl_SetSockOpt(sock, SL_SOL_SOCKET, SL_SO_SECURE_FILES_CA_FILE_NAME,
+                          ssl_ca_cert, strlen(ssl_ca_cert));
+        LOG(LL_DEBUG, ("CA_FILE_NAME %s -> %d", ssl_ca_cert, err));
+      } else {
+        err = -1;
       }
-    }
-    if (ctx->ssl_server_name != NULL) {
-      err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
-                          SL_SO_SECURE_DOMAIN_NAME_VERIFICATION,
-                          ctx->ssl_server_name, strlen(ctx->ssl_server_name));
-      DBG(("DOMAIN_NAME_VERIFICATION %s -> %d", ctx->ssl_server_name, err));
-      /* Domain name verificationw as added in a NWP service pack, older
-       * versions return SL_ERROR_BSD_ENOPROTOOPT. There isn't much we can do
-       * about it,
-       * so we ignore the error. */
-      if (err != 0 && err != SL_ERROR_BSD_ENOPROTOOPT) return err;
+      MG_FREE(ssl_ca_cert);
+      if (err != 0) return err;
     }
   }
+  if (ctx->ssl_server_name != NULL) {
+    err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
+                        SL_SO_SECURE_DOMAIN_NAME_VERIFICATION,
+                        ctx->ssl_server_name, strlen(ctx->ssl_server_name));
+    DBG(("DOMAIN_NAME_VERIFICATION %s -> %d", ctx->ssl_server_name, err));
+    /* Domain name verificationw as added in a NWP service pack, older
+     * versions return SL_ERROR_BSD_ENOPROTOOPT. There isn't much we can do
+     * about it,
+     * so we ignore the error. */
+    if (err != 0 && err != SL_ERROR_BSD_ENOPROTOOPT) return err;
+  }
   return 0;
 }
 
@@ -14462,9 +14477,9 @@ struct mg_lwip_conn_state {
   /* Last SSL write size, for retries. */
   int last_ssl_write_size;
   /* Whether MG_SIG_RECV is already pending for this connection */
-  int recv_pending : 1;
+  int recv_pending;
   /* Whether the connection is about to close, just `rx_chain` needs to drain */
-  int draining_rx_chain : 1;
+  int draining_rx_chain;
 };
 
 enum mg_sig_type {
@@ -14545,10 +14560,6 @@ void mg_lwip_mgr_schedule_poll(struct mg_mgr *mgr);
 typedef void (*tcpip_callback_fn)(void *arg);
 #endif
 
-void mg_lwip_ssl_do_hs(struct mg_connection *nc);
-void mg_lwip_ssl_send(struct mg_connection *nc);
-void mg_lwip_ssl_recv(struct mg_connection *nc);
-
 void mg_lwip_if_init(struct mg_iface *iface);
 void mg_lwip_if_free(struct mg_iface *iface);
 void mg_lwip_if_add_conn(struct mg_connection *nc);
@@ -14620,10 +14631,12 @@ static void mg_lwip_tcp_error_cb(void *arg, err_t err) {
 static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
                                  struct pbuf *p, err_t err) {
   struct mg_connection *nc = (struct mg_connection *) arg;
-  DBG(("%p %p %u %d", nc, tpcb, (p != NULL ? p->tot_len : 0), err));
+  struct mg_lwip_conn_state *cs =
+      (nc ? (struct mg_lwip_conn_state *) nc->sock : NULL);
+  DBG(("%p %p %p %p %u %d", nc, cs, tpcb, p, (p != NULL ? p->tot_len : 0),
+       err));
   if (p == NULL) {
     if (nc != NULL && !(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
-      struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
       if (cs->rx_chain != NULL) {
         /*
          * rx_chain still contains non-consumed data, don't close the
@@ -14641,7 +14654,6 @@ static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
     tcp_abort(tpcb);
     return ERR_ARG;
   }
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
   /*
    * If we get a chain of more than one segment at once, we need to bump
    * refcount on the subsequent bufs to make them independent.
@@ -14656,7 +14668,7 @@ static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
   } else if (pbuf_clen(cs->rx_chain) >= 4) {
     /* ESP SDK has a limited pool of 5 pbufs. We must not hog them all or RX
      * will be completely blocked. We already have at least 4 in the chain,
-     * this one is, so we have to make a copy and release this one. */
+     * this one is the last, so we have to make a copy and release this one. */
     struct pbuf *np = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
     if (np != NULL) {
       pbuf_copy(np, p);
@@ -14664,58 +14676,9 @@ static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
       p = np;
     }
   }
-  mgos_unlock();
   mg_lwip_recv_common(nc, p);
-  return ERR_OK;
-}
-
-static void mg_lwip_consume_rx_chain_tcp(struct mg_connection *nc) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  if (cs->rx_chain == NULL) return;
-#if MG_ENABLE_SSL
-  if (nc->flags & MG_F_SSL) {
-    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
-      mg_lwip_ssl_recv(nc);
-    } else {
-      mg_lwip_ssl_do_hs(nc);
-    }
-    return;
-  }
-#endif
-  mgos_lock();
-  while (cs->rx_chain != NULL && nc->recv_mbuf.len < nc->recv_mbuf_limit) {
-    struct pbuf *seg = cs->rx_chain;
-
-    size_t seg_len = (seg->len - cs->rx_offset);
-    size_t buf_avail = (nc->recv_mbuf_limit - nc->recv_mbuf.len);
-    size_t len = MIN(seg_len, buf_avail);
-
-    char *data = (char *) MG_MALLOC(len);
-    if (data == NULL) {
-      mgos_unlock();
-      DBG(("OOM"));
-      return;
-    }
-    pbuf_copy_partial(seg, data, len, cs->rx_offset);
-    cs->rx_offset += len;
-    if (cs->rx_offset == cs->rx_chain->len) {
-      cs->rx_chain = pbuf_dechain(cs->rx_chain);
-      pbuf_free(seg);
-      cs->rx_offset = 0;
-    }
-    mgos_unlock();
-    mg_if_recv_tcp_cb(nc, data, len, 1 /* own */);
-    mgos_lock();
-  }
   mgos_unlock();
-}
-
-static void mg_lwip_handle_recv_tcp(struct mg_connection *nc) {
-  mg_lwip_consume_rx_chain_tcp(nc);
-
-  if (nc->send_mbuf.len > 0) {
-    mg_lwip_mgr_schedule_poll(nc->mgr);
-  }
+  return ERR_OK;
 }
 
 static err_t mg_lwip_tcp_sent_cb(void *arg, struct tcp_pcb *tpcb,
@@ -14801,13 +14764,14 @@ static void mg_lwip_udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p,
   /* Logic in the recv handler requires that there be exactly one data pbuf. */
   p = pbuf_coalesce(p, PBUF_RAW);
   pbuf_chain(sap, p);
+  mgos_lock();
   mg_lwip_recv_common(nc, sap);
+  mgos_unlock();
   (void) pcb;
 }
 
 static void mg_lwip_recv_common(struct mg_connection *nc, struct pbuf *p) {
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  mgos_lock();
   if (cs->rx_chain == NULL) {
     cs->rx_chain = p;
   } else {
@@ -14817,32 +14781,30 @@ static void mg_lwip_recv_common(struct mg_connection *nc, struct pbuf *p) {
     cs->recv_pending = 1;
     mg_lwip_post_signal(MG_SIG_RECV, nc);
   }
-  mgos_unlock();
 }
 
-static void mg_lwip_handle_recv_udp(struct mg_connection *nc) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+static int mg_lwip_if_udp_recv(struct mg_connection *nc, void *buf, size_t len,
+                               union socket_address *sa, size_t *sa_len) {
   /*
    * For UDP, RX chain consists of interleaved address and packet bufs:
    * Address pbuf followed by exactly one data pbuf (recv_cb took care of that).
    */
-  while (cs->rx_chain != NULL) {
-    struct pbuf *sap = cs->rx_chain;
-    struct pbuf *p = sap->next;
-    cs->rx_chain = pbuf_dechain(p);
-    size_t data_len = p->len;
-    char *data = (char *) MG_MALLOC(data_len);
-    if (data != NULL) {
-      pbuf_copy_partial(p, data, data_len, 0);
-      pbuf_free(p);
-      mg_if_recv_udp_cb(nc, data, data_len,
-                        (union socket_address *) sap->payload, sap->len);
-      pbuf_free(sap);
-    } else {
-      pbuf_free(p);
-      pbuf_free(sap);
-    }
+  int res = 0;
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  if (nc->sock == INVALID_SOCKET) return -1;
+  mgos_lock();
+  if (cs->rx_chain != NULL) {
+    struct pbuf *ap = cs->rx_chain;
+    struct pbuf *dp = ap->next;
+    cs->rx_chain = pbuf_dechain(dp);
+    res = MIN(dp->len, len);
+    pbuf_copy_partial(dp, buf, res, 0);
+    pbuf_free(dp);
+    pbuf_copy_partial(ap, sa, MIN(*sa_len, ap->len), 0);
+    pbuf_free(ap);
   }
+  mgos_unlock();
+  return res;
 }
 
 static void mg_lwip_if_connect_udp_tcpip(void *arg) {
@@ -14864,13 +14826,6 @@ void mg_lwip_if_connect_udp(struct mg_connection *nc) {
   tcpip_callback(mg_lwip_if_connect_udp_tcpip, nc);
 }
 
-void mg_lwip_accept_conn(struct mg_connection *nc, struct tcp_pcb *tpcb) {
-  union socket_address sa;
-  SET_ADDR(&sa, &tpcb->remote_ip);
-  sa.sin.sin_port = htons(tpcb->remote_port);
-  mg_if_accept_tcp_cb(nc, &sa, sizeof(sa.sin));
-}
-
 static void tcp_close_tcpip(void *arg) {
   tcp_close((struct tcp_pcb *) arg);
 }
@@ -14878,17 +14833,11 @@ static void tcp_close_tcpip(void *arg) {
 void mg_lwip_handle_accept(struct mg_connection *nc) {
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
   if (cs->pcb.tcp == NULL) return;
-#if MG_ENABLE_SSL
-  if (cs->lc->flags & MG_F_SSL) {
-    if (mg_ssl_if_conn_accept(nc, cs->lc) != MG_SSL_OK) {
-      LOG(LL_ERROR, ("SSL error"));
-      tcpip_callback(tcp_close_tcpip, cs->pcb.tcp);
-    }
-  } else
-#endif
-  {
-    mg_lwip_accept_conn(nc, cs->pcb.tcp);
-  }
+  union socket_address sa;
+  struct tcp_pcb *tpcb = cs->pcb.tcp;
+  SET_ADDR(&sa, &tpcb->remote_ip);
+  sa.sin.sin_port = htons(tpcb->remote_port);
+  mg_if_accept_tcp_cb(nc, &sa, sizeof(sa.sin));
 }
 
 static err_t mg_lwip_accept_cb(void *arg, struct tcp_pcb *newtpcb, err_t err) {
@@ -15045,14 +14994,13 @@ static void mg_lwip_tcp_write_tcpip(void *arg) {
   ctx->ret = len;
 }
 
-static int mg_lwip_tcp_write(struct mg_connection *nc, const void *data,
-                             uint16_t len) {
-  struct mg_lwip_tcp_write_ctx ctx = {.nc = nc, .data = data, .len = len};
+int mg_lwip_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) {
+  struct mg_lwip_tcp_write_ctx ctx = {.nc = nc, .data = buf, .len = len};
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  if (nc->sock == INVALID_SOCKET) return -1;
   struct tcp_pcb *tpcb = cs->pcb.tcp;
-  if (tpcb == NULL) {
-    return -1;
-  }
+  if (tpcb == NULL) return -1;
+  if (tpcb->snd_buf <= 0) return 0;
   tcpip_callback(mg_lwip_tcp_write_tcpip, &ctx);
   return ctx.ret;
 }
@@ -15070,18 +15018,10 @@ static void udp_sendto_tcpip(void *arg) {
   ctx->ret = udp_sendto(ctx->upcb, ctx->p, ctx->ip, ctx->port);
 }
 
-static int mg_lwip_udp_send(struct mg_connection *nc, const void *data,
-                            uint16_t len) {
+static int mg_lwip_if_udp_send(struct mg_connection *nc, const void *data,
+                               size_t len) {
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  if (cs->pcb.udp == NULL) {
-    /*
-     * In case of UDP, this usually means, what
-     * async DNS resolve is still in progress and connection
-     * is not ready yet
-     */
-    DBG(("%p socket is not connected", nc));
-    return -1;
-  }
+  if (nc->sock == INVALID_SOCKET || cs->pcb.udp == NULL) return -1;
   struct udp_pcb *upcb = cs->pcb.udp;
   struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
 #if defined(LWIP_IPV4) && LWIP_IPV4 && defined(LWIP_IPV6) && LWIP_IPV6
@@ -15090,46 +15030,28 @@ static int mg_lwip_udp_send(struct mg_connection *nc, const void *data,
   ip_addr_t ip = {.addr = nc->sa.sin.sin_addr.s_addr};
 #endif
   u16_t port = ntohs(nc->sa.sin.sin_port);
-  if (p == NULL) {
-    DBG(("OOM"));
-    return 0;
-  }
+  if (p == NULL) return 0;
   memcpy(p->payload, data, len);
   struct udp_sendto_ctx ctx = {.upcb = upcb, .p = p, .ip = &ip, .port = port};
   tcpip_callback(udp_sendto_tcpip, &ctx);
   cs->err = ctx.ret;
   pbuf_free(p);
-  return (cs->err == ERR_OK ? len : -1);
+  return (cs->err == ERR_OK ? (int) len : -2);
 }
 
-static void mg_lwip_send_more(struct mg_connection *nc) {
-  int num_sent = 0;
-  if (nc->sock == INVALID_SOCKET) return;
-  if (nc->flags & MG_F_UDP) {
-    num_sent = mg_lwip_udp_send(nc, nc->send_mbuf.buf, nc->send_mbuf.len);
-    DBG(("%p mg_lwip_udp_send %u = %d", nc, nc->send_mbuf.len, num_sent));
-  } else {
-    num_sent = mg_lwip_tcp_write(nc, nc->send_mbuf.buf, nc->send_mbuf.len);
-    DBG(("%p mg_lwip_tcp_write %u = %d", nc, nc->send_mbuf.len, num_sent));
-  }
-  if (num_sent == 0) return;
-  if (num_sent > 0) {
-    mg_if_sent_cb(nc, num_sent);
-  } else {
-    mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
+static int mg_lwip_if_can_send(struct mg_connection *nc,
+                               struct mg_lwip_conn_state *cs) {
+  int can_send = 0;
+  if (nc->send_mbuf.len > 0 || (nc->flags & MG_F_WANT_WRITE)) {
+    /* We have stuff to send, but can we? */
+    if (nc->flags & MG_F_UDP) {
+      /* UDP is always ready for sending. */
+      can_send = (cs->pcb.udp != NULL);
+    } else {
+      can_send = (cs->pcb.tcp != NULL && cs->pcb.tcp->snd_buf > 0);
+    }
   }
-}
-
-void mg_lwip_if_tcp_send(struct mg_connection *nc, const void *buf,
-                         size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
-  mg_lwip_mgr_schedule_poll(nc->mgr);
-}
-
-void mg_lwip_if_udp_send(struct mg_connection *nc, const void *buf,
-                         size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
-  mg_lwip_mgr_schedule_poll(nc->mgr);
+  return can_send;
 }
 
 struct tcp_recved_ctx {
@@ -15142,27 +15064,35 @@ void tcp_recved_tcpip(void *arg) {
   tcp_recved(ctx->tpcb, ctx->len);
 }
 
-void mg_lwip_if_recved(struct mg_connection *nc, size_t len) {
-  if (nc->flags & MG_F_UDP) return;
+static int mg_lwip_if_tcp_recv(struct mg_connection *nc, void *buf,
+                               size_t len) {
+  int res = 0;
+  char *bufp = buf;
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  if (nc->sock == INVALID_SOCKET || cs->pcb.tcp == NULL) {
-    DBG(("%p invalid socket", nc));
-    return;
+  if (nc->sock == INVALID_SOCKET) return -1;
+  mgos_lock();
+  while (cs->rx_chain != NULL && len > 0) {
+    struct pbuf *seg = cs->rx_chain;
+    size_t seg_len = (seg->len - cs->rx_offset);
+    size_t copy_len = MIN(len, seg_len);
+
+    pbuf_copy_partial(seg, bufp, copy_len, cs->rx_offset);
+    len -= copy_len;
+    res += copy_len;
+    bufp += copy_len;
+    cs->rx_offset += copy_len;
+    if (cs->rx_offset == cs->rx_chain->len) {
+      cs->rx_chain = pbuf_dechain(cs->rx_chain);
+      pbuf_free(seg);
+      cs->rx_offset = 0;
+    }
   }
-  DBG(("%p %p %u %u", nc, cs->pcb.tcp, len,
-       (cs->rx_chain ? cs->rx_chain->tot_len : 0)));
-  struct tcp_recved_ctx ctx = {.tpcb = cs->pcb.tcp, .len = len};
-#if MG_ENABLE_SSL
-  if (!(nc->flags & MG_F_SSL)) {
+  mgos_unlock();
+  if (res > 0) {
+    struct tcp_recved_ctx ctx = {.tpcb = cs->pcb.tcp, .len = res};
     tcpip_callback(tcp_recved_tcpip, &ctx);
-  } else {
-    /* Currently SSL acknowledges data immediately.
-     * TODO(rojer): Find a way to propagate mg_lwip_if_recved. */
   }
-#else
-  tcpip_callback(tcp_recved_tcpip, &ctx);
-#endif
-  mbuf_trim(&nc->recv_mbuf);
+  return res;
 }
 
 int mg_lwip_if_create_conn(struct mg_connection *nc) {
@@ -15251,7 +15181,8 @@ void mg_lwip_if_sock_set(struct mg_connection *nc, sock_t sock) {
     mg_lwip_if_connect_udp,                                           \
     mg_lwip_if_tcp_send,                                              \
     mg_lwip_if_udp_send,                                              \
-    mg_lwip_if_recved,                                                \
+    mg_lwip_if_tcp_recv,                                              \
+    mg_lwip_if_udp_recv,                                              \
     mg_lwip_if_create_conn,                                           \
     mg_lwip_if_destroy_conn,                                          \
     mg_lwip_if_sock_set,                                              \
@@ -15320,15 +15251,7 @@ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {
     if (nc->iface == NULL || nc->mgr == NULL) continue;
     switch (sig) {
       case MG_SIG_CONNECT_RESULT: {
-#if MG_ENABLE_SSL
-        if (cs->err == 0 && (nc->flags & MG_F_SSL) &&
-            !(nc->flags & MG_F_SSL_HANDSHAKE_DONE)) {
-          mg_lwip_ssl_do_hs(nc);
-        } else
-#endif
-        {
-          mg_if_connect_cb(nc, cs->err);
-        }
+        mg_if_connect_cb(nc, cs->err);
         break;
       }
       case MG_SIG_CLOSE_CONN: {
@@ -15338,11 +15261,8 @@ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {
       }
       case MG_SIG_RECV: {
         cs->recv_pending = 0;
-        if (nc->flags & MG_F_UDP) {
-          mg_lwip_handle_recv_udp(nc);
-        } else {
-          mg_lwip_handle_recv_tcp(nc);
-        }
+        mg_if_can_recv_cb(nc);
+        mbuf_trim(&nc->recv_mbuf);
         break;
       }
       case MG_SIG_TOMBSTONE: {
@@ -15357,7 +15277,8 @@ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {
 }
 
 void mg_lwip_if_init(struct mg_iface *iface) {
-  LOG(LL_INFO, ("%p Mongoose init", iface));
+  LOG(LL_INFO, ("Mongoose %s, LwIP %u.%u.%u", MG_VERSION, LWIP_VERSION_MAJOR,
+                LWIP_VERSION_MINOR, LWIP_VERSION_REVISION));
   iface->data = MG_CALLOC(1, sizeof(struct mg_ev_mgr_lwip_data));
 }
 
@@ -15396,42 +15317,7 @@ time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) {
     struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
     tmp = nc->next;
     n++;
-    if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
-        ((nc->flags & MG_F_SEND_AND_CLOSE) && (nc->flags & MG_F_UDP) &&
-         (nc->send_mbuf.len == 0))) {
-      mg_close_conn(nc);
-      continue;
-    }
-    mg_if_poll(nc, now);
-    mg_if_timer(nc, now);
-#if MG_ENABLE_SSL
-    if ((nc->flags & MG_F_SSL) && cs != NULL && cs->pcb.tcp != NULL &&
-        cs->pcb.tcp->state == ESTABLISHED) {
-      if (((nc->flags & MG_F_WANT_WRITE) ||
-           ((nc->send_mbuf.len > 0) &&
-            (nc->flags & MG_F_SSL_HANDSHAKE_DONE))) &&
-          cs->pcb.tcp->snd_buf > 0) {
-        /* Can write more. */
-        if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
-          if (!(nc->flags & MG_F_CONNECTING)) mg_lwip_ssl_send(nc);
-        } else {
-          mg_lwip_ssl_do_hs(nc);
-        }
-      }
-      if (cs->rx_chain != NULL || (nc->flags & MG_F_WANT_READ)) {
-        if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
-          if (!(nc->flags & MG_F_CONNECTING)) mg_lwip_ssl_recv(nc);
-        } else {
-          mg_lwip_ssl_do_hs(nc);
-        }
-      }
-    } else
-#endif /* MG_ENABLE_SSL */
-    {
-      if (nc->send_mbuf.len > 0 && !(nc->flags & MG_F_CONNECTING)) {
-        mg_lwip_send_more(nc);
-      }
-    }
+    if (!mg_if_poll(nc, now)) continue;
     if (nc->sock != INVALID_SOCKET &&
         !(nc->flags & (MG_F_UDP | MG_F_LISTENING)) && cs->pcb.tcp != NULL &&
         cs->pcb.tcp->unsent != NULL) {
@@ -15445,14 +15331,17 @@ time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) {
     }
 
     if (nc->sock != INVALID_SOCKET) {
-      /* Try to consume data from cs->rx_chain */
-      mg_lwip_consume_rx_chain_tcp(nc);
-
-      /*
-       * If the connection is about to close, and rx_chain is finally empty,
-       * send the MG_SIG_CLOSE_CONN signal
-       */
-      if (cs->draining_rx_chain && cs->rx_chain == NULL) {
+      if (mg_lwip_if_can_send(nc, cs)) {
+        mg_if_can_send_cb(nc);
+        mbuf_trim(&nc->send_mbuf);
+      }
+      if (cs->rx_chain != NULL) {
+        mg_if_can_recv_cb(nc);
+      } else if (cs->draining_rx_chain) {
+        /*
+         * If the connection is about to close, and rx_chain is finally empty,
+         * send the MG_SIG_CLOSE_CONN signal
+         */
         mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
       }
     }
@@ -15480,21 +15369,9 @@ uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr) {
       }
       num_timers++;
     }
-    if (nc->send_mbuf.len > 0
-#if MG_ENABLE_SSL
-        || (nc->flags & MG_F_WANT_WRITE)
-#endif
-            ) {
-      int can_send = 0;
-      /* We have stuff to send, but can we? */
-      if (nc->flags & MG_F_UDP) {
-        /* UDP is always ready for sending. */
-        can_send = (cs->pcb.udp != NULL);
-      } else {
-        can_send = (cs->pcb.tcp != NULL && cs->pcb.tcp->snd_buf > 0);
-      }
-      /* We want and can send, request a poll immediately. */
-      if (can_send) return 0;
+    /* We want and can send data, request a poll immediately. */
+    if (nc->sock != INVALID_SOCKET && mg_lwip_if_can_send(nc, cs)) {
+      return 0;
     }
   }
   uint32_t timeout_ms = ~0;
@@ -15512,206 +15389,6 @@ uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr) {
 
 #endif /* MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL */
 #ifdef MG_MODULE_LINES
-#line 1 "common/platforms/lwip/mg_lwip_ssl_if.c"
-#endif
-/*
- * Copyright (c) 2014-2016 Cesanta Software Limited
- * All rights reserved
- */
-
-#if MG_ENABLE_SSL && MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL
-
-/* Amalgamated: #include "common/mg_mem.h" */
-/* Amalgamated: #include "common/cs_dbg.h" */
-
-#include <lwip/pbuf.h>
-#include <lwip/tcp.h>
-
-#ifndef MG_LWIP_SSL_IO_SIZE
-#define MG_LWIP_SSL_IO_SIZE 1024
-#endif
-
-#ifndef MIN
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-#endif
-
-void mg_lwip_ssl_do_hs(struct mg_connection *nc) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  int server_side = (nc->listener != NULL);
-  enum mg_ssl_if_result res;
-  if (nc->flags & MG_F_CLOSE_IMMEDIATELY) return;
-  res = mg_ssl_if_handshake(nc);
-  DBG(("%p %lu %d %d", nc, nc->flags, server_side, res));
-  if (res != MG_SSL_OK) {
-    if (res == MG_SSL_WANT_WRITE) {
-      nc->flags |= MG_F_WANT_WRITE;
-      cs->err = 0;
-    } else if (res == MG_SSL_WANT_READ) {
-      /*
-       * Nothing to do in particular, we are callback-driven.
-       * What we definitely do not need anymore is SSL reading (nothing left).
-       */
-      nc->flags &= ~MG_F_WANT_READ;
-      cs->err = 0;
-    } else {
-      cs->err = res;
-      if (server_side) {
-        mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
-      } else {
-        mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);
-      }
-    }
-  } else {
-    cs->err = 0;
-    nc->flags &= ~MG_F_WANT_WRITE;
-    /*
-     * Handshake is done. Schedule a read immediately to consume app data
-     * which may already be waiting.
-     */
-    nc->flags |= (MG_F_SSL_HANDSHAKE_DONE | MG_F_WANT_READ);
-    if (server_side) {
-      mg_lwip_accept_conn(nc, cs->pcb.tcp);
-    } else {
-      mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);
-    }
-  }
-}
-
-void mg_lwip_ssl_send(struct mg_connection *nc) {
-  if (nc->sock == INVALID_SOCKET) {
-    DBG(("%p invalid socket", nc));
-    return;
-  }
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  /* It's ok if the buffer is empty. Return value of 0 may also be valid. */
-  int len = cs->last_ssl_write_size;
-  if (len == 0) {
-    len = MIN(MG_LWIP_SSL_IO_SIZE, nc->send_mbuf.len);
-  }
-  int ret = mg_ssl_if_write(nc, nc->send_mbuf.buf, len);
-  DBG(("%p SSL_write %u = %d", nc, len, ret));
-  if (ret > 0) {
-    mg_if_sent_cb(nc, ret);
-    cs->last_ssl_write_size = 0;
-  } else if (ret < 0) {
-    /* This is tricky. We must remember the exact data we were sending to retry
-     * exactly the same send next time. */
-    cs->last_ssl_write_size = len;
-  }
-  if (ret == len) {
-    nc->flags &= ~MG_F_WANT_WRITE;
-  } else if (ret == MG_SSL_WANT_WRITE) {
-    nc->flags |= MG_F_WANT_WRITE;
-  } else {
-    mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
-  }
-}
-
-void mg_lwip_ssl_recv(struct mg_connection *nc) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  /* Don't deliver data before connect callback */
-  if (nc->flags & MG_F_CONNECTING) return;
-  while (nc->recv_mbuf.len < nc->recv_mbuf_limit) {
-    char *buf = (char *) MG_MALLOC(MG_LWIP_SSL_IO_SIZE);
-    if (buf == NULL) return;
-    int ret = mg_ssl_if_read(nc, buf, MG_LWIP_SSL_IO_SIZE);
-    DBG(("%p %p SSL_read %u = %d", nc, cs->rx_chain, MG_LWIP_SSL_IO_SIZE, ret));
-    if (ret <= 0) {
-      MG_FREE(buf);
-      if (ret == MG_SSL_WANT_WRITE) {
-        nc->flags |= MG_F_WANT_WRITE;
-        return;
-      } else if (ret == MG_SSL_WANT_READ) {
-        /*
-         * Nothing to do in particular, we are callback-driven.
-         * What we definitely do not need anymore is SSL reading (nothing left).
-         */
-        nc->flags &= ~MG_F_WANT_READ;
-        cs->err = 0;
-        return;
-      } else {
-        mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
-        return;
-      }
-    } else {
-      mg_if_recv_tcp_cb(nc, buf, ret, 1 /* own */);
-    }
-  }
-}
-
-#ifdef KR_VERSION
-
-ssize_t kr_send(int fd, const void *buf, size_t len) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) fd;
-  int ret = mg_lwip_tcp_write(cs->nc, buf, len);
-  DBG(("%p mg_lwip_tcp_write %u = %d", cs->nc, len, ret));
-  if (ret == 0) ret = KR_IO_WOULDBLOCK;
-  return ret;
-}
-
-ssize_t kr_recv(int fd, void *buf, size_t len) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) fd;
-  struct pbuf *seg = cs->rx_chain;
-  if (seg == NULL) {
-    DBG(("%u - nothing to read", len));
-    return KR_IO_WOULDBLOCK;
-  }
-  size_t seg_len = (seg->len - cs->rx_offset);
-  DBG(("%u %u %u %u", len, cs->rx_chain->len, seg_len, cs->rx_chain->tot_len));
-  len = MIN(len, seg_len);
-  pbuf_copy_partial(seg, buf, len, cs->rx_offset);
-  cs->rx_offset += len;
-  tcp_recved(cs->pcb.tcp, len);
-  if (cs->rx_offset == cs->rx_chain->len) {
-    cs->rx_chain = pbuf_dechain(cs->rx_chain);
-    pbuf_free(seg);
-    cs->rx_offset = 0;
-  }
-  return len;
-}
-
-#elif MG_SSL_IF == MG_SSL_IF_MBEDTLS
-
-int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len) {
-  struct mg_connection *nc = (struct mg_connection *) ctx;
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  int ret = mg_lwip_tcp_write(cs->nc, buf, len);
-  if (ret == 0) ret = MBEDTLS_ERR_SSL_WANT_WRITE;
-  LOG(LL_DEBUG, ("%p %d -> %d", nc, len, ret));
-  return ret;
-}
-
-int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len) {
-  struct mg_connection *nc = (struct mg_connection *) ctx;
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  struct pbuf *seg = cs->rx_chain;
-  if (seg == NULL) {
-    DBG(("%u - nothing to read", len));
-    return MBEDTLS_ERR_SSL_WANT_READ;
-  }
-  size_t seg_len = (seg->len - cs->rx_offset);
-  DBG(("%u %u %u %u", len, cs->rx_chain->len, seg_len, cs->rx_chain->tot_len));
-  mgos_lock();
-  len = MIN(len, seg_len);
-  pbuf_copy_partial(seg, buf, len, cs->rx_offset);
-  cs->rx_offset += len;
-  /* TCP PCB may be NULL if connection has already been closed
-   * but we still have data to deliver to SSL. */
-  if (cs->pcb.tcp != NULL) tcp_recved(cs->pcb.tcp, len);
-  if (cs->rx_offset == cs->rx_chain->len) {
-    cs->rx_chain = pbuf_dechain(cs->rx_chain);
-    pbuf_free(seg);
-    cs->rx_offset = 0;
-  }
-  mgos_unlock();
-  LOG(LL_DEBUG, ("%p <- %d", nc, (int) len));
-  return len;
-}
-
-#endif
-
-#endif /* MG_ENABLE_SSL && MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL */
-#ifdef MG_MODULE_LINES
 #line 1 "common/platforms/wince/wince_libc.c"
 #endif
 /*
diff --git a/mongoose.h b/mongoose.h
index c668594a5..e9b1fb171 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -3245,10 +3245,12 @@ struct mg_iface_vtable {
   void (*connect_udp)(struct mg_connection *nc);
 
   /* Send functions for TCP and UDP. Sent data is copied before return. */
-  void (*tcp_send)(struct mg_connection *nc, const void *buf, size_t len);
-  void (*udp_send)(struct mg_connection *nc, const void *buf, size_t len);
+  int (*tcp_send)(struct mg_connection *nc, const void *buf, size_t len);
+  int (*udp_send)(struct mg_connection *nc, const void *buf, size_t len);
 
-  void (*recved)(struct mg_connection *nc, size_t len);
+  int (*tcp_recv)(struct mg_connection *nc, void *buf, size_t len);
+  int (*udp_recv)(struct mg_connection *nc, void *buf, size_t len,
+                  union socket_address *sa, size_t *sa_len);
 
   /* Perform interface-related connection initialization. Return 1 on ok. */
   int (*create_conn)(struct mg_connection *nc);
@@ -3289,19 +3291,15 @@ void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa,
 
 /* Callback invoked by connect methods. err = 0 -> ok, != 0 -> error. */
 void mg_if_connect_cb(struct mg_connection *nc, int err);
-/* Callback that reports that data has been put on the wire. */
-void mg_if_sent_cb(struct mg_connection *nc, int num_sent);
 /*
- * Receive callback.
- * if `own` is true, buf must be heap-allocated and ownership is transferred
- * to the core.
- * Core will acknowledge consumption by calling iface::recved.
+ * Callback that tells the core that data can be received.
+ * Core will use tcp/udp_recv to retrieve the data.
  */
-void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len, int own);
+void mg_if_can_recv_cb(struct mg_connection *nc);
+void mg_if_can_send_cb(struct mg_connection *nc);
 /*
  * Receive callback.
  * buf must be heap-allocated and ownership is transferred to the core.
- * Core will acknowledge consumption by calling iface::recved.
  */
 void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
                        union socket_address *sa, size_t sa_len);
@@ -3309,10 +3307,7 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
 /* void mg_if_close_conn(struct mg_connection *nc); */
 
 /* Deliver a POLL event to the connection. */
-void mg_if_poll(struct mg_connection *nc, time_t now);
-
-/* Deliver a TIMER event to the connection. */
-void mg_if_timer(struct mg_connection *c, double now);
+int mg_if_poll(struct mg_connection *nc, double now);
 
 #ifdef __cplusplus
 }
diff --git a/src/common/platforms/lwip/mg_lwip_ev_mgr.c b/src/common/platforms/lwip/mg_lwip_ev_mgr.c
index 5eea26f3c..11a1bfc18 100644
--- a/src/common/platforms/lwip/mg_lwip_ev_mgr.c
+++ b/src/common/platforms/lwip/mg_lwip_ev_mgr.c
@@ -50,15 +50,7 @@ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {
     if (nc->iface == NULL || nc->mgr == NULL) continue;
     switch (sig) {
       case MG_SIG_CONNECT_RESULT: {
-#if MG_ENABLE_SSL
-        if (cs->err == 0 && (nc->flags & MG_F_SSL) &&
-            !(nc->flags & MG_F_SSL_HANDSHAKE_DONE)) {
-          mg_lwip_ssl_do_hs(nc);
-        } else
-#endif
-        {
-          mg_if_connect_cb(nc, cs->err);
-        }
+        mg_if_connect_cb(nc, cs->err);
         break;
       }
       case MG_SIG_CLOSE_CONN: {
@@ -68,11 +60,8 @@ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {
       }
       case MG_SIG_RECV: {
         cs->recv_pending = 0;
-        if (nc->flags & MG_F_UDP) {
-          mg_lwip_handle_recv_udp(nc);
-        } else {
-          mg_lwip_handle_recv_tcp(nc);
-        }
+        mg_if_can_recv_cb(nc);
+        mbuf_trim(&nc->recv_mbuf);
         break;
       }
       case MG_SIG_TOMBSTONE: {
@@ -87,7 +76,8 @@ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {
 }
 
 void mg_lwip_if_init(struct mg_iface *iface) {
-  LOG(LL_INFO, ("%p Mongoose init", iface));
+  LOG(LL_INFO, ("Mongoose %s, LwIP %u.%u.%u", MG_VERSION, LWIP_VERSION_MAJOR,
+                LWIP_VERSION_MINOR, LWIP_VERSION_REVISION));
   iface->data = MG_CALLOC(1, sizeof(struct mg_ev_mgr_lwip_data));
 }
 
@@ -126,42 +116,7 @@ time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) {
     struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
     tmp = nc->next;
     n++;
-    if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
-        ((nc->flags & MG_F_SEND_AND_CLOSE) && (nc->flags & MG_F_UDP) &&
-         (nc->send_mbuf.len == 0))) {
-      mg_close_conn(nc);
-      continue;
-    }
-    mg_if_poll(nc, now);
-    mg_if_timer(nc, now);
-#if MG_ENABLE_SSL
-    if ((nc->flags & MG_F_SSL) && cs != NULL && cs->pcb.tcp != NULL &&
-        cs->pcb.tcp->state == ESTABLISHED) {
-      if (((nc->flags & MG_F_WANT_WRITE) ||
-           ((nc->send_mbuf.len > 0) &&
-            (nc->flags & MG_F_SSL_HANDSHAKE_DONE))) &&
-          cs->pcb.tcp->snd_buf > 0) {
-        /* Can write more. */
-        if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
-          if (!(nc->flags & MG_F_CONNECTING)) mg_lwip_ssl_send(nc);
-        } else {
-          mg_lwip_ssl_do_hs(nc);
-        }
-      }
-      if (cs->rx_chain != NULL || (nc->flags & MG_F_WANT_READ)) {
-        if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
-          if (!(nc->flags & MG_F_CONNECTING)) mg_lwip_ssl_recv(nc);
-        } else {
-          mg_lwip_ssl_do_hs(nc);
-        }
-      }
-    } else
-#endif /* MG_ENABLE_SSL */
-    {
-      if (nc->send_mbuf.len > 0 && !(nc->flags & MG_F_CONNECTING)) {
-        mg_lwip_send_more(nc);
-      }
-    }
+    if (!mg_if_poll(nc, now)) continue;
     if (nc->sock != INVALID_SOCKET &&
         !(nc->flags & (MG_F_UDP | MG_F_LISTENING)) && cs->pcb.tcp != NULL &&
         cs->pcb.tcp->unsent != NULL) {
@@ -175,14 +130,17 @@ time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) {
     }
 
     if (nc->sock != INVALID_SOCKET) {
-      /* Try to consume data from cs->rx_chain */
-      mg_lwip_consume_rx_chain_tcp(nc);
-
-      /*
-       * If the connection is about to close, and rx_chain is finally empty,
-       * send the MG_SIG_CLOSE_CONN signal
-       */
-      if (cs->draining_rx_chain && cs->rx_chain == NULL) {
+      if (mg_lwip_if_can_send(nc, cs)) {
+        mg_if_can_send_cb(nc);
+        mbuf_trim(&nc->send_mbuf);
+      }
+      if (cs->rx_chain != NULL) {
+        mg_if_can_recv_cb(nc);
+      } else if (cs->draining_rx_chain) {
+        /*
+         * If the connection is about to close, and rx_chain is finally empty,
+         * send the MG_SIG_CLOSE_CONN signal
+         */
         mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
       }
     }
@@ -210,21 +168,9 @@ uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr) {
       }
       num_timers++;
     }
-    if (nc->send_mbuf.len > 0
-#if MG_ENABLE_SSL
-        || (nc->flags & MG_F_WANT_WRITE)
-#endif
-            ) {
-      int can_send = 0;
-      /* We have stuff to send, but can we? */
-      if (nc->flags & MG_F_UDP) {
-        /* UDP is always ready for sending. */
-        can_send = (cs->pcb.udp != NULL);
-      } else {
-        can_send = (cs->pcb.tcp != NULL && cs->pcb.tcp->snd_buf > 0);
-      }
-      /* We want and can send, request a poll immediately. */
-      if (can_send) return 0;
+    /* We want and can send data, request a poll immediately. */
+    if (nc->sock != INVALID_SOCKET && mg_lwip_if_can_send(nc, cs)) {
+      return 0;
     }
   }
   uint32_t timeout_ms = ~0;
diff --git a/src/common/platforms/lwip/mg_lwip_net_if.c b/src/common/platforms/lwip/mg_lwip_net_if.c
index 460f8f5af..b9042e837 100644
--- a/src/common/platforms/lwip/mg_lwip_net_if.c
+++ b/src/common/platforms/lwip/mg_lwip_net_if.c
@@ -57,10 +57,6 @@
 typedef void (*tcpip_callback_fn)(void *arg);
 #endif
 
-void mg_lwip_ssl_do_hs(struct mg_connection *nc);
-void mg_lwip_ssl_send(struct mg_connection *nc);
-void mg_lwip_ssl_recv(struct mg_connection *nc);
-
 void mg_lwip_if_init(struct mg_iface *iface);
 void mg_lwip_if_free(struct mg_iface *iface);
 void mg_lwip_if_add_conn(struct mg_connection *nc);
@@ -132,10 +128,12 @@ static void mg_lwip_tcp_error_cb(void *arg, err_t err) {
 static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
                                  struct pbuf *p, err_t err) {
   struct mg_connection *nc = (struct mg_connection *) arg;
-  DBG(("%p %p %u %d", nc, tpcb, (p != NULL ? p->tot_len : 0), err));
+  struct mg_lwip_conn_state *cs =
+      (nc ? (struct mg_lwip_conn_state *) nc->sock : NULL);
+  DBG(("%p %p %p %p %u %d", nc, cs, tpcb, p, (p != NULL ? p->tot_len : 0),
+       err));
   if (p == NULL) {
     if (nc != NULL && !(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
-      struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
       if (cs->rx_chain != NULL) {
         /*
          * rx_chain still contains non-consumed data, don't close the
@@ -153,7 +151,6 @@ static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
     tcp_abort(tpcb);
     return ERR_ARG;
   }
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
   /*
    * If we get a chain of more than one segment at once, we need to bump
    * refcount on the subsequent bufs to make them independent.
@@ -168,7 +165,7 @@ static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
   } else if (pbuf_clen(cs->rx_chain) >= 4) {
     /* ESP SDK has a limited pool of 5 pbufs. We must not hog them all or RX
      * will be completely blocked. We already have at least 4 in the chain,
-     * this one is, so we have to make a copy and release this one. */
+     * this one is the last, so we have to make a copy and release this one. */
     struct pbuf *np = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
     if (np != NULL) {
       pbuf_copy(np, p);
@@ -176,58 +173,9 @@ static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
       p = np;
     }
   }
-  mgos_unlock();
   mg_lwip_recv_common(nc, p);
-  return ERR_OK;
-}
-
-static void mg_lwip_consume_rx_chain_tcp(struct mg_connection *nc) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  if (cs->rx_chain == NULL) return;
-#if MG_ENABLE_SSL
-  if (nc->flags & MG_F_SSL) {
-    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
-      mg_lwip_ssl_recv(nc);
-    } else {
-      mg_lwip_ssl_do_hs(nc);
-    }
-    return;
-  }
-#endif
-  mgos_lock();
-  while (cs->rx_chain != NULL && nc->recv_mbuf.len < nc->recv_mbuf_limit) {
-    struct pbuf *seg = cs->rx_chain;
-
-    size_t seg_len = (seg->len - cs->rx_offset);
-    size_t buf_avail = (nc->recv_mbuf_limit - nc->recv_mbuf.len);
-    size_t len = MIN(seg_len, buf_avail);
-
-    char *data = (char *) MG_MALLOC(len);
-    if (data == NULL) {
-      mgos_unlock();
-      DBG(("OOM"));
-      return;
-    }
-    pbuf_copy_partial(seg, data, len, cs->rx_offset);
-    cs->rx_offset += len;
-    if (cs->rx_offset == cs->rx_chain->len) {
-      cs->rx_chain = pbuf_dechain(cs->rx_chain);
-      pbuf_free(seg);
-      cs->rx_offset = 0;
-    }
-    mgos_unlock();
-    mg_if_recv_tcp_cb(nc, data, len, 1 /* own */);
-    mgos_lock();
-  }
   mgos_unlock();
-}
-
-static void mg_lwip_handle_recv_tcp(struct mg_connection *nc) {
-  mg_lwip_consume_rx_chain_tcp(nc);
-
-  if (nc->send_mbuf.len > 0) {
-    mg_lwip_mgr_schedule_poll(nc->mgr);
-  }
+  return ERR_OK;
 }
 
 static err_t mg_lwip_tcp_sent_cb(void *arg, struct tcp_pcb *tpcb,
@@ -313,13 +261,14 @@ static void mg_lwip_udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p,
   /* Logic in the recv handler requires that there be exactly one data pbuf. */
   p = pbuf_coalesce(p, PBUF_RAW);
   pbuf_chain(sap, p);
+  mgos_lock();
   mg_lwip_recv_common(nc, sap);
+  mgos_unlock();
   (void) pcb;
 }
 
 static void mg_lwip_recv_common(struct mg_connection *nc, struct pbuf *p) {
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  mgos_lock();
   if (cs->rx_chain == NULL) {
     cs->rx_chain = p;
   } else {
@@ -329,32 +278,30 @@ static void mg_lwip_recv_common(struct mg_connection *nc, struct pbuf *p) {
     cs->recv_pending = 1;
     mg_lwip_post_signal(MG_SIG_RECV, nc);
   }
-  mgos_unlock();
 }
 
-static void mg_lwip_handle_recv_udp(struct mg_connection *nc) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+static int mg_lwip_if_udp_recv(struct mg_connection *nc, void *buf, size_t len,
+                               union socket_address *sa, size_t *sa_len) {
   /*
    * For UDP, RX chain consists of interleaved address and packet bufs:
    * Address pbuf followed by exactly one data pbuf (recv_cb took care of that).
    */
-  while (cs->rx_chain != NULL) {
-    struct pbuf *sap = cs->rx_chain;
-    struct pbuf *p = sap->next;
-    cs->rx_chain = pbuf_dechain(p);
-    size_t data_len = p->len;
-    char *data = (char *) MG_MALLOC(data_len);
-    if (data != NULL) {
-      pbuf_copy_partial(p, data, data_len, 0);
-      pbuf_free(p);
-      mg_if_recv_udp_cb(nc, data, data_len,
-                        (union socket_address *) sap->payload, sap->len);
-      pbuf_free(sap);
-    } else {
-      pbuf_free(p);
-      pbuf_free(sap);
-    }
+  int res = 0;
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  if (nc->sock == INVALID_SOCKET) return -1;
+  mgos_lock();
+  if (cs->rx_chain != NULL) {
+    struct pbuf *ap = cs->rx_chain;
+    struct pbuf *dp = ap->next;
+    cs->rx_chain = pbuf_dechain(dp);
+    res = MIN(dp->len, len);
+    pbuf_copy_partial(dp, buf, res, 0);
+    pbuf_free(dp);
+    pbuf_copy_partial(ap, sa, MIN(*sa_len, ap->len), 0);
+    pbuf_free(ap);
   }
+  mgos_unlock();
+  return res;
 }
 
 static void mg_lwip_if_connect_udp_tcpip(void *arg) {
@@ -376,13 +323,6 @@ void mg_lwip_if_connect_udp(struct mg_connection *nc) {
   tcpip_callback(mg_lwip_if_connect_udp_tcpip, nc);
 }
 
-void mg_lwip_accept_conn(struct mg_connection *nc, struct tcp_pcb *tpcb) {
-  union socket_address sa;
-  SET_ADDR(&sa, &tpcb->remote_ip);
-  sa.sin.sin_port = htons(tpcb->remote_port);
-  mg_if_accept_tcp_cb(nc, &sa, sizeof(sa.sin));
-}
-
 static void tcp_close_tcpip(void *arg) {
   tcp_close((struct tcp_pcb *) arg);
 }
@@ -390,17 +330,11 @@ static void tcp_close_tcpip(void *arg) {
 void mg_lwip_handle_accept(struct mg_connection *nc) {
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
   if (cs->pcb.tcp == NULL) return;
-#if MG_ENABLE_SSL
-  if (cs->lc->flags & MG_F_SSL) {
-    if (mg_ssl_if_conn_accept(nc, cs->lc) != MG_SSL_OK) {
-      LOG(LL_ERROR, ("SSL error"));
-      tcpip_callback(tcp_close_tcpip, cs->pcb.tcp);
-    }
-  } else
-#endif
-  {
-    mg_lwip_accept_conn(nc, cs->pcb.tcp);
-  }
+  union socket_address sa;
+  struct tcp_pcb *tpcb = cs->pcb.tcp;
+  SET_ADDR(&sa, &tpcb->remote_ip);
+  sa.sin.sin_port = htons(tpcb->remote_port);
+  mg_if_accept_tcp_cb(nc, &sa, sizeof(sa.sin));
 }
 
 static err_t mg_lwip_accept_cb(void *arg, struct tcp_pcb *newtpcb, err_t err) {
@@ -557,14 +491,13 @@ static void mg_lwip_tcp_write_tcpip(void *arg) {
   ctx->ret = len;
 }
 
-static int mg_lwip_tcp_write(struct mg_connection *nc, const void *data,
-                             uint16_t len) {
-  struct mg_lwip_tcp_write_ctx ctx = {.nc = nc, .data = data, .len = len};
+int mg_lwip_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) {
+  struct mg_lwip_tcp_write_ctx ctx = {.nc = nc, .data = buf, .len = len};
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  if (nc->sock == INVALID_SOCKET) return -1;
   struct tcp_pcb *tpcb = cs->pcb.tcp;
-  if (tpcb == NULL) {
-    return -1;
-  }
+  if (tpcb == NULL) return -1;
+  if (tpcb->snd_buf <= 0) return 0;
   tcpip_callback(mg_lwip_tcp_write_tcpip, &ctx);
   return ctx.ret;
 }
@@ -582,18 +515,10 @@ static void udp_sendto_tcpip(void *arg) {
   ctx->ret = udp_sendto(ctx->upcb, ctx->p, ctx->ip, ctx->port);
 }
 
-static int mg_lwip_udp_send(struct mg_connection *nc, const void *data,
-                            uint16_t len) {
+static int mg_lwip_if_udp_send(struct mg_connection *nc, const void *data,
+                               size_t len) {
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  if (cs->pcb.udp == NULL) {
-    /*
-     * In case of UDP, this usually means, what
-     * async DNS resolve is still in progress and connection
-     * is not ready yet
-     */
-    DBG(("%p socket is not connected", nc));
-    return -1;
-  }
+  if (nc->sock == INVALID_SOCKET || cs->pcb.udp == NULL) return -1;
   struct udp_pcb *upcb = cs->pcb.udp;
   struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
 #if defined(LWIP_IPV4) && LWIP_IPV4 && defined(LWIP_IPV6) && LWIP_IPV6
@@ -602,46 +527,28 @@ static int mg_lwip_udp_send(struct mg_connection *nc, const void *data,
   ip_addr_t ip = {.addr = nc->sa.sin.sin_addr.s_addr};
 #endif
   u16_t port = ntohs(nc->sa.sin.sin_port);
-  if (p == NULL) {
-    DBG(("OOM"));
-    return 0;
-  }
+  if (p == NULL) return 0;
   memcpy(p->payload, data, len);
   struct udp_sendto_ctx ctx = {.upcb = upcb, .p = p, .ip = &ip, .port = port};
   tcpip_callback(udp_sendto_tcpip, &ctx);
   cs->err = ctx.ret;
   pbuf_free(p);
-  return (cs->err == ERR_OK ? len : -1);
+  return (cs->err == ERR_OK ? (int) len : -2);
 }
 
-static void mg_lwip_send_more(struct mg_connection *nc) {
-  int num_sent = 0;
-  if (nc->sock == INVALID_SOCKET) return;
-  if (nc->flags & MG_F_UDP) {
-    num_sent = mg_lwip_udp_send(nc, nc->send_mbuf.buf, nc->send_mbuf.len);
-    DBG(("%p mg_lwip_udp_send %u = %d", nc, nc->send_mbuf.len, num_sent));
-  } else {
-    num_sent = mg_lwip_tcp_write(nc, nc->send_mbuf.buf, nc->send_mbuf.len);
-    DBG(("%p mg_lwip_tcp_write %u = %d", nc, nc->send_mbuf.len, num_sent));
-  }
-  if (num_sent == 0) return;
-  if (num_sent > 0) {
-    mg_if_sent_cb(nc, num_sent);
-  } else {
-    mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
+static int mg_lwip_if_can_send(struct mg_connection *nc,
+                               struct mg_lwip_conn_state *cs) {
+  int can_send = 0;
+  if (nc->send_mbuf.len > 0 || (nc->flags & MG_F_WANT_WRITE)) {
+    /* We have stuff to send, but can we? */
+    if (nc->flags & MG_F_UDP) {
+      /* UDP is always ready for sending. */
+      can_send = (cs->pcb.udp != NULL);
+    } else {
+      can_send = (cs->pcb.tcp != NULL && cs->pcb.tcp->snd_buf > 0);
+    }
   }
-}
-
-void mg_lwip_if_tcp_send(struct mg_connection *nc, const void *buf,
-                         size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
-  mg_lwip_mgr_schedule_poll(nc->mgr);
-}
-
-void mg_lwip_if_udp_send(struct mg_connection *nc, const void *buf,
-                         size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
-  mg_lwip_mgr_schedule_poll(nc->mgr);
+  return can_send;
 }
 
 struct tcp_recved_ctx {
@@ -654,27 +561,35 @@ void tcp_recved_tcpip(void *arg) {
   tcp_recved(ctx->tpcb, ctx->len);
 }
 
-void mg_lwip_if_recved(struct mg_connection *nc, size_t len) {
-  if (nc->flags & MG_F_UDP) return;
+static int mg_lwip_if_tcp_recv(struct mg_connection *nc, void *buf,
+                               size_t len) {
+  int res = 0;
+  char *bufp = buf;
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  if (nc->sock == INVALID_SOCKET || cs->pcb.tcp == NULL) {
-    DBG(("%p invalid socket", nc));
-    return;
+  if (nc->sock == INVALID_SOCKET) return -1;
+  mgos_lock();
+  while (cs->rx_chain != NULL && len > 0) {
+    struct pbuf *seg = cs->rx_chain;
+    size_t seg_len = (seg->len - cs->rx_offset);
+    size_t copy_len = MIN(len, seg_len);
+
+    pbuf_copy_partial(seg, bufp, copy_len, cs->rx_offset);
+    len -= copy_len;
+    res += copy_len;
+    bufp += copy_len;
+    cs->rx_offset += copy_len;
+    if (cs->rx_offset == cs->rx_chain->len) {
+      cs->rx_chain = pbuf_dechain(cs->rx_chain);
+      pbuf_free(seg);
+      cs->rx_offset = 0;
+    }
   }
-  DBG(("%p %p %u %u", nc, cs->pcb.tcp, len,
-       (cs->rx_chain ? cs->rx_chain->tot_len : 0)));
-  struct tcp_recved_ctx ctx = {.tpcb = cs->pcb.tcp, .len = len};
-#if MG_ENABLE_SSL
-  if (!(nc->flags & MG_F_SSL)) {
+  mgos_unlock();
+  if (res > 0) {
+    struct tcp_recved_ctx ctx = {.tpcb = cs->pcb.tcp, .len = res};
     tcpip_callback(tcp_recved_tcpip, &ctx);
-  } else {
-    /* Currently SSL acknowledges data immediately.
-     * TODO(rojer): Find a way to propagate mg_lwip_if_recved. */
   }
-#else
-  tcpip_callback(tcp_recved_tcpip, &ctx);
-#endif
-  mbuf_trim(&nc->recv_mbuf);
+  return res;
 }
 
 int mg_lwip_if_create_conn(struct mg_connection *nc) {
@@ -763,7 +678,8 @@ void mg_lwip_if_sock_set(struct mg_connection *nc, sock_t sock) {
     mg_lwip_if_connect_udp,                                           \
     mg_lwip_if_tcp_send,                                              \
     mg_lwip_if_udp_send,                                              \
-    mg_lwip_if_recved,                                                \
+    mg_lwip_if_tcp_recv,                                              \
+    mg_lwip_if_udp_recv,                                              \
     mg_lwip_if_create_conn,                                           \
     mg_lwip_if_destroy_conn,                                          \
     mg_lwip_if_sock_set,                                              \
diff --git a/src/common/platforms/lwip/mg_lwip_net_if.h b/src/common/platforms/lwip/mg_lwip_net_if.h
index d00fd2fdd..d94cfdf6e 100644
--- a/src/common/platforms/lwip/mg_lwip_net_if.h
+++ b/src/common/platforms/lwip/mg_lwip_net_if.h
@@ -30,9 +30,9 @@ struct mg_lwip_conn_state {
   /* Last SSL write size, for retries. */
   int last_ssl_write_size;
   /* Whether MG_SIG_RECV is already pending for this connection */
-  int recv_pending : 1;
+  int recv_pending;
   /* Whether the connection is about to close, just `rx_chain` needs to drain */
-  int draining_rx_chain : 1;
+  int draining_rx_chain;
 };
 
 enum mg_sig_type {
diff --git a/src/common/platforms/lwip/mg_lwip_ssl_if.c b/src/common/platforms/lwip/mg_lwip_ssl_if.c
deleted file mode 100644
index 04ed7784c..000000000
--- a/src/common/platforms/lwip/mg_lwip_ssl_if.c
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (c) 2014-2016 Cesanta Software Limited
- * All rights reserved
- */
-
-#if MG_ENABLE_SSL && MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL
-
-#include "common/mg_mem.h"
-#include "common/cs_dbg.h"
-
-#include <lwip/pbuf.h>
-#include <lwip/tcp.h>
-
-#ifndef MG_LWIP_SSL_IO_SIZE
-#define MG_LWIP_SSL_IO_SIZE 1024
-#endif
-
-#ifndef MIN
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-#endif
-
-void mg_lwip_ssl_do_hs(struct mg_connection *nc) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  int server_side = (nc->listener != NULL);
-  enum mg_ssl_if_result res;
-  if (nc->flags & MG_F_CLOSE_IMMEDIATELY) return;
-  res = mg_ssl_if_handshake(nc);
-  DBG(("%p %lu %d %d", nc, nc->flags, server_side, res));
-  if (res != MG_SSL_OK) {
-    if (res == MG_SSL_WANT_WRITE) {
-      nc->flags |= MG_F_WANT_WRITE;
-      cs->err = 0;
-    } else if (res == MG_SSL_WANT_READ) {
-      /*
-       * Nothing to do in particular, we are callback-driven.
-       * What we definitely do not need anymore is SSL reading (nothing left).
-       */
-      nc->flags &= ~MG_F_WANT_READ;
-      cs->err = 0;
-    } else {
-      cs->err = res;
-      if (server_side) {
-        mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
-      } else {
-        mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);
-      }
-    }
-  } else {
-    cs->err = 0;
-    nc->flags &= ~MG_F_WANT_WRITE;
-    /*
-     * Handshake is done. Schedule a read immediately to consume app data
-     * which may already be waiting.
-     */
-    nc->flags |= (MG_F_SSL_HANDSHAKE_DONE | MG_F_WANT_READ);
-    if (server_side) {
-      mg_lwip_accept_conn(nc, cs->pcb.tcp);
-    } else {
-      mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);
-    }
-  }
-}
-
-void mg_lwip_ssl_send(struct mg_connection *nc) {
-  if (nc->sock == INVALID_SOCKET) {
-    DBG(("%p invalid socket", nc));
-    return;
-  }
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  /* It's ok if the buffer is empty. Return value of 0 may also be valid. */
-  int len = cs->last_ssl_write_size;
-  if (len == 0) {
-    len = MIN(MG_LWIP_SSL_IO_SIZE, nc->send_mbuf.len);
-  }
-  int ret = mg_ssl_if_write(nc, nc->send_mbuf.buf, len);
-  DBG(("%p SSL_write %u = %d", nc, len, ret));
-  if (ret > 0) {
-    mg_if_sent_cb(nc, ret);
-    cs->last_ssl_write_size = 0;
-  } else if (ret < 0) {
-    /* This is tricky. We must remember the exact data we were sending to retry
-     * exactly the same send next time. */
-    cs->last_ssl_write_size = len;
-  }
-  if (ret == len) {
-    nc->flags &= ~MG_F_WANT_WRITE;
-  } else if (ret == MG_SSL_WANT_WRITE) {
-    nc->flags |= MG_F_WANT_WRITE;
-  } else {
-    mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
-  }
-}
-
-void mg_lwip_ssl_recv(struct mg_connection *nc) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  /* Don't deliver data before connect callback */
-  if (nc->flags & MG_F_CONNECTING) return;
-  while (nc->recv_mbuf.len < nc->recv_mbuf_limit) {
-    char *buf = (char *) MG_MALLOC(MG_LWIP_SSL_IO_SIZE);
-    if (buf == NULL) return;
-    int ret = mg_ssl_if_read(nc, buf, MG_LWIP_SSL_IO_SIZE);
-    DBG(("%p %p SSL_read %u = %d", nc, cs->rx_chain, MG_LWIP_SSL_IO_SIZE, ret));
-    if (ret <= 0) {
-      MG_FREE(buf);
-      if (ret == MG_SSL_WANT_WRITE) {
-        nc->flags |= MG_F_WANT_WRITE;
-        return;
-      } else if (ret == MG_SSL_WANT_READ) {
-        /*
-         * Nothing to do in particular, we are callback-driven.
-         * What we definitely do not need anymore is SSL reading (nothing left).
-         */
-        nc->flags &= ~MG_F_WANT_READ;
-        cs->err = 0;
-        return;
-      } else {
-        mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
-        return;
-      }
-    } else {
-      mg_if_recv_tcp_cb(nc, buf, ret, 1 /* own */);
-    }
-  }
-}
-
-#ifdef KR_VERSION
-
-ssize_t kr_send(int fd, const void *buf, size_t len) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) fd;
-  int ret = mg_lwip_tcp_write(cs->nc, buf, len);
-  DBG(("%p mg_lwip_tcp_write %u = %d", cs->nc, len, ret));
-  if (ret == 0) ret = KR_IO_WOULDBLOCK;
-  return ret;
-}
-
-ssize_t kr_recv(int fd, void *buf, size_t len) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) fd;
-  struct pbuf *seg = cs->rx_chain;
-  if (seg == NULL) {
-    DBG(("%u - nothing to read", len));
-    return KR_IO_WOULDBLOCK;
-  }
-  size_t seg_len = (seg->len - cs->rx_offset);
-  DBG(("%u %u %u %u", len, cs->rx_chain->len, seg_len, cs->rx_chain->tot_len));
-  len = MIN(len, seg_len);
-  pbuf_copy_partial(seg, buf, len, cs->rx_offset);
-  cs->rx_offset += len;
-  tcp_recved(cs->pcb.tcp, len);
-  if (cs->rx_offset == cs->rx_chain->len) {
-    cs->rx_chain = pbuf_dechain(cs->rx_chain);
-    pbuf_free(seg);
-    cs->rx_offset = 0;
-  }
-  return len;
-}
-
-#elif MG_SSL_IF == MG_SSL_IF_MBEDTLS
-
-int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len) {
-  struct mg_connection *nc = (struct mg_connection *) ctx;
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  int ret = mg_lwip_tcp_write(cs->nc, buf, len);
-  if (ret == 0) ret = MBEDTLS_ERR_SSL_WANT_WRITE;
-  LOG(LL_DEBUG, ("%p %d -> %d", nc, len, ret));
-  return ret;
-}
-
-int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len) {
-  struct mg_connection *nc = (struct mg_connection *) ctx;
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  struct pbuf *seg = cs->rx_chain;
-  if (seg == NULL) {
-    DBG(("%u - nothing to read", len));
-    return MBEDTLS_ERR_SSL_WANT_READ;
-  }
-  size_t seg_len = (seg->len - cs->rx_offset);
-  DBG(("%u %u %u %u", len, cs->rx_chain->len, seg_len, cs->rx_chain->tot_len));
-  mgos_lock();
-  len = MIN(len, seg_len);
-  pbuf_copy_partial(seg, buf, len, cs->rx_offset);
-  cs->rx_offset += len;
-  /* TCP PCB may be NULL if connection has already been closed
-   * but we still have data to deliver to SSL. */
-  if (cs->pcb.tcp != NULL) tcp_recved(cs->pcb.tcp, len);
-  if (cs->rx_offset == cs->rx_chain->len) {
-    cs->rx_chain = pbuf_dechain(cs->rx_chain);
-    pbuf_free(seg);
-    cs->rx_offset = 0;
-  }
-  mgos_unlock();
-  LOG(LL_DEBUG, ("%p <- %d", nc, (int) len));
-  return len;
-}
-
-#endif
-
-#endif /* MG_ENABLE_SSL && MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL */
diff --git a/src/common/platforms/platform_cc3220.h b/src/common/platforms/platform_cc3220.h
index a32264a70..6992e142f 100644
--- a/src/common/platforms/platform_cc3220.h
+++ b/src/common/platforms/platform_cc3220.h
@@ -22,7 +22,9 @@
 #endif
 
 #define MG_NET_IF MG_NET_IF_SIMPLELINK
+#ifndef MG_SSL_IF
 #define MG_SSL_IF MG_SSL_IF_SIMPLELINK
+#endif
 
 /* Only SPIFFS supports directories, SLFS does not. */
 #if defined(CC3220_FS_SPIFFS) && !defined(MG_ENABLE_DIRECTORY_LISTING)
diff --git a/src/common/platforms/simplelink/sl_net_if.c b/src/common/platforms/simplelink/sl_net_if.c
index 2a39d1405..fcc5632b2 100644
--- a/src/common/platforms/simplelink/sl_net_if.c
+++ b/src/common/platforms/simplelink/sl_net_if.c
@@ -17,7 +17,7 @@ static sock_t mg_open_listening_socket(struct mg_connection *nc,
                                        union socket_address *sa, int type,
                                        int proto);
 
-void mg_set_non_blocking_mode(sock_t sock) {
+static void mg_set_non_blocking_mode(sock_t sock) {
   SlSockNonblocking_t opt;
 #if SL_MAJOR_VERSION_NUM < 2
   opt.NonblockingEnabled = 1;
@@ -31,17 +31,19 @@ static int mg_is_error(int n) {
   return (n < 0 && n != SL_ERROR_BSD_EALREADY && n != SL_ERROR_BSD_EAGAIN);
 }
 
-void mg_sl_if_connect_tcp(struct mg_connection *nc,
-                          const union socket_address *sa) {
+static void mg_sl_if_connect_tcp(struct mg_connection *nc,
+                                 const union socket_address *sa) {
   int proto = 0;
+#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK
   if (nc->flags & MG_F_SSL) proto = SL_SEC_SOCKET;
+#endif
   sock_t sock = sl_Socket(AF_INET, SOCK_STREAM, proto);
   if (sock < 0) {
     nc->err = sock;
     goto out;
   }
   mg_sock_set(nc, sock);
-#if MG_ENABLE_SSL
+#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK
   nc->err = sl_set_ssl_opts(sock, nc);
   if (nc->err != 0) goto out;
 #endif
@@ -51,7 +53,7 @@ out:
        ntohs(sa->sin.sin_port), nc->sock, proto, nc->err));
 }
 
-void mg_sl_if_connect_udp(struct mg_connection *nc) {
+static void mg_sl_if_connect_udp(struct mg_connection *nc) {
   sock_t sock = sl_Socket(AF_INET, SOCK_DGRAM, 0);
   if (sock < 0) {
     nc->err = sock;
@@ -61,7 +63,8 @@ void mg_sl_if_connect_udp(struct mg_connection *nc) {
   nc->err = 0;
 }
 
-int mg_sl_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
+static int mg_sl_if_listen_tcp(struct mg_connection *nc,
+                               union socket_address *sa) {
   int proto = 0;
   if (nc->flags & MG_F_SSL) proto = SL_SEC_SOCKET;
   sock_t sock = mg_open_listening_socket(nc, sa, SOCK_STREAM, proto);
@@ -70,27 +73,50 @@ int mg_sl_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
   return 0;
 }
 
-int mg_sl_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
+static int mg_sl_if_listen_udp(struct mg_connection *nc,
+                               union socket_address *sa) {
   sock_t sock = mg_open_listening_socket(nc, sa, SOCK_DGRAM, 0);
   if (sock == INVALID_SOCKET) return (errno ? errno : 1);
   mg_sock_set(nc, sock);
   return 0;
 }
 
-void mg_sl_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
+static int mg_sl_if_tcp_send(struct mg_connection *nc, const void *buf,
+                             size_t len) {
+  int n = (int) sl_Send(nc->sock, buf, len, 0);
+  if (n < 0 && !mg_is_error(n)) n = 0;
+  return n;
 }
 
-void mg_sl_if_udp_send(struct mg_connection *nc, const void *buf, size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
+static int mg_sl_if_udp_send(struct mg_connection *nc, const void *buf,
+                             size_t len) {
+  int n = sl_SendTo(nc->sock, buf, len, 0, &nc->sa.sa, sizeof(nc->sa.sin));
+  if (n < 0 && !mg_is_error(n)) n = 0;
+  return n;
 }
 
-void mg_sl_if_recved(struct mg_connection *nc, size_t len) {
-  (void) nc;
-  (void) len;
+static int mg_sl_if_tcp_recv(struct mg_connection *nc, void *buf, size_t len) {
+  int n = sl_Recv(nc->sock, buf, len, 0);
+  if (n == 0) {
+    /* Orderly shutdown of the socket, try flushing output. */
+    nc->flags |= MG_F_SEND_AND_CLOSE;
+  } else if (n < 0 && !mg_is_error(n)) {
+    n = 0;
+  }
+  return n;
 }
 
-int mg_sl_if_create_conn(struct mg_connection *nc) {
+static int mg_sl_if_udp_recv(struct mg_connection *nc, void *buf, size_t len,
+                             union socket_address *sa, size_t *sa_len) {
+  SlSocklen_t sa_len_t = *sa_len;
+  int n = sl_RecvFrom(nc->sock, buf, MG_UDP_RECV_BUFFER_SIZE, 0,
+                      (SlSockAddr_t *) sa, &sa_len_t);
+  *sa_len = sa_len_t;
+  if (n < 0 && !mg_is_error(n)) n = 0;
+  return n;
+}
+
+static int mg_sl_if_create_conn(struct mg_connection *nc) {
   (void) nc;
   return 1;
 }
@@ -121,7 +147,6 @@ static int mg_accept_conn(struct mg_connection *lc) {
   DBG(("%p conn from %s:%d", nc, inet_ntoa(sa.sin.sin_addr),
        ntohs(sa.sin.sin_port)));
   mg_sock_set(nc, sock);
-  if (nc->flags & MG_F_SSL) nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
   mg_if_accept_tcp_cb(nc, &sa, sa_len);
   return 1;
 }
@@ -135,11 +160,11 @@ static sock_t mg_open_listening_socket(struct mg_connection *nc,
       (sa->sa.sa_family == AF_INET) ? sizeof(sa->sin) : sizeof(sa->sin6);
   sock_t sock = sl_Socket(sa->sa.sa_family, type, proto);
   if (sock < 0) return sock;
+#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK
+  if ((r = sl_set_ssl_opts(sock, nc)) < 0) goto clean;
+#endif
   if ((r = sl_Bind(sock, &sa->sa, sa_len)) < 0) goto clean;
   if (type != SOCK_DGRAM) {
-#if MG_ENABLE_SSL
-    if ((r = sl_set_ssl_opts(sock, nc)) < 0) goto clean;
-#endif
     if ((r = sl_Listen(sock, SOMAXCONN)) < 0) goto clean;
   }
   mg_set_non_blocking_mode(sock);
@@ -151,86 +176,18 @@ clean:
   return sock;
 }
 
-static void mg_write_to_socket(struct mg_connection *nc) {
-  struct mbuf *io = &nc->send_mbuf;
-  int n = 0;
-
-  if (nc->flags & MG_F_UDP) {
-    n = sl_SendTo(nc->sock, io->buf, io->len, 0, &nc->sa.sa,
-                  sizeof(nc->sa.sin));
-    DBG(("%p %d %d %d %s:%hu", nc, nc->sock, n, errno,
-         inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port)));
-  } else {
-    n = (int) sl_Send(nc->sock, io->buf, io->len, 0);
-    DBG(("%p %d bytes -> %d", nc, n, nc->sock));
-  }
-
-  if (n > 0) {
-    mg_if_sent_cb(nc, n);
-  } else if (n < 0 && mg_is_error(n)) {
-    /* Something went wrong, drop the connection. */
-    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-  }
-}
-
-MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) {
-  size_t avail;
-  if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0;
-  avail = conn->recv_mbuf_limit - conn->recv_mbuf.len;
-  return avail > max ? max : avail;
-}
-
-static void mg_handle_tcp_read(struct mg_connection *conn) {
-  int n = 0;
-  char *buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE);
-
-  if (buf == NULL) {
-    DBG(("OOM"));
-    return;
-  }
-
-  n = (int) sl_Recv(conn->sock, buf,
-                    recv_avail_size(conn, MG_TCP_RECV_BUFFER_SIZE), 0);
-  DBG(("%p %d bytes <- %d", conn, n, conn->sock));
-  if (n > 0) {
-    mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */);
-  } else {
-    MG_FREE(buf);
-  }
-  if (n == 0) {
-    /* Orderly shutdown of the socket, try flushing output. */
-    conn->flags |= MG_F_SEND_AND_CLOSE;
-  } else if (mg_is_error(n)) {
-    conn->flags |= MG_F_CLOSE_IMMEDIATELY;
-  }
-}
-
-static void mg_handle_udp_read(struct mg_connection *nc) {
-  char *buf = (char *) MG_MALLOC(MG_UDP_RECV_BUFFER_SIZE);
-  if (buf == NULL) return;
-  union socket_address sa;
-  socklen_t sa_len = sizeof(sa);
-  int n = sl_RecvFrom(nc->sock, buf, MG_UDP_RECV_BUFFER_SIZE, 0,
-                      (SlSockAddr_t *) &sa, &sa_len);
-  DBG(("%p %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr),
-       ntohs(nc->sa.sin.sin_port)));
-  if (n > 0) {
-    mg_if_recv_udp_cb(nc, buf, n, &sa, sa_len);
-  } else {
-    MG_FREE(buf);
-  }
-}
-
 #define _MG_F_FD_CAN_READ 1
 #define _MG_F_FD_CAN_WRITE 1 << 1
 #define _MG_F_FD_ERROR 1 << 2
 
 void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
-  DBG(("%p fd=%d fd_flags=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock,
+  DBG(("%p fd=%d fd_flags=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock,
        fd_flags, nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
 
+  if (!mg_if_poll(nc, now)) return;
+
   if (nc->flags & MG_F_CONNECTING) {
-    if (nc->flags & MG_F_UDP || nc->err != SL_ERROR_BSD_EALREADY) {
+    if ((nc->flags & MG_F_UDP) || nc->err != SL_ERROR_BSD_EALREADY) {
       mg_if_connect_cb(nc, nc->err);
     } else {
       /* In SimpleLink, to get status of non-blocking connect() we need to wait
@@ -252,9 +209,6 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
             ) {
           nc->err = 0;
         }
-        if (nc->flags & MG_F_SSL && nc->err == 0) {
-          nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
-        }
         mg_if_connect_cb(nc, nc->err);
       }
     }
@@ -264,28 +218,21 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
 
   if (fd_flags & _MG_F_FD_CAN_READ) {
     if (nc->flags & MG_F_UDP) {
-      mg_handle_udp_read(nc);
+      mg_if_can_recv_cb(nc);
     } else {
       if (nc->flags & MG_F_LISTENING) {
         mg_accept_conn(nc);
       } else {
-        mg_handle_tcp_read(nc);
+        mg_if_can_recv_cb(nc);
       }
     }
   }
 
-  if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
-    if ((fd_flags & _MG_F_FD_CAN_WRITE) && nc->send_mbuf.len > 0) {
-      mg_write_to_socket(nc);
-    }
-
-    if (!(fd_flags & (_MG_F_FD_CAN_READ | _MG_F_FD_CAN_WRITE))) {
-      mg_if_poll(nc, now);
-    }
-    mg_if_timer(nc, now);
+  if (fd_flags & _MG_F_FD_CAN_WRITE) {
+    mg_if_can_send_cb(nc);
   }
 
-  DBG(("%p after fd=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock, nc->flags,
+  DBG(("%p after fd=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock, nc->flags,
        (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
 }
 
@@ -406,14 +353,6 @@ time_t mg_sl_if_poll(struct mg_iface *iface, int timeout_ms) {
     mg_mgr_handle_conn(nc, fd_flags, now);
   }
 
-  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {
-    tmp = nc->next;
-    if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
-        (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) {
-      mg_close_conn(nc);
-    }
-  }
-
   return now;
 }
 
@@ -464,7 +403,8 @@ void sl_restart_cb(struct mg_mgr *mgr) {
     mg_sl_if_connect_udp,                                               \
     mg_sl_if_tcp_send,                                                  \
     mg_sl_if_udp_send,                                                  \
-    mg_sl_if_recved,                                                    \
+    mg_sl_if_tcp_recv,                                                  \
+    mg_sl_if_udp_recv,                                                  \
     mg_sl_if_create_conn,                                               \
     mg_sl_if_destroy_conn,                                              \
     mg_sl_if_sock_set,                                                  \
diff --git a/src/common/platforms/simplelink/sl_ssl_if.c b/src/common/platforms/simplelink/sl_ssl_if.c
index 99642b4bd..0cd9c25b8 100644
--- a/src/common/platforms/simplelink/sl_ssl_if.c
+++ b/src/common/platforms/simplelink/sl_ssl_if.c
@@ -54,6 +54,31 @@ enum mg_ssl_if_result mg_ssl_if_conn_init(
   return MG_SSL_OK;
 }
 
+enum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc,
+                                            struct mg_connection *lc) {
+  /* SimpleLink does everything for us, nothing for us to do. */
+  (void) nc;
+  (void) lc;
+  return MG_SSL_OK;
+}
+
+enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {
+  /* SimpleLink has already performed the handshake, nothing to do. */
+  return MG_SSL_OK;
+}
+
+int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t len) {
+  /* SimpelLink handles TLS, so this is just a pass-through. */
+  int n = nc->iface->vtable->tcp_recv(nc, buf, len);
+  if (n == 0) nc->flags |= MG_F_WANT_READ;
+  return n;
+}
+
+int mg_ssl_if_write(struct mg_connection *nc, const void *buf, size_t len) {
+  /* SimpelLink handles TLS, so this is just a pass-through. */
+  return nc->iface->vtable->tcp_send(nc, buf, len);
+}
+
 void mg_ssl_if_conn_close_notify(struct mg_connection *nc) {
   /* Nothing to do */
   (void) nc;
@@ -154,56 +179,59 @@ int sl_set_ssl_opts(int sock, struct mg_connection *nc) {
   const struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
   DBG(("%p ssl ctx: %p", nc, ctx));
 
-  if (ctx != NULL) {
-    DBG(("%p %s,%s,%s,%s", nc, (ctx->ssl_cert ? ctx->ssl_cert : "-"),
-         (ctx->ssl_key ? ctx->ssl_cert : "-"),
-         (ctx->ssl_ca_cert ? ctx->ssl_ca_cert : "-"),
-         (ctx->ssl_server_name ? ctx->ssl_server_name : "-")));
-    if (ctx->ssl_cert != NULL && ctx->ssl_key != NULL) {
-      char *ssl_cert = sl_pem2der(ctx->ssl_cert);
-      char *ssl_key = sl_pem2der(ctx->ssl_key);
-      if (ssl_cert != NULL && ssl_key != NULL) {
-        err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
-                            SL_SO_SECURE_FILES_CERTIFICATE_FILE_NAME, ssl_cert,
-                            strlen(ssl_cert));
-        LOG(LL_INFO, ("CERTIFICATE_FILE_NAME %s -> %d", ssl_cert, err));
+  if (ctx == NULL) return 0;
+  DBG(("%p %s,%s,%s,%s", nc, (ctx->ssl_cert ? ctx->ssl_cert : "-"),
+       (ctx->ssl_key ? ctx->ssl_cert : "-"),
+       (ctx->ssl_ca_cert ? ctx->ssl_ca_cert : "-"),
+       (ctx->ssl_server_name ? ctx->ssl_server_name : "-")));
+  if (ctx->ssl_cert != NULL && ctx->ssl_key != NULL) {
+    char *ssl_cert = sl_pem2der(ctx->ssl_cert), *ssl_key = NULL;
+    if (ssl_cert != NULL) {
+      err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
+                          SL_SO_SECURE_FILES_CERTIFICATE_FILE_NAME, ssl_cert,
+                          strlen(ssl_cert));
+      MG_FREE(ssl_cert);
+      LOG(LL_DEBUG, ("CERTIFICATE_FILE_NAME %s -> %d", ssl_cert, err));
+      ssl_key = sl_pem2der(ctx->ssl_key);
+      if (ssl_key != NULL) {
         err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
                             SL_SO_SECURE_FILES_PRIVATE_KEY_FILE_NAME, ssl_key,
                             strlen(ssl_key));
-        LOG(LL_INFO, ("PRIVATE_KEY_FILE_NAME %s -> %d", ssl_key, err));
+        MG_FREE(ssl_key);
+        LOG(LL_DEBUG, ("PRIVATE_KEY_FILE_NAME %s -> %d", ssl_key, err));
       } else {
         err = -1;
       }
-      MG_FREE(ssl_cert);
-      MG_FREE(ssl_key);
-      if (err != 0) return err;
+    } else {
+      err = -1;
     }
-    if (ctx->ssl_ca_cert != NULL) {
-      if (ctx->ssl_ca_cert[0] != '\0') {
-        char *ssl_ca_cert = sl_pem2der(ctx->ssl_ca_cert);
-        if (ssl_ca_cert != NULL) {
-          err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
-                              SL_SO_SECURE_FILES_CA_FILE_NAME, ssl_ca_cert,
-                              strlen(ssl_ca_cert));
-          LOG(LL_INFO, ("CA_FILE_NAME %s -> %d", ssl_ca_cert, err));
-        } else {
-          err = -1;
-        }
-        MG_FREE(ssl_ca_cert);
-        if (err != 0) return err;
+    if (err != 0) return err;
+  }
+  if (ctx->ssl_ca_cert != NULL) {
+    if (ctx->ssl_ca_cert[0] != '\0') {
+      char *ssl_ca_cert = sl_pem2der(ctx->ssl_ca_cert);
+      if (ssl_ca_cert != NULL) {
+        err =
+            sl_SetSockOpt(sock, SL_SOL_SOCKET, SL_SO_SECURE_FILES_CA_FILE_NAME,
+                          ssl_ca_cert, strlen(ssl_ca_cert));
+        LOG(LL_DEBUG, ("CA_FILE_NAME %s -> %d", ssl_ca_cert, err));
+      } else {
+        err = -1;
       }
+      MG_FREE(ssl_ca_cert);
+      if (err != 0) return err;
     }
-    if (ctx->ssl_server_name != NULL) {
-      err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
-                          SL_SO_SECURE_DOMAIN_NAME_VERIFICATION,
-                          ctx->ssl_server_name, strlen(ctx->ssl_server_name));
-      DBG(("DOMAIN_NAME_VERIFICATION %s -> %d", ctx->ssl_server_name, err));
-      /* Domain name verificationw as added in a NWP service pack, older
-       * versions return SL_ERROR_BSD_ENOPROTOOPT. There isn't much we can do
-       * about it,
-       * so we ignore the error. */
-      if (err != 0 && err != SL_ERROR_BSD_ENOPROTOOPT) return err;
-    }
+  }
+  if (ctx->ssl_server_name != NULL) {
+    err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
+                        SL_SO_SECURE_DOMAIN_NAME_VERIFICATION,
+                        ctx->ssl_server_name, strlen(ctx->ssl_server_name));
+    DBG(("DOMAIN_NAME_VERIFICATION %s -> %d", ctx->ssl_server_name, err));
+    /* Domain name verificationw as added in a NWP service pack, older
+     * versions return SL_ERROR_BSD_ENOPROTOOPT. There isn't much we can do
+     * about it,
+     * so we ignore the error. */
+    if (err != 0 && err != SL_ERROR_BSD_ENOPROTOOPT) return err;
   }
   return 0;
 }
diff --git a/src/mg_modules.mk b/src/mg_modules.mk
index 235e0d5ff..bf3f58525 100644
--- a/src/mg_modules.mk
+++ b/src/mg_modules.mk
@@ -96,7 +96,6 @@ SOURCES = $(COMMON)/mg_mem.h \
           $(COMMON)/platforms/lwip/mg_lwip_net_if.h \
           $(COMMON)/platforms/lwip/mg_lwip_net_if.c \
           $(COMMON)/platforms/lwip/mg_lwip_ev_mgr.c \
-          $(COMMON)/platforms/lwip/mg_lwip_ssl_if.c \
           $(COMMON)/platforms/wince/wince_libc.c \
           $(COMMON)/platforms/pic32/pic32_net_if.h \
           $(COMMON)/platforms/pic32/pic32_net_if.c \
diff --git a/src/mg_net.c b/src/mg_net.c
index 70e2cc84e..050ac791b 100644
--- a/src/mg_net.c
+++ b/src/mg_net.c
@@ -24,6 +24,13 @@
 
 #define MG_MAX_HOST_LEN 200
 
+#ifndef MG_TCP_IO_SIZE
+#define MG_TCP_IO_SIZE 1460
+#endif
+#ifndef MG_UDP_IO_SIZE
+#define MG_UDP_IO_SIZE 1460
+#endif
+
 #define MG_COPY_COMMON_CONNECTION_OPTIONS(dst, src) \
   memcpy(dst, src, sizeof(*dst));
 
@@ -64,8 +71,6 @@ MG_INTERNAL void mg_remove_conn(struct mg_connection *conn) {
 MG_INTERNAL void mg_call(struct mg_connection *nc,
                          mg_event_handler_t ev_handler, void *user_data, int ev,
                          void *ev_data) {
-  static int nesting_level = 0;
-  nesting_level++;
   if (ev_handler == NULL) {
     /*
      * If protocol handler is specified, call it. Otherwise, call user-specified
@@ -74,7 +79,7 @@ MG_INTERNAL void mg_call(struct mg_connection *nc,
     ev_handler = nc->proto_handler ? nc->proto_handler : nc->handler;
   }
   if (ev != MG_EV_POLL) {
-    DBG(("%p %s ev=%d ev_data=%p flags=%lu rmbl=%d smbl=%d", nc,
+    DBG(("%p %s ev=%d ev_data=%p flags=0x%lx rmbl=%d smbl=%d", nc,
          ev_handler == nc->handler ? "user" : "proto", ev, ev_data, nc->flags,
          (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
   }
@@ -87,33 +92,24 @@ MG_INTERNAL void mg_call(struct mg_connection *nc,
 #endif
   if (ev_handler != NULL) {
     unsigned long flags_before = nc->flags;
-    size_t recv_mbuf_before = nc->recv_mbuf.len, recved;
     ev_handler(nc, ev, ev_data MG_UD_ARG(user_data));
-    recved = (recv_mbuf_before - nc->recv_mbuf.len);
     /* Prevent user handler from fiddling with system flags. */
     if (ev_handler == nc->handler && nc->flags != flags_before) {
       nc->flags = (flags_before & ~_MG_CALLBACK_MODIFIABLE_FLAGS_MASK) |
                   (nc->flags & _MG_CALLBACK_MODIFIABLE_FLAGS_MASK);
     }
-    /* It's important to not double-count recved bytes, and since mg_call can be
-     * called recursively (e.g. proto_handler invokes user handler), we keep
-     * track of recursion and only report received bytes at the top level. */
-    if (nesting_level == 1 && recved > 0 && !(nc->flags & MG_F_UDP)) {
-      nc->iface->vtable->recved(nc, recved);
-    }
   }
   if (ev != MG_EV_POLL) {
-    DBG(("%p after %s flags=%lu rmbl=%d smbl=%d", nc,
+    DBG(("%p after %s flags=0x%lx rmbl=%d smbl=%d", nc,
          ev_handler == nc->handler ? "user" : "proto", nc->flags,
          (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
   }
-  nesting_level--;
 #if !MG_ENABLE_CALLBACK_USERDATA
   (void) user_data;
 #endif
 }
 
-void mg_if_timer(struct mg_connection *c, double now) {
+MG_INTERNAL void mg_timer(struct mg_connection *c, double now) {
   if (c->ev_timer_time > 0 && now >= c->ev_timer_time) {
     double old_value = c->ev_timer_time;
     c->ev_timer_time = 0;
@@ -121,13 +117,45 @@ void mg_if_timer(struct mg_connection *c, double now) {
   }
 }
 
-void mg_if_poll(struct mg_connection *nc, time_t now) {
-  if (!(nc->flags & MG_F_SSL) || (nc->flags & MG_F_SSL_HANDSHAKE_DONE)) {
-    mg_call(nc, NULL, nc->user_data, MG_EV_POLL, &now);
+MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) {
+  size_t avail;
+  if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0;
+  avail = conn->recv_mbuf_limit - conn->recv_mbuf.len;
+  return avail > max ? max : avail;
+}
+
+static int mg_do_recv(struct mg_connection *nc);
+
+int mg_if_poll(struct mg_connection *nc, double now) {
+  if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
+      (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) {
+    mg_close_conn(nc);
+    return 0;
   }
+#if MG_ENABLE_SSL
+  if ((nc->flags & (MG_F_SSL | MG_F_LISTENING | MG_F_CONNECTING)) == MG_F_SSL) {
+    /* SSL library may have data to be delivered to the app in its buffers,
+     * drain them. */
+    int recved = 0;
+    do {
+      if (nc->flags & (MG_F_WANT_READ | MG_F_WANT_WRITE)) break;
+      if (recv_avail_size(nc, MG_TCP_IO_SIZE) <= 0) break;
+      recved = mg_do_recv(nc);
+    } while (recved > 0);
+  }
+#endif /* MG_ENABLE_SSL */
+  mg_timer(nc, now);
+  {
+    time_t now_t = (time_t) now;
+    mg_call(nc, NULL, nc->user_data, MG_EV_POLL, &now_t);
+  }
+  return 1;
 }
 
 void mg_destroy_conn(struct mg_connection *conn, int destroy_if) {
+  if (conn->sock != INVALID_SOCKET) { /* Don't print timer-only conns */
+    LOG(LL_DEBUG, ("%p 0x%lx %d", conn, conn->flags, destroy_if));
+  }
   if (destroy_if) conn->iface->vtable->destroy_conn(conn);
   if (conn->proto_data != NULL && conn->proto_data_destructor != NULL) {
     conn->proto_data_destructor(conn->proto_data);
@@ -143,7 +171,6 @@ void mg_destroy_conn(struct mg_connection *conn, int destroy_if) {
 }
 
 void mg_close_conn(struct mg_connection *conn) {
-  DBG(("%p %lu %d", conn, conn->flags, conn->sock));
 #if MG_ENABLE_SSL
   if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) {
     mg_ssl_if_conn_close_notify(conn);
@@ -180,15 +207,6 @@ void mg_mgr_init_opt(struct mg_mgr *m, void *user_data,
   signal(SIGPIPE, SIG_IGN);
 #endif
 
-#if MG_ENABLE_SSL
-  {
-    static int init_done;
-    if (!init_done) {
-      mg_ssl_if_init();
-      init_done++;
-    }
-  }
-#endif
   {
     int i;
     if (opts.num_ifaces == 0) {
@@ -211,6 +229,15 @@ void mg_mgr_init_opt(struct mg_mgr *m, void *user_data,
   }
   DBG(("=================================="));
   DBG(("init mgr=%p", m));
+#if MG_ENABLE_SSL
+  {
+    static int init_done;
+    if (!init_done) {
+      mg_ssl_if_init();
+      init_done++;
+    }
+  }
+#endif
 }
 
 void mg_mgr_free(struct mg_mgr *m) {
@@ -453,6 +480,37 @@ MG_INTERNAL int mg_parse_address(const char *str, union socket_address *sa,
   return port < 0xffffUL && (ch == '\0' || ch == ',' || isspace(ch)) ? len : -1;
 }
 
+#if MG_ENABLE_SSL
+MG_INTERNAL void mg_ssl_handshake(struct mg_connection *nc) {
+  int err = 0;
+  int server_side = (nc->listener != NULL);
+  enum mg_ssl_if_result res;
+  if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) return;
+  res = mg_ssl_if_handshake(nc);
+  LOG(LL_DEBUG, ("%p %d res %d", nc, server_side, res));
+
+  if (res == MG_SSL_OK) {
+    nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
+    nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE);
+    if (server_side) {
+      mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
+    } else {
+      mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+    }
+  } else if (res == MG_SSL_WANT_READ) {
+    nc->flags |= MG_F_WANT_READ;
+  } else if (res == MG_SSL_WANT_WRITE) {
+    nc->flags |= MG_F_WANT_WRITE;
+  } else {
+    if (!server_side) {
+      err = res;
+      mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+    }
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+  }
+}
+#endif /* MG_ENABLE_SSL */
+
 struct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc) {
   struct mg_add_sock_opts opts;
   struct mg_connection *nc;
@@ -466,95 +524,125 @@ struct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc) {
   nc->iface = lc->iface;
   if (lc->flags & MG_F_SSL) nc->flags |= MG_F_SSL;
   mg_add_conn(nc->mgr, nc);
-  DBG(("%p %p %d %d", lc, nc, nc->sock, (int) nc->flags));
+  LOG(LL_DEBUG, ("%p %p %d %d", lc, nc, nc->sock, (int) nc->flags));
   return nc;
 }
 
 void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa,
                          size_t sa_len) {
-  (void) sa_len;
+  LOG(LL_DEBUG, ("%p %s://%s:%hu", nc, (nc->flags & MG_F_UDP ? "udp" : "tcp"),
+                 inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));
   nc->sa = *sa;
-  mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
+#if MG_ENABLE_SSL
+  if (nc->listener->flags & MG_F_SSL) {
+    nc->flags |= MG_F_SSL;
+    if (mg_ssl_if_conn_accept(nc, nc->listener) == MG_SSL_OK) {
+      mg_ssl_handshake(nc);
+    } else {
+      mg_close_conn(nc);
+    }
+  } else
+#endif
+  {
+    mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
+  }
+  (void) sa_len;
 }
 
 void mg_send(struct mg_connection *nc, const void *buf, int len) {
   nc->last_io_time = (time_t) mg_time();
-  if (nc->flags & MG_F_UDP) {
-    nc->iface->vtable->udp_send(nc, buf, len);
-  } else {
-    nc->iface->vtable->tcp_send(nc, buf, len);
-  }
+  mbuf_append(&nc->send_mbuf, buf, len);
 }
 
-void mg_if_sent_cb(struct mg_connection *nc, int num_sent) {
-  DBG(("%p %d", nc, num_sent));
-#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
-  if (nc->mgr && nc->mgr->hexdump_file != NULL) {
-    char *buf = nc->send_mbuf.buf;
-    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, num_sent, MG_EV_SEND);
+static int mg_recv_tcp(struct mg_connection *nc, char *buf, size_t len);
+static int mg_recv_udp(struct mg_connection *nc, char *buf, size_t len);
+
+static int mg_do_recv(struct mg_connection *nc) {
+  int res = 0;
+  char *buf = NULL;
+  size_t len = (nc->flags & MG_F_UDP ? MG_UDP_IO_SIZE : MG_TCP_IO_SIZE);
+  if ((nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_CONNECTING)) ||
+      ((nc->flags & MG_F_LISTENING) && !(nc->flags & MG_F_UDP))) {
+    return -1;
   }
-#endif
-  if (num_sent < 0) {
-    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+  len = recv_avail_size(nc, len);
+  if (len == 0) return -2;
+  if (nc->recv_mbuf.size < nc->recv_mbuf.len + len) {
+    mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.len + len);
+  }
+  buf = nc->recv_mbuf.buf + nc->recv_mbuf.len;
+  len = nc->recv_mbuf.size - nc->recv_mbuf.len;
+  if (nc->flags & MG_F_UDP) {
+    res = mg_recv_udp(nc, buf, len);
   } else {
-    mbuf_remove(&nc->send_mbuf, num_sent);
-    mbuf_trim(&nc->send_mbuf);
+    res = mg_recv_tcp(nc, buf, len);
   }
-  mg_call(nc, NULL, nc->user_data, MG_EV_SEND, &num_sent);
+  return res;
 }
 
-MG_INTERNAL void mg_recv_common(struct mg_connection *nc, void *buf, int len,
-                                int own) {
-  DBG(("%p %d %u", nc, len, (unsigned int) nc->recv_mbuf.len));
-
-#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
-  if (nc->mgr && nc->mgr->hexdump_file != NULL) {
-    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, len, MG_EV_RECV);
-  }
-#endif
+void mg_if_can_recv_cb(struct mg_connection *nc) {
+  mg_do_recv(nc);
+}
 
-  if (nc->flags & MG_F_CLOSE_IMMEDIATELY) {
-    DBG(("%p discarded %d bytes", nc, len));
-    /*
-     * This connection will not survive next poll. Do not deliver events,
-     * send data to /dev/null without acking.
-     */
-    if (own) {
-      MG_FREE(buf);
+static int mg_recv_tcp(struct mg_connection *nc, char *buf, size_t len) {
+  int n = 0;
+#if MG_ENABLE_SSL
+  if (nc->flags & MG_F_SSL) {
+    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
+      n = mg_ssl_if_read(nc, buf, len);
+      DBG(("%p <- %d bytes (SSL)", nc, n));
+      if (n < 0) {
+        if (n == MG_SSL_WANT_READ) {
+          nc->flags |= MG_F_WANT_READ;
+          n = 0;
+        } else {
+          nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+        }
+      } else if (n > 0) {
+        nc->flags &= ~MG_F_WANT_READ;
+      }
+    } else {
+      mg_ssl_handshake(nc);
     }
-    return;
+  } else
+#endif
+  {
+    n = nc->iface->vtable->tcp_recv(nc, buf, len);
+    DBG(("%p <- %d bytes", nc, n));
   }
-  nc->last_io_time = (time_t) mg_time();
-  if (!own) {
-    mbuf_append(&nc->recv_mbuf, buf, len);
-  } else if (nc->recv_mbuf.len == 0) {
-    /* Adopt buf as recv_mbuf's backing store. */
-    mbuf_free(&nc->recv_mbuf);
-    nc->recv_mbuf.buf = (char *) buf;
-    nc->recv_mbuf.size = nc->recv_mbuf.len = len;
-  } else {
-    mbuf_append(&nc->recv_mbuf, buf, len);
-    MG_FREE(buf);
+  if (n > 0) {
+    nc->recv_mbuf.len += n;
+    nc->last_io_time = (time_t) mg_time();
+#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
+    if (nc->mgr && nc->mgr->hexdump_file != NULL) {
+      mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_RECV);
+    }
+#endif
+    mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n);
+  } else if (n < 0) {
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
   }
-  mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &len);
-}
-
-void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len, int own) {
-  mg_recv_common(nc, buf, len, own);
+  return n;
 }
 
-void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
-                       union socket_address *sa, size_t sa_len) {
-  assert(nc->flags & MG_F_UDP);
-  DBG(("%p %u", nc, (unsigned int) len));
+static int mg_recv_udp(struct mg_connection *nc, char *buf, size_t len) {
+  int n = 0;
+  struct mg_connection *lc = nc;
+  union socket_address sa;
+  size_t sa_len = sizeof(sa);
+  n = nc->iface->vtable->udp_recv(lc, buf, len, &sa, &sa_len);
+  if (n < 0) {
+    lc->flags |= MG_F_CLOSE_IMMEDIATELY;
+    goto out;
+  }
   if (nc->flags & MG_F_LISTENING) {
-    struct mg_connection *lc = nc;
     /*
      * Do we have an existing connection for this source?
      * This is very inefficient for long connection lists.
      */
+    lc = nc;
     for (nc = mg_next(lc->mgr, NULL); nc != NULL; nc = mg_next(lc->mgr, nc)) {
-      if (memcmp(&nc->sa.sa, &sa->sa, sa_len) == 0 && nc->listener == lc) {
+      if (memcmp(&nc->sa.sa, &sa.sa, sa_len) == 0 && nc->listener == lc) {
         break;
       }
     }
@@ -566,7 +654,7 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
       if (nc != NULL) {
         nc->sock = lc->sock;
         nc->listener = lc;
-        nc->sa = *sa;
+        nc->sa = sa;
         nc->proto_handler = lc->proto_handler;
         nc->user_data = lc->user_data;
         nc->recv_mbuf_limit = lc->recv_mbuf_limit;
@@ -585,18 +673,88 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
         nc->flags |= MG_F_SEND_AND_CLOSE;
         mg_add_conn(lc->mgr, nc);
         mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
-      } else {
-        DBG(("OOM"));
-        /* No return here, we still need to drop on the floor */
       }
     }
   }
   if (nc != NULL) {
-    mg_recv_common(nc, buf, len, 1);
-  } else {
-    /* Drop on the floor. */
-    MG_FREE(buf);
+    DBG(("%p <- %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr),
+         ntohs(nc->sa.sin.sin_port)));
+    if (nc == lc) {
+      nc->recv_mbuf.len += n;
+    } else {
+      mbuf_append(&nc->recv_mbuf, buf, n);
+    }
+    mbuf_trim(&lc->recv_mbuf);
+    lc->last_io_time = nc->last_io_time = (time_t) mg_time();
+#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
+    if (nc->mgr && nc->mgr->hexdump_file != NULL) {
+      mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_RECV);
+    }
+#endif
+    mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n);
   }
+
+out:
+  mbuf_free(&lc->recv_mbuf);
+  return n;
+}
+
+void mg_if_can_send_cb(struct mg_connection *nc) {
+  int n = 0;
+  const char *buf = nc->send_mbuf.buf;
+  size_t len = nc->send_mbuf.len;
+
+  if (nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_CONNECTING)) {
+    return;
+  }
+  if (!(nc->flags & MG_F_UDP)) {
+    if (nc->flags & MG_F_LISTENING) return;
+    if (len > MG_TCP_IO_SIZE) len = MG_TCP_IO_SIZE;
+  }
+#if MG_ENABLE_SSL
+  if (nc->flags & MG_F_SSL) {
+    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
+      if (len > 0) {
+        n = mg_ssl_if_write(nc, buf, len);
+        DBG(("%p -> %d bytes (SSL)", nc, n));
+      }
+      if (n < 0) {
+        if (n == MG_SSL_WANT_WRITE) {
+          nc->flags |= MG_F_WANT_WRITE;
+          n = 0;
+        } else {
+          nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+        }
+      } else {
+        nc->flags &= ~MG_F_WANT_WRITE;
+      }
+    } else {
+      mg_ssl_handshake(nc);
+    }
+  } else
+#endif
+  {
+    if (nc->flags & MG_F_UDP) {
+      n = nc->iface->vtable->udp_send(nc, buf, len);
+    } else {
+      n = nc->iface->vtable->tcp_send(nc, buf, len);
+    }
+    DBG(("%p -> %d bytes", nc, n));
+  }
+
+#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
+  if (n > 0 && nc->mgr && nc->mgr->hexdump_file != NULL) {
+    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_SEND);
+  }
+#endif
+  if (n < 0) {
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+  } else if (n > 0) {
+    nc->last_io_time = (time_t) mg_time();
+    mbuf_remove(&nc->send_mbuf, n);
+    mbuf_trim(&nc->send_mbuf);
+  }
+  if (n != 0) mg_call(nc, NULL, nc->user_data, MG_EV_SEND, &n);
 }
 
 /*
@@ -608,8 +766,8 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
 MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,
                                                 int proto,
                                                 union socket_address *sa) {
-  DBG(("%p %s://%s:%hu", nc, proto == SOCK_DGRAM ? "udp" : "tcp",
-       inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));
+  LOG(LL_DEBUG, ("%p %s://%s:%hu", nc, proto == SOCK_DGRAM ? "udp" : "tcp",
+                 inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));
 
   nc->flags |= MG_F_CONNECTING;
   if (proto == SOCK_DGRAM) {
@@ -622,12 +780,21 @@ MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,
 }
 
 void mg_if_connect_cb(struct mg_connection *nc, int err) {
-  DBG(("%p connect, err=%d", nc, err));
+  LOG(LL_DEBUG,
+      ("%p %s://%s:%hu -> %d", nc, (nc->flags & MG_F_UDP ? "udp" : "tcp"),
+       inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port), err));
   nc->flags &= ~MG_F_CONNECTING;
   if (err != 0) {
     nc->flags |= MG_F_CLOSE_IMMEDIATELY;
   }
-  mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+#if MG_ENABLE_SSL
+  if (err == 0 && (nc->flags & MG_F_SSL)) {
+    mg_ssl_handshake(nc);
+  } else
+#endif
+  {
+    mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+  }
 }
 
 #if MG_ENABLE_ASYNC_RESOLVER
@@ -717,7 +884,8 @@ struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
 #endif
 
 #if MG_ENABLE_SSL
-  DBG(("%p %s %s,%s,%s", nc, address, (opts.ssl_cert ? opts.ssl_cert : "-"),
+  LOG(LL_DEBUG,
+      ("%p %s %s,%s,%s", nc, address, (opts.ssl_cert ? opts.ssl_cert : "-"),
        (opts.ssl_key ? opts.ssl_key : "-"),
        (opts.ssl_ca_cert ? opts.ssl_ca_cert : "-")));
 
@@ -963,7 +1131,7 @@ double mg_set_timer(struct mg_connection *c, double timestamp) {
   DBG(("%p %p %d -> %lu", c, c->priv_2, (c->flags & MG_F_RESOLVING ? 1 : 0),
        (unsigned long) timestamp));
   if ((c->flags & MG_F_RESOLVING) && c->priv_2 != NULL) {
-    ((struct mg_connection *) c->priv_2)->ev_timer_time = timestamp;
+    mg_set_timer((struct mg_connection *) c->priv_2, timestamp);
   }
   return result;
 }
diff --git a/src/mg_net_if.h b/src/mg_net_if.h
index 43eb2d994..cba58fb95 100644
--- a/src/mg_net_if.h
+++ b/src/mg_net_if.h
@@ -54,10 +54,12 @@ struct mg_iface_vtable {
   void (*connect_udp)(struct mg_connection *nc);
 
   /* Send functions for TCP and UDP. Sent data is copied before return. */
-  void (*tcp_send)(struct mg_connection *nc, const void *buf, size_t len);
-  void (*udp_send)(struct mg_connection *nc, const void *buf, size_t len);
+  int (*tcp_send)(struct mg_connection *nc, const void *buf, size_t len);
+  int (*udp_send)(struct mg_connection *nc, const void *buf, size_t len);
 
-  void (*recved)(struct mg_connection *nc, size_t len);
+  int (*tcp_recv)(struct mg_connection *nc, void *buf, size_t len);
+  int (*udp_recv)(struct mg_connection *nc, void *buf, size_t len,
+                  union socket_address *sa, size_t *sa_len);
 
   /* Perform interface-related connection initialization. Return 1 on ok. */
   int (*create_conn)(struct mg_connection *nc);
@@ -98,19 +100,15 @@ void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa,
 
 /* Callback invoked by connect methods. err = 0 -> ok, != 0 -> error. */
 void mg_if_connect_cb(struct mg_connection *nc, int err);
-/* Callback that reports that data has been put on the wire. */
-void mg_if_sent_cb(struct mg_connection *nc, int num_sent);
 /*
- * Receive callback.
- * if `own` is true, buf must be heap-allocated and ownership is transferred
- * to the core.
- * Core will acknowledge consumption by calling iface::recved.
+ * Callback that tells the core that data can be received.
+ * Core will use tcp/udp_recv to retrieve the data.
  */
-void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len, int own);
+void mg_if_can_recv_cb(struct mg_connection *nc);
+void mg_if_can_send_cb(struct mg_connection *nc);
 /*
  * Receive callback.
  * buf must be heap-allocated and ownership is transferred to the core.
- * Core will acknowledge consumption by calling iface::recved.
  */
 void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
                        union socket_address *sa, size_t sa_len);
@@ -118,10 +116,7 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
 /* void mg_if_close_conn(struct mg_connection *nc); */
 
 /* Deliver a POLL event to the connection. */
-void mg_if_poll(struct mg_connection *nc, time_t now);
-
-/* Deliver a TIMER event to the connection. */
-void mg_if_timer(struct mg_connection *c, double now);
+int mg_if_poll(struct mg_connection *nc, double now);
 
 #ifdef __cplusplus
 }
diff --git a/src/mg_net_if_socket.c b/src/mg_net_if_socket.c
index c13c300a4..c713e8458 100644
--- a/src/mg_net_if_socket.c
+++ b/src/mg_net_if_socket.c
@@ -9,14 +9,8 @@
 #include "mg_internal.h"
 #include "mg_util.h"
 
-#define MG_TCP_RECV_BUFFER_SIZE 1024
-#define MG_UDP_RECV_BUFFER_SIZE 1500
-
 static sock_t mg_open_listening_socket(union socket_address *sa, int type,
                                        int proto);
-#if MG_ENABLE_SSL
-static void mg_ssl_begin(struct mg_connection *nc);
-#endif
 
 void mg_set_non_blocking_mode(sock_t sock) {
 #ifdef _WIN32
@@ -85,27 +79,48 @@ int mg_socket_if_listen_tcp(struct mg_connection *nc,
   return 0;
 }
 
-int mg_socket_if_listen_udp(struct mg_connection *nc,
-                            union socket_address *sa) {
+static int mg_socket_if_listen_udp(struct mg_connection *nc,
+                                   union socket_address *sa) {
   sock_t sock = mg_open_listening_socket(sa, SOCK_DGRAM, 0);
   if (sock == INVALID_SOCKET) return (mg_get_errno() ? mg_get_errno() : 1);
   mg_sock_set(nc, sock);
   return 0;
 }
 
-void mg_socket_if_tcp_send(struct mg_connection *nc, const void *buf,
-                           size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
+static int mg_socket_if_tcp_send(struct mg_connection *nc, const void *buf,
+                                 size_t len) {
+  int n = (int) MG_SEND_FUNC(nc->sock, buf, len, 0);
+  if (n < 0 && !mg_is_error()) n = 0;
+  return n;
 }
 
-void mg_socket_if_udp_send(struct mg_connection *nc, const void *buf,
-                           size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
+static int mg_socket_if_udp_send(struct mg_connection *nc, const void *buf,
+                                 size_t len) {
+  int n = sendto(nc->sock, buf, len, 0, &nc->sa.sa, sizeof(nc->sa.sin));
+  if (n < 0 && !mg_is_error()) n = 0;
+  return n;
 }
 
-void mg_socket_if_recved(struct mg_connection *nc, size_t len) {
-  (void) nc;
-  (void) len;
+static int mg_socket_if_tcp_recv(struct mg_connection *nc, void *buf,
+                                 size_t len) {
+  int n = (int) MG_RECV_FUNC(nc->sock, buf, len, 0);
+  if (n == 0) {
+    /* Orderly shutdown of the socket, try flushing output. */
+    nc->flags |= MG_F_SEND_AND_CLOSE;
+  } else if (n < 0 && !mg_is_error()) {
+    n = 0;
+  }
+  return n;
+}
+
+static int mg_socket_if_udp_recv(struct mg_connection *nc, void *buf,
+                                 size_t len, union socket_address *sa,
+                                 size_t *sa_len) {
+  socklen_t sa_len_st = *sa_len;
+  int n = recvfrom(nc->sock, buf, len, 0, &sa->sa, &sa_len_st);
+  *sa_len = sa_len_st;
+  if (n < 0 && !mg_is_error()) n = 0;
+  return n;
 }
 
 int mg_socket_if_create_conn(struct mg_connection *nc) {
@@ -142,14 +157,7 @@ static int mg_accept_conn(struct mg_connection *lc) {
   DBG(("%p conn from %s:%d", nc, inet_ntoa(sa.sin.sin_addr),
        ntohs(sa.sin.sin_port)));
   mg_sock_set(nc, sock);
-#if MG_ENABLE_SSL
-  if (lc->flags & MG_F_SSL) {
-    if (mg_ssl_if_conn_accept(nc, lc) != MG_SSL_OK) mg_close_conn(nc);
-  } else
-#endif
-  {
-    mg_if_accept_tcp_cb(nc, &sa, sa_len);
-  }
+  mg_if_accept_tcp_cb(nc, &sa, sa_len);
   return 1;
 }
 
@@ -200,165 +208,6 @@ static sock_t mg_open_listening_socket(union socket_address *sa, int type,
   return sock;
 }
 
-static void mg_write_to_socket(struct mg_connection *nc) {
-  struct mbuf *io = &nc->send_mbuf;
-  int n = 0;
-
-#if MG_LWIP
-  /* With LWIP we don't know if the socket is ready */
-  if (io->len == 0) return;
-#endif
-
-  assert(io->len > 0);
-
-  if (nc->flags & MG_F_UDP) {
-    int n =
-        sendto(nc->sock, io->buf, io->len, 0, &nc->sa.sa, sizeof(nc->sa.sin));
-    DBG(("%p %d %d %d %s:%hu", nc, nc->sock, n, mg_get_errno(),
-         inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port)));
-    mg_if_sent_cb(nc, n);
-    return;
-  }
-
-#if MG_ENABLE_SSL
-  if (nc->flags & MG_F_SSL) {
-    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
-      n = mg_ssl_if_write(nc, io->buf, io->len);
-      DBG(("%p %d bytes -> %d (SSL)", nc, n, nc->sock));
-      if (n < 0) {
-        if (n != MG_SSL_WANT_READ && n != MG_SSL_WANT_WRITE) {
-          nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-        }
-        return;
-      } else {
-        /* Successful SSL operation, clear off SSL wait flags */
-        nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE);
-      }
-    } else {
-      mg_ssl_begin(nc);
-      return;
-    }
-  } else
-#endif
-  {
-    n = (int) MG_SEND_FUNC(nc->sock, io->buf, io->len, 0);
-    DBG(("%p %d bytes -> %d", nc, n, nc->sock));
-  }
-
-  mg_if_sent_cb(nc, n);
-}
-
-MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) {
-  size_t avail;
-  if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0;
-  avail = conn->recv_mbuf_limit - conn->recv_mbuf.len;
-  return avail > max ? max : avail;
-}
-
-static void mg_handle_tcp_read(struct mg_connection *conn) {
-  int n = 0;
-  char *buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE);
-
-  if (buf == NULL) {
-    DBG(("OOM"));
-    return;
-  }
-
-#if MG_ENABLE_SSL
-  if (conn->flags & MG_F_SSL) {
-    if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) {
-      /* SSL library may have more bytes ready to read than we ask to read.
-       * Therefore, read in a loop until we read everything. Without the loop,
-       * we skip to the next select() cycle which can just timeout. */
-      while ((n = mg_ssl_if_read(conn, buf, MG_TCP_RECV_BUFFER_SIZE)) > 0) {
-        DBG(("%p %d bytes <- %d (SSL)", conn, n, conn->sock));
-        mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */);
-        buf = NULL;
-        if (conn->flags & MG_F_CLOSE_IMMEDIATELY) break;
-        /* buf has been freed, we need a new one. */
-        buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE);
-        if (buf == NULL) break;
-      }
-      MG_FREE(buf);
-      if (n < 0 && n != MG_SSL_WANT_READ) conn->flags |= MG_F_CLOSE_IMMEDIATELY;
-    } else {
-      MG_FREE(buf);
-      mg_ssl_begin(conn);
-      return;
-    }
-  } else
-#endif
-  {
-    n = (int) MG_RECV_FUNC(conn->sock, buf,
-                           recv_avail_size(conn, MG_TCP_RECV_BUFFER_SIZE), 0);
-    DBG(("%p %d bytes (PLAIN) <- %d", conn, n, conn->sock));
-    if (n > 0) {
-      mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */);
-    } else {
-      MG_FREE(buf);
-    }
-    if (n == 0) {
-      /* Orderly shutdown of the socket, try flushing output. */
-      conn->flags |= MG_F_SEND_AND_CLOSE;
-    } else if (n < 0 && mg_is_error()) {
-      conn->flags |= MG_F_CLOSE_IMMEDIATELY;
-    }
-  }
-}
-
-static int mg_recvfrom(struct mg_connection *nc, union socket_address *sa,
-                       socklen_t *sa_len, char **buf) {
-  int n;
-  *buf = (char *) MG_MALLOC(MG_UDP_RECV_BUFFER_SIZE);
-  if (*buf == NULL) {
-    DBG(("Out of memory"));
-    return -ENOMEM;
-  }
-  n = recvfrom(nc->sock, *buf, MG_UDP_RECV_BUFFER_SIZE, 0, &sa->sa, sa_len);
-  if (n <= 0) {
-    DBG(("%p recvfrom: %s", nc, strerror(mg_get_errno())));
-    MG_FREE(*buf);
-  }
-  return n;
-}
-
-static void mg_handle_udp_read(struct mg_connection *nc) {
-  char *buf = NULL;
-  union socket_address sa;
-  socklen_t sa_len = sizeof(sa);
-  int n = mg_recvfrom(nc, &sa, &sa_len, &buf);
-  DBG(("%p %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr),
-       ntohs(nc->sa.sin.sin_port)));
-  mg_if_recv_udp_cb(nc, buf, n, &sa, sa_len);
-}
-
-#if MG_ENABLE_SSL
-static void mg_ssl_begin(struct mg_connection *nc) {
-  int server_side = (nc->listener != NULL);
-  enum mg_ssl_if_result res = mg_ssl_if_handshake(nc);
-  DBG(("%p %d res %d", nc, server_side, res));
-
-  if (res == MG_SSL_OK) {
-    nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
-    nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE);
-
-    if (server_side) {
-      union socket_address sa;
-      socklen_t sa_len = sizeof(sa);
-      (void) getpeername(nc->sock, &sa.sa, &sa_len);
-      mg_if_accept_tcp_cb(nc, &sa, sa_len);
-    } else {
-      mg_if_connect_cb(nc, 0);
-    }
-  } else if (res != MG_SSL_WANT_READ && res != MG_SSL_WANT_WRITE) {
-    if (!server_side) {
-      mg_if_connect_cb(nc, res);
-    }
-    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-  }
-}
-#endif /* MG_ENABLE_SSL */
-
 #define _MG_F_FD_CAN_READ 1
 #define _MG_F_FD_CAN_WRITE 1 << 1
 #define _MG_F_FD_ERROR 1 << 2
@@ -367,11 +216,13 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
   int worth_logging =
       fd_flags != 0 || (nc->flags & (MG_F_WANT_READ | MG_F_WANT_WRITE));
   if (worth_logging) {
-    DBG(("%p fd=%d fd_flags=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock,
+    DBG(("%p fd=%d fd_flags=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock,
          fd_flags, nc->flags, (int) nc->recv_mbuf.len,
          (int) nc->send_mbuf.len));
   }
 
+  if (!mg_if_poll(nc, now)) return;
+
   if (nc->flags & MG_F_CONNECTING) {
     if (fd_flags != 0) {
       int err = 0;
@@ -392,15 +243,7 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
        */
       err = nc->err;
 #endif
-#if MG_ENABLE_SSL
-      if ((nc->flags & MG_F_SSL) && err == 0) {
-        mg_ssl_begin(nc);
-      } else {
-        mg_if_connect_cb(nc, err);
-      }
-#else
       mg_if_connect_cb(nc, err);
-#endif
     } else if (nc->err != 0) {
       mg_if_connect_cb(nc, nc->err);
     }
@@ -408,7 +251,7 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
 
   if (fd_flags & _MG_F_FD_CAN_READ) {
     if (nc->flags & MG_F_UDP) {
-      mg_handle_udp_read(nc);
+      mg_if_can_recv_cb(nc);
     } else {
       if (nc->flags & MG_F_LISTENING) {
         /*
@@ -418,22 +261,16 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
          */
         mg_accept_conn(nc);
       } else {
-        mg_handle_tcp_read(nc);
+        mg_if_can_recv_cb(nc);
       }
     }
   }
 
-  if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
-    if ((fd_flags & _MG_F_FD_CAN_WRITE) && nc->send_mbuf.len > 0) {
-      mg_write_to_socket(nc);
-    }
-    mg_if_poll(nc, (time_t) now);
-    mg_if_timer(nc, now);
-  }
+  if (fd_flags & _MG_F_FD_CAN_WRITE) mg_if_can_send_cb(nc);
 
   if (worth_logging) {
-    DBG(("%p after fd=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock, nc->flags,
-         (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
+    DBG(("%p after fd=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock,
+         nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
   }
 }
 
@@ -547,8 +384,7 @@ time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) {
       }
 #endif
 
-      if (!(nc->flags & MG_F_WANT_WRITE) &&
-          nc->recv_mbuf.len < nc->recv_mbuf_limit &&
+      if (nc->recv_mbuf.len < nc->recv_mbuf_limit &&
           (!(nc->flags & MG_F_UDP) || nc->listener == NULL)) {
         mg_add_to_set(nc->sock, &read_set, &max_fd);
       }
@@ -619,14 +455,6 @@ time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) {
     mg_mgr_handle_conn(nc, fd_flags, now);
   }
 
-  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {
-    tmp = nc->next;
-    if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
-        (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) {
-      mg_close_conn(nc);
-    }
-  }
-
   return (time_t) now;
 }
 
@@ -735,7 +563,8 @@ void mg_socket_if_get_conn_addr(struct mg_connection *nc, int remote,
     mg_socket_if_connect_udp,                                           \
     mg_socket_if_tcp_send,                                              \
     mg_socket_if_udp_send,                                              \
-    mg_socket_if_recved,                                                \
+    mg_socket_if_tcp_recv,                                              \
+    mg_socket_if_udp_recv,                                              \
     mg_socket_if_create_conn,                                           \
     mg_socket_if_destroy_conn,                                          \
     mg_socket_if_sock_set,                                              \
diff --git a/src/mg_net_if_socks.c b/src/mg_net_if_socks.c
index c5661e97c..2a4214268 100644
--- a/src/mg_net_if_socks.c
+++ b/src/mg_net_if_socks.c
@@ -9,18 +9,35 @@ struct socksdata {
   char *proxy_addr;        /* HOST:PORT of the socks5 proxy server */
   struct mg_connection *s; /* Respective connection to the server */
   struct mg_connection *c; /* Connection to the client */
-  struct mbuf tmp;         /* Temporary buffer for sent data */
 };
 
 static void socks_if_disband(struct socksdata *d) {
   LOG(LL_DEBUG, ("disbanding proxy %p %p", d->c, d->s));
-  if (d->c) d->c->flags |= MG_F_SEND_AND_CLOSE;
-  if (d->s) d->s->flags |= MG_F_SEND_AND_CLOSE;
-  d->c = d->s = NULL;
+  if (d->c) {
+    d->c->flags |= MG_F_SEND_AND_CLOSE;
+    d->c->user_data = NULL;
+    d->c = NULL;
+  }
+  if (d->s) {
+    d->s->flags |= MG_F_SEND_AND_CLOSE;
+    d->s->user_data = NULL;
+    d->s = NULL;
+  }
+}
+
+static void socks_if_relay(struct mg_connection *s) {
+  struct socksdata *d = (struct socksdata *) s->user_data;
+  if (d == NULL || d->c == NULL || !(s->flags & MG_SOCKS_CONNECT_DONE) ||
+      d->s == NULL) {
+    return;
+  }
+  if (s->recv_mbuf.len > 0) mg_if_can_recv_cb(d->c);
+  if (d->c->send_mbuf.len > 0 && s->send_mbuf.len == 0) mg_if_can_send_cb(d->c);
 }
 
 static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) {
   struct socksdata *d = (struct socksdata *) c->user_data;
+  if (d == NULL) return;
   if (ev == MG_EV_CONNECT) {
     int res = *(int *) ev_data;
     if (res == 0) {
@@ -53,6 +70,7 @@ static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) {
       memcpy(buf + 4, &d->c->sa.sin.sin_addr, 4);
       memcpy(buf + 8, &d->c->sa.sin.sin_port, 2);
       mg_send(c, buf, sizeof(buf));
+      LOG(LL_DEBUG, ("%p Sent connect request", c));
     }
     /* Process connect request */
     if ((c->flags & MG_SOCKS_HANDSHAKE_DONE) &&
@@ -65,17 +83,12 @@ static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) {
       }
       mbuf_remove(&c->recv_mbuf, 10);
       c->flags |= MG_SOCKS_CONNECT_DONE;
-      /* Connected. Move sent data from client, if any, to server */
-      if (d->s && d->c) {
-        mbuf_append(&d->s->send_mbuf, d->tmp.buf, d->tmp.len);
-        mbuf_free(&d->tmp);
-      }
-    }
-    /* All flags are set, we're in relay mode */
-    if ((c->flags & MG_SOCKS_CONNECT_DONE) && d->c && d->s) {
-      mbuf_append(&d->c->recv_mbuf, d->s->recv_mbuf.buf, d->s->recv_mbuf.len);
-      mbuf_remove(&d->s->recv_mbuf, d->s->recv_mbuf.len);
+      LOG(LL_DEBUG, ("%p Connect done %p", c, d->c));
+      mg_if_connect_cb(d->c, 0);
     }
+    socks_if_relay(c);
+  } else if (ev == MG_EV_SEND || ev == MG_EV_POLL) {
+    socks_if_relay(c);
   }
 }
 
@@ -85,7 +98,7 @@ static void mg_socks_if_connect_tcp(struct mg_connection *c,
   d->c = c;
   d->s = mg_connect(c->mgr, d->proxy_addr, socks_if_handler);
   d->s->user_data = d;
-  LOG(LL_DEBUG, ("%p %s", c, d->proxy_addr));
+  LOG(LL_DEBUG, ("%p %s %p %p", c, d->proxy_addr, d, d->s));
   (void) sa;
 }
 
@@ -107,29 +120,44 @@ static int mg_socks_if_listen_udp(struct mg_connection *c,
   return -1;
 }
 
-static void mg_socks_if_tcp_send(struct mg_connection *c, const void *buf,
-                                 size_t len) {
+static int mg_socks_if_tcp_send(struct mg_connection *c, const void *buf,
+                                size_t len) {
+  int res;
   struct socksdata *d = (struct socksdata *) c->iface->data;
-  LOG(LL_DEBUG, ("%p -> %p %d %d", c, buf, (int) len, (int) c->send_mbuf.len));
-  if (d && d->s && d->s->flags & MG_SOCKS_CONNECT_DONE) {
-    mbuf_append(&d->s->send_mbuf, d->tmp.buf, d->tmp.len);
-    mbuf_append(&d->s->send_mbuf, buf, len);
-    mbuf_free(&d->tmp);
-  } else {
-    mbuf_append(&d->tmp, buf, len);
-  }
+  if (d->s == NULL) return -1;
+  res = (int) mbuf_append(&d->s->send_mbuf, buf, len);
+  DBG(("%p -> %d -> %p", c, res, d->s));
+  return res;
 }
 
-static void mg_socks_if_udp_send(struct mg_connection *c, const void *buf,
-                                 size_t len) {
+static int mg_socks_if_udp_send(struct mg_connection *c, const void *buf,
+                                size_t len) {
   (void) c;
   (void) buf;
   (void) len;
+  return -1;
+}
+
+int mg_socks_if_tcp_recv(struct mg_connection *c, void *buf, size_t len) {
+  struct socksdata *d = (struct socksdata *) c->iface->data;
+  if (d->s == NULL) return -1;
+  if (len > d->s->recv_mbuf.len) len = d->s->recv_mbuf.len;
+  if (len > 0) {
+    memcpy(buf, d->s->recv_mbuf.buf, len);
+    mbuf_remove(&d->s->recv_mbuf, len);
+  }
+  DBG(("%p <- %d <- %p", c, (int) len, d->s));
+  return len;
 }
 
-static void mg_socks_if_recved(struct mg_connection *c, size_t len) {
+int mg_socks_if_udp_recv(struct mg_connection *c, void *buf, size_t len,
+                         union socket_address *sa, size_t *sa_len) {
   (void) c;
+  (void) buf;
   (void) len;
+  (void) sa;
+  (void) sa_len;
+  return -1;
 }
 
 static int mg_socks_if_create_conn(struct mg_connection *c) {
@@ -158,7 +186,6 @@ static void mg_socks_if_free(struct mg_iface *iface) {
   LOG(LL_DEBUG, ("%p", iface));
   if (d != NULL) {
     socks_if_disband(d);
-    mbuf_free(&d->tmp);
     MG_FREE(d->proxy_addr);
     MG_FREE(d);
     iface->data = NULL;
@@ -189,14 +216,15 @@ static void mg_socks_if_get_conn_addr(struct mg_connection *c, int remote,
 }
 
 const struct mg_iface_vtable mg_socks_iface_vtable = {
-    mg_socks_if_init,        mg_socks_if_free,
-    mg_socks_if_add_conn,    mg_socks_if_remove_conn,
-    mg_socks_if_poll,        mg_socks_if_listen_tcp,
-    mg_socks_if_listen_udp,  mg_socks_if_connect_tcp,
-    mg_socks_if_connect_udp, mg_socks_if_tcp_send,
-    mg_socks_if_udp_send,    mg_socks_if_recved,
-    mg_socks_if_create_conn, mg_socks_if_destroy_conn,
-    mg_socks_if_sock_set,    mg_socks_if_get_conn_addr,
+    mg_socks_if_init,          mg_socks_if_free,
+    mg_socks_if_add_conn,      mg_socks_if_remove_conn,
+    mg_socks_if_poll,          mg_socks_if_listen_tcp,
+    mg_socks_if_listen_udp,    mg_socks_if_connect_tcp,
+    mg_socks_if_connect_udp,   mg_socks_if_tcp_send,
+    mg_socks_if_udp_send,      mg_socks_if_tcp_recv,
+    mg_socks_if_udp_recv,      mg_socks_if_create_conn,
+    mg_socks_if_destroy_conn,  mg_socks_if_sock_set,
+    mg_socks_if_get_conn_addr,
 };
 
 struct mg_iface *mg_socks_mk_iface(struct mg_mgr *mgr, const char *proxy_addr) {
diff --git a/src/mg_resolv.c b/src/mg_resolv.c
index e445ac50e..b8b73a994 100644
--- a/src/mg_resolv.c
+++ b/src/mg_resolv.c
@@ -148,7 +148,6 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev,
   time_t now = (time_t) mg_time();
   struct mg_resolve_async_request *req;
   struct mg_dns_message *msg;
-  int first = 0;
 #if !MG_ENABLE_CALLBACK_USERDATA
   void *user_data = nc->user_data;
 #endif
@@ -162,17 +161,16 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev,
   }
 
   switch (ev) {
-    case MG_EV_CONNECT:
-      /* don't depend on timer not being at epoch for sending out first req */
-      first = 1;
-    /* fallthrough */
     case MG_EV_POLL:
       if (req->retries > req->max_retries) {
         req->err = MG_RESOLVE_EXCEEDED_RETRY_COUNT;
         nc->flags |= MG_F_CLOSE_IMMEDIATELY;
         break;
       }
-      if (first || now - req->last_time >= req->timeout) {
+      if (nc->flags & MG_F_CONNECTING) break;
+    /* fallthrough */
+    case MG_EV_CONNECT:
+      if (req->retries == 0 || now - req->last_time >= req->timeout) {
         mg_send_dns_query(nc, req->name, req->query);
         req->last_time = now;
         req->retries++;
diff --git a/src/mg_ssl_if_mbedtls.c b/src/mg_ssl_if_mbedtls.c
index 9bf07fa95..38ba1869b 100644
--- a/src/mg_ssl_if_mbedtls.c
+++ b/src/mg_ssl_if_mbedtls.c
@@ -7,9 +7,12 @@
 
 #include <mbedtls/debug.h>
 #include <mbedtls/ecp.h>
+#include <mbedtls/net.h>
 #include <mbedtls/platform.h>
 #include <mbedtls/ssl.h>
+#include <mbedtls/ssl_internal.h>
 #include <mbedtls/x509_crt.h>
+#include <mbedtls/version.h>
 
 static void mg_ssl_mbed_log(void *ctx, int level, const char *file, int line,
                             const char *str) {
@@ -19,6 +22,8 @@ static void mg_ssl_mbed_log(void *ctx, int level, const char *file, int line,
       cs_level = LL_ERROR;
       break;
     case 2:
+      cs_level = LL_INFO;
+      break;
     case 3:
       cs_level = LL_DEBUG;
       break;
@@ -38,12 +43,14 @@ struct mg_ssl_if_ctx {
   mbedtls_pk_context *key;
   mbedtls_x509_crt *ca_cert;
   struct mbuf cipher_suites;
+  size_t saved_len;
 };
 
 /* Must be provided by the platform. ctx is struct mg_connection. */
 extern int mg_ssl_if_mbed_random(void *ctx, unsigned char *buf, size_t len);
 
 void mg_ssl_if_init() {
+  LOG(LL_INFO, ("%s", MBEDTLS_VERSION_STRING_FULL));
 }
 
 enum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc,
@@ -164,40 +171,41 @@ enum mg_ssl_if_result mg_ssl_if_conn_init(
   return MG_SSL_OK;
 }
 
-#if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL
-int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len);
-int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len);
-#else
-static int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len) {
+static int mg_ssl_if_mbed_send(void *ctx, const unsigned char *buf,
+                               size_t len) {
   struct mg_connection *nc = (struct mg_connection *) ctx;
-  int n = (int) MG_SEND_FUNC(nc->sock, buf, len, 0);
-  LOG(LL_DEBUG, ("%p %d -> %d", nc, (int) len, n));
-  if (n >= 0) return n;
-  n = mg_get_errno();
-  return ((n == EAGAIN || n == EINPROGRESS) ? MBEDTLS_ERR_SSL_WANT_WRITE : -1);
+  int n = nc->iface->vtable->tcp_send(nc, buf, len);
+  if (n > 0) return n;
+  if (n == 0) return MBEDTLS_ERR_SSL_WANT_WRITE;
+  return MBEDTLS_ERR_NET_SEND_FAILED;
 }
 
-static int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len) {
+static int mg_ssl_if_mbed_recv(void *ctx, unsigned char *buf, size_t len) {
   struct mg_connection *nc = (struct mg_connection *) ctx;
-  int n = (int) MG_RECV_FUNC(nc->sock, buf, len, 0);
-  LOG(LL_DEBUG, ("%p %d <- %d", nc, (int) len, n));
-  if (n >= 0) return n;
-  n = mg_get_errno();
-  return ((n == EAGAIN || n == EINPROGRESS) ? MBEDTLS_ERR_SSL_WANT_READ : -1);
+  int n = nc->iface->vtable->tcp_recv(nc, buf, len);
+  if (n > 0) return n;
+  if (n == 0) return MBEDTLS_ERR_SSL_WANT_READ;
+  return MBEDTLS_ERR_NET_RECV_FAILED;
 }
-#endif
 
 static enum mg_ssl_if_result mg_ssl_if_mbed_err(struct mg_connection *nc,
                                                 int ret) {
-  if (ret == MBEDTLS_ERR_SSL_WANT_READ) return MG_SSL_WANT_READ;
-  if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) return MG_SSL_WANT_WRITE;
-  if (ret !=
-      MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { /* CLOSE_NOTIFY = Normal shutdown */
-    LOG(LL_ERROR, ("%p SSL error: %d", nc, ret));
+  enum mg_ssl_if_result res = MG_SSL_OK;
+  if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
+    res = MG_SSL_WANT_READ;
+  } else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+    res = MG_SSL_WANT_WRITE;
+  } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
+    LOG(LL_DEBUG, ("%p TLS connection closed by peer", nc));
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+    res = MG_SSL_OK;
+  } else {
+    LOG(LL_ERROR, ("%p mbedTLS error: -0x%04x", nc, -ret));
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+    res = MG_SSL_ERROR;
   }
   nc->err = ret;
-  nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-  return MG_SSL_ERROR;
+  return res;
 }
 
 static void mg_ssl_if_mbed_free_certs_and_keys(struct mg_ssl_if_ctx *ctx) {
@@ -228,7 +236,8 @@ enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {
   int err;
   /* If bio is not yet set, do it now. */
   if (ctx->ssl->p_bio == NULL) {
-    mbedtls_ssl_set_bio(ctx->ssl, nc, ssl_socket_send, ssl_socket_recv, NULL);
+    mbedtls_ssl_set_bio(ctx->ssl, nc, mg_ssl_if_mbed_send, mg_ssl_if_mbed_recv,
+                        NULL);
   }
   err = mbedtls_ssl_handshake(ctx->ssl);
   if (err != 0) return mg_ssl_if_mbed_err(nc, err);
@@ -254,20 +263,35 @@ enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {
   return MG_SSL_OK;
 }
 
-int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t buf_size) {
+int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t len) {
   struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
-  int n = mbedtls_ssl_read(ctx->ssl, (unsigned char *) buf, buf_size);
-  DBG(("%p %d -> %d", nc, (int) buf_size, n));
+  int n = mbedtls_ssl_read(ctx->ssl, (unsigned char *) buf, len);
+  DBG(("%p %d -> %d", nc, (int) len, n));
   if (n < 0) return mg_ssl_if_mbed_err(nc, n);
   if (n == 0) nc->flags |= MG_F_CLOSE_IMMEDIATELY;
   return n;
 }
 
-int mg_ssl_if_write(struct mg_connection *nc, const void *data, size_t len) {
+int mg_ssl_if_write(struct mg_connection *nc, const void *buf, size_t len) {
   struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
-  int n = mbedtls_ssl_write(ctx->ssl, (const unsigned char *) data, len);
-  DBG(("%p %d -> %d", nc, (int) len, n));
-  if (n < 0) return mg_ssl_if_mbed_err(nc, n);
+  /* Per mbedTLS docs, if write returns WANT_READ or WANT_WRITE, the operation
+   * should be retried with the same data and length.
+   * Here we assume that the data being pushed will remain the same but the
+   * amount may grow between calls so we save the length that was used and
+   * retry. The assumption being that the data itself won't change and won't
+   * be removed. */
+  size_t l = len;
+  if (ctx->saved_len > 0 && ctx->saved_len < l) l = ctx->saved_len;
+  int n = mbedtls_ssl_write(ctx->ssl, (const unsigned char *) buf, l);
+  DBG(("%p %d,%d,%d -> %d", nc, (int) len, (int) ctx->saved_len, (int) l, n));
+  if (n < 0) {
+    if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE) {
+      ctx->saved_len = len;
+    }
+    return mg_ssl_if_mbed_err(nc, n);
+  } else if (n > 0) {
+    ctx->saved_len = 0;
+  }
   return n;
 }
 
diff --git a/test/unit_test.c b/test/unit_test.c
index b6f95989a..446217b1c 100644
--- a/test/unit_test.c
+++ b/test/unit_test.c
@@ -1145,7 +1145,7 @@ static const char *test_timer(void) {
   ASSERT((c = mg_connect(&m, "awful.sad:1234", ev_timer_handler)) != NULL);
   c->user_data = &n;
   mg_set_timer(c, 1);
-  mg_mgr_poll(&m, 1);
+  poll_until(&m, 1, c_int_eq, &n, (void *) 101);
   ASSERT_EQ(n, 101);
 
   mg_mgr_free(&m);
@@ -2019,6 +2019,7 @@ static const char *test_http(void) {
   ASSERT((nc = mg_connect(&mgr, local_addr, cb7)) != NULL);
   mg_set_protocol_http_websocket(nc);
   nc->user_data = status;
+  mbuf_resize(&nc->recv_mbuf, 10000000);
 
   /* Wine and GDB set argv0 to full path: strip the dir component */
   if ((this_binary = strrchr(g_argv_0, '\\')) != NULL) {
@@ -5454,12 +5455,14 @@ static const char *test_socks(void) {
     this_binary = g_argv_0;
   }
   mg_printf(c, "GET /%s HTTP/1.0\n\n", this_binary);
+  mbuf_resize(&c->recv_mbuf, 10000000);
 
   /* Run event loop. Use more cycles to let file download complete. */
-  poll_until(&mgr, 5, c_str_ne, status, (void *) "");
-  mg_mgr_free(&mgr);
+  poll_until(&mgr, 10, c_str_ne, status, (void *) "");
   ASSERT_STREQ(status, "success");
 
+  mg_mgr_free(&mgr);
+
   return NULL;
 }
 #endif
-- 
GitLab