diff --git a/mongoose.c b/mongoose.c
index 24ba9916b86542d7a7ccdeda456f73f5adef569c..2d03840f627b4ef8556d6924fc79249f0ee6e83c 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -119,6 +119,10 @@ MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm);
 extern void *(*test_malloc)(size_t size);
 extern void *(*test_calloc)(size_t count, size_t size);
 
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
 #endif /* CS_MONGOOSE_SRC_INTERNAL_H_ */
 #ifdef MG_MODULE_LINES
 #line 1 "common/cs_dbg.h"
@@ -11140,3 +11144,850 @@ void sl_restart_cb(struct mg_mgr *mgr) {
 }
 
 #endif /* !defined(MG_DISABLE_SOCKET_IF) && defined(MG_SOCKET_SIMPLELINK) */
+#ifdef MG_MODULE_LINES
+#line 1 "common/platforms/lwip/mg_lwip_net_if.h"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_PLATFORMS_LWIP_MG_NET_IF_LWIP_H_
+#define CS_COMMON_PLATFORMS_LWIP_MG_NET_IF_LWIP_H_
+
+#ifdef MG_NET_IF_LWIP
+
+#include <inttypes.h>
+
+struct mg_lwip_conn_state {
+  union {
+    struct tcp_pcb *tcp;
+    struct udp_pcb *udp;
+  } pcb;
+  err_t err;
+  size_t num_sent; /* Number of acknowledged bytes to be reported to the core */
+  struct pbuf *rx_chain; /* Chain of incoming data segments. */
+  size_t rx_offset; /* Offset within the first pbuf (if partially consumed) */
+  /* Last SSL write size, for retries. */
+  int last_ssl_write_size;
+};
+
+enum mg_sig_type {
+  MG_SIG_CONNECT_RESULT = 1, /* struct mg_connection* */
+  MG_SIG_SENT_CB = 2,        /* struct mg_connection* */
+  MG_SIG_CLOSE_CONN = 3,     /* struct mg_connection* */
+  MG_SIG_TOMBSTONE = 4,
+};
+
+void mg_lwip_post_signal(enum mg_sig_type sig, struct mg_connection *nc);
+
+/* To be implemented by the platform. */
+void mg_lwip_mgr_schedule_poll(struct mg_mgr *mgr);
+
+#endif /* MG_NET_IF_LWIP */
+
+#endif /* CS_COMMON_PLATFORMS_LWIP_MG_NET_IF_LWIP_H_ */
+#ifdef MG_MODULE_LINES
+#line 1 "common/platforms/lwip/mg_lwip_net_if.c"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifdef MG_NET_IF_LWIP
+
+#include <lwip/pbuf.h>
+#include <lwip/tcp.h>
+#include <lwip/tcp_impl.h>
+#include <lwip/udp.h>
+
+/* Amalgamated: #include "common/cs_dbg.h" */
+
+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_set_keepalive_params(struct mg_connection *nc, int idle,
+                                  int interval, int count) {
+  if (nc->sock == INVALID_SOCKET || nc->flags & MG_F_UDP) {
+    return;
+  }
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  struct tcp_pcb *tpcb = cs->pcb.tcp;
+  if (idle > 0 && interval > 0 && count > 0) {
+    tpcb->keep_idle = idle * 1000;
+    tpcb->keep_intvl = interval * 1000;
+    tpcb->keep_cnt = count;
+    tpcb->so_options |= SOF_KEEPALIVE;
+  } else {
+    tpcb->so_options &= ~SOF_KEEPALIVE;
+  }
+}
+
+static err_t mg_lwip_tcp_conn_cb(void *arg, struct tcp_pcb *tpcb, err_t err) {
+  struct mg_connection *nc = (struct mg_connection *) arg;
+  DBG(("%p connect to %s:%u = %d", nc, ipaddr_ntoa(&tpcb->remote_ip),
+       tpcb->remote_port, err));
+  if (nc == NULL) {
+    tcp_abort(tpcb);
+    return ERR_ARG;
+  }
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  cs->err = err;
+  if (err == 0) mg_lwip_set_keepalive_params(nc, 60, 10, 6);
+#ifdef SSL_KRYPTON
+  if (err == 0 && nc->ssl != NULL) {
+    SSL_set_fd(nc->ssl, (intptr_t) nc);
+    mg_lwip_ssl_do_hs(nc);
+  } else
+#endif
+  {
+    mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);
+  }
+  return ERR_OK;
+}
+
+static void mg_lwip_tcp_error_cb(void *arg, err_t err) {
+  struct mg_connection *nc = (struct mg_connection *) arg;
+  DBG(("%p conn error %d", nc, err));
+  if (nc == NULL) return;
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  cs->pcb.tcp = NULL; /* Has already been deallocated */
+  if (nc->flags & MG_F_CONNECTING) {
+    cs->err = err;
+    mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);
+  } else {
+    mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
+  }
+}
+
+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));
+  if (p == NULL) {
+    if (nc != NULL) {
+      mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
+    } else {
+      /* Tombstoned connection, do nothing. */
+    }
+    return ERR_OK;
+  } else if (nc == NULL) {
+    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.
+   */
+  if (p->next != NULL) {
+    struct pbuf *q = p->next;
+    for (; q != NULL; q = q->next) pbuf_ref(q);
+  }
+  if (cs->rx_chain == NULL) {
+    cs->rx_chain = p;
+    cs->rx_offset = 0;
+  } 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. */
+      struct pbuf *np = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
+      if (np != NULL) {
+        pbuf_copy(np, p);
+        pbuf_free(p);
+        p = np;
+      }
+    }
+    pbuf_chain(cs->rx_chain, p);
+  }
+
+#ifdef SSL_KRYPTON
+  if (nc->ssl != NULL) {
+    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
+      mg_lwip_ssl_recv(nc);
+    } else {
+      mg_lwip_ssl_do_hs(nc);
+    }
+    return ERR_OK;
+  }
+#endif
+
+  while (cs->rx_chain != NULL) {
+    struct pbuf *seg = cs->rx_chain;
+    size_t len = (seg->len - cs->rx_offset);
+    char *data = (char *) malloc(len);
+    if (data == NULL) {
+      DBG(("OOM"));
+      return ERR_MEM;
+    }
+    pbuf_copy_partial(seg, data, len, cs->rx_offset);
+    mg_if_recv_tcp_cb(nc, data, len); /* callee takes over data */
+    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;
+    }
+  }
+
+  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,
+                                 u16_t num_sent) {
+  struct mg_connection *nc = (struct mg_connection *) arg;
+  DBG(("%p %p %u", nc, tpcb, num_sent));
+  if (nc == NULL) {
+    tcp_abort(tpcb);
+    return ERR_ABRT;
+  }
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  cs->num_sent += num_sent;
+
+  mg_lwip_post_signal(MG_SIG_SENT_CB, nc);
+  return ERR_OK;
+}
+
+void mg_if_connect_tcp(struct mg_connection *nc,
+                       const union socket_address *sa) {
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  struct tcp_pcb *tpcb = tcp_new();
+  cs->pcb.tcp = tpcb;
+  ip_addr_t *ip = (ip_addr_t *) &sa->sin.sin_addr.s_addr;
+  u16_t port = ntohs(sa->sin.sin_port);
+  tcp_arg(tpcb, nc);
+  tcp_err(tpcb, mg_lwip_tcp_error_cb);
+  tcp_sent(tpcb, mg_lwip_tcp_sent_cb);
+  tcp_recv(tpcb, mg_lwip_tcp_recv_cb);
+  cs->err = tcp_bind(tpcb, IP_ADDR_ANY, 0 /* any port */);
+  DBG(("%p tcp_bind = %d", nc, cs->err));
+  if (cs->err != ERR_OK) {
+    mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);
+    return;
+  }
+  cs->err = tcp_connect(tpcb, ip, port, mg_lwip_tcp_conn_cb);
+  DBG(("%p tcp_connect %p = %d", nc, tpcb, cs->err));
+  if (cs->err != ERR_OK) {
+    mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);
+    return;
+  }
+}
+
+static void mg_lwip_udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p,
+                                ip_addr_t *addr, u16_t port) {
+  struct mg_connection *nc = (struct mg_connection *) arg;
+  size_t len = p->len;
+  char *data = (char *) malloc(len);
+  union socket_address sa;
+  (void) pcb;
+  DBG(("%p %s:%u %u", nc, ipaddr_ntoa(addr), port, p->len));
+  if (data == NULL) {
+    DBG(("OOM"));
+    pbuf_free(p);
+    return;
+  }
+  sa.sin.sin_addr.s_addr = addr->addr;
+  sa.sin.sin_port = htons(port);
+  pbuf_copy_partial(p, data, len, 0);
+  pbuf_free(p);
+  mg_if_recv_udp_cb(nc, data, len, &sa, sizeof(sa.sin));
+}
+
+void mg_if_connect_udp(struct mg_connection *nc) {
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  struct udp_pcb *upcb = udp_new();
+  cs->err = udp_bind(upcb, IP_ADDR_ANY, 0 /* any port */);
+  DBG(("%p udp_bind %p = %d", nc, upcb, cs->err));
+  if (cs->err == ERR_OK) {
+    udp_recv(upcb, mg_lwip_udp_recv_cb, nc);
+    cs->pcb.udp = upcb;
+  } else {
+    udp_remove(upcb);
+  }
+  mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);
+}
+
+void mg_lwip_accept_conn(struct mg_connection *nc, struct tcp_pcb *tpcb) {
+  union socket_address sa;
+  sa.sin.sin_addr.s_addr = tpcb->remote_ip.addr;
+  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) {
+  struct mg_connection *lc = (struct mg_connection *) arg;
+  (void) err;
+  DBG(("%p conn %p from %s:%u", lc, newtpcb, ipaddr_ntoa(&newtpcb->remote_ip),
+       newtpcb->remote_port));
+  struct mg_connection *nc = mg_if_accept_new_conn(lc);
+  if (nc == NULL) {
+    tcp_abort(newtpcb);
+    return ERR_ABRT;
+  }
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  cs->pcb.tcp = newtpcb;
+  tcp_arg(newtpcb, nc);
+  tcp_err(newtpcb, mg_lwip_tcp_error_cb);
+  tcp_sent(newtpcb, mg_lwip_tcp_sent_cb);
+  tcp_recv(newtpcb, mg_lwip_tcp_recv_cb);
+  mg_lwip_set_keepalive_params(nc, 60, 10, 6);
+#ifdef SSL_KRYPTON
+  if (lc->ssl_ctx != NULL) {
+    nc->ssl = SSL_new(lc->ssl_ctx);
+    if (nc->ssl == NULL || SSL_set_fd(nc->ssl, (intptr_t) nc) != 1) {
+      LOG(LL_ERROR, ("SSL error"));
+      tcp_close(newtpcb);
+    }
+  } else
+#endif
+  {
+    mg_lwip_accept_conn(nc, newtpcb);
+  }
+  return ERR_OK;
+}
+
+int mg_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  struct tcp_pcb *tpcb = tcp_new();
+  ip_addr_t *ip = (ip_addr_t *) &sa->sin.sin_addr.s_addr;
+  u16_t port = ntohs(sa->sin.sin_port);
+  cs->err = tcp_bind(tpcb, ip, port);
+  DBG(("%p tcp_bind(%s:%u) = %d", nc, ipaddr_ntoa(ip), port, cs->err));
+  if (cs->err != ERR_OK) {
+    tcp_close(tpcb);
+    return -1;
+  }
+  tcp_arg(tpcb, nc);
+  tpcb = tcp_listen(tpcb);
+  cs->pcb.tcp = tpcb;
+  tcp_accept(tpcb, mg_lwip_accept_cb);
+  return 0;
+}
+
+int mg_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  struct udp_pcb *upcb = udp_new();
+  ip_addr_t *ip = (ip_addr_t *) &sa->sin.sin_addr.s_addr;
+  u16_t port = ntohs(sa->sin.sin_port);
+  cs->err = udp_bind(upcb, ip, port);
+  DBG(("%p udb_bind(%s:%u) = %d", nc, ipaddr_ntoa(ip), port, cs->err));
+  if (cs->err != ERR_OK) {
+    udp_remove(upcb);
+    return -1;
+  }
+  udp_recv(upcb, mg_lwip_udp_recv_cb, nc);
+  cs->pcb.udp = upcb;
+  return 0;
+}
+
+int mg_lwip_tcp_write(struct mg_connection *nc, const void *data,
+                      uint16_t len) {
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  struct tcp_pcb *tpcb = cs->pcb.tcp;
+  len = MIN(tpcb->mss, MIN(len, tpcb->snd_buf));
+  if (len == 0) {
+    DBG(("%p no buf avail %u %u %u %p %p", tpcb, tpcb->acked, tpcb->snd_buf,
+         tpcb->snd_queuelen, tpcb->unsent, tpcb->unacked));
+    tcp_output(tpcb);
+    return 0;
+  }
+  err_t err = tcp_write(tpcb, data, len, TCP_WRITE_FLAG_COPY);
+  tcp_output(tpcb);
+  DBG(("%p tcp_write %u = %d", tpcb, len, err));
+  if (err != ERR_OK) {
+    /*
+     * We ignore ERR_MEM because memory will be freed up when the data is sent
+     * and we'll retry.
+     */
+    return (err == ERR_MEM ? 0 : -1);
+  }
+  return len;
+}
+
+static void mg_lwip_send_more(struct mg_connection *nc) {
+  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;
+  }
+  int num_written = 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_written));
+  if (num_written == 0) return;
+  if (num_written < 0) {
+    mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
+  }
+  mbuf_remove(&nc->send_mbuf, num_written);
+  mbuf_trim(&nc->send_mbuf);
+}
+
+void mg_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_if_udp_send(struct mg_connection *nc, const void *buf, size_t len) {
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  if (nc->sock == INVALID_SOCKET || 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;
+  }
+  struct udp_pcb *upcb = cs->pcb.udp;
+  struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
+  ip_addr_t *ip = (ip_addr_t *) &nc->sa.sin.sin_addr.s_addr;
+  u16_t port = ntohs(nc->sa.sin.sin_port);
+  memcpy(p->payload, buf, len);
+  cs->err = udp_sendto(upcb, p, (ip_addr_t *) ip, port);
+  DBG(("%p udp_sendto = %d", nc, cs->err));
+  pbuf_free(p);
+  if (cs->err != ERR_OK) {
+    mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
+  } else {
+    cs->num_sent += len;
+    mg_lwip_post_signal(MG_SIG_SENT_CB, nc);
+  }
+}
+
+void mg_if_recved(struct mg_connection *nc, size_t len) {
+  if (nc->flags & MG_F_UDP) return;
+  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;
+  }
+  DBG(("%p %p %u", nc, cs->pcb.tcp, len));
+  /* Currently SSL acknowledges data immediately.
+   * TODO(rojer): Find a way to propagate mg_if_recved. */
+  if (nc->ssl == NULL) {
+    tcp_recved(cs->pcb.tcp, len);
+  }
+  mbuf_trim(&nc->recv_mbuf);
+}
+
+int mg_if_create_conn(struct mg_connection *nc) {
+  struct mg_lwip_conn_state *cs =
+      (struct mg_lwip_conn_state *) calloc(1, sizeof(*cs));
+  if (cs == NULL) return 0;
+  nc->sock = (intptr_t) cs;
+  return 1;
+}
+
+void mg_if_destroy_conn(struct mg_connection *nc) {
+  if (nc->sock == INVALID_SOCKET) return;
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  if (!(nc->flags & MG_F_UDP)) {
+    struct tcp_pcb *tpcb = cs->pcb.tcp;
+    if (tpcb != NULL) {
+      tcp_arg(tpcb, NULL);
+      DBG(("%p tcp_close %p", nc, tpcb));
+      tcp_arg(tpcb, NULL);
+      tcp_close(tpcb);
+    }
+    while (cs->rx_chain != NULL) {
+      struct pbuf *seg = cs->rx_chain;
+      cs->rx_chain = pbuf_dechain(cs->rx_chain);
+      pbuf_free(seg);
+    }
+    memset(cs, 0, sizeof(*cs));
+    free(cs);
+  } else if (nc->listener == NULL) {
+    /* Only close outgoing UDP pcb or listeners. */
+    struct udp_pcb *upcb = cs->pcb.udp;
+    if (upcb != NULL) {
+      DBG(("%p udp_remove %p", nc, upcb));
+      udp_remove(upcb);
+    }
+    memset(cs, 0, sizeof(*cs));
+    free(cs);
+  }
+  nc->sock = INVALID_SOCKET;
+}
+
+void mg_if_get_conn_addr(struct mg_connection *nc, int remote,
+                         union socket_address *sa) {
+  memset(sa, 0, sizeof(*sa));
+  if (nc->sock == INVALID_SOCKET) return;
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  if (nc->flags & MG_F_UDP) {
+    struct udp_pcb *upcb = cs->pcb.udp;
+    if (remote) {
+      memcpy(sa, &nc->sa, sizeof(*sa));
+    } else {
+      sa->sin.sin_port = htons(upcb->local_port);
+      sa->sin.sin_addr.s_addr = upcb->local_ip.addr;
+    }
+  } else {
+    struct tcp_pcb *tpcb = cs->pcb.tcp;
+    if (remote) {
+      sa->sin.sin_port = htons(tpcb->remote_port);
+      sa->sin.sin_addr.s_addr = tpcb->remote_ip.addr;
+    } else {
+      sa->sin.sin_port = htons(tpcb->local_port);
+      sa->sin.sin_addr.s_addr = tpcb->local_ip.addr;
+    }
+  }
+}
+
+void mg_sock_set(struct mg_connection *nc, sock_t sock) {
+  nc->sock = sock;
+}
+
+#endif /* MG_NET_IF_LWIP */
+#ifdef MG_MODULE_LINES
+#line 1 "common/platforms/lwip/mg_lwip_ev_mgr.c"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifdef MG_NET_IF_LWIP
+
+#ifndef MG_SIG_QUEUE_LEN
+#define MG_SIG_QUEUE_LEN 16
+#endif
+
+struct mg_ev_mgr_lwip_signal {
+  int sig;
+  struct mg_connection *nc;
+};
+
+struct mg_ev_mgr_lwip_data {
+  struct mg_ev_mgr_lwip_signal sig_queue[MG_SIG_QUEUE_LEN];
+  int sig_queue_len;
+  int start_index;
+};
+
+void mg_lwip_post_signal(enum mg_sig_type sig, struct mg_connection *nc) {
+  struct mg_ev_mgr_lwip_data *md =
+      (struct mg_ev_mgr_lwip_data *) nc->mgr->mgr_data;
+  if (md->sig_queue_len >= MG_SIG_QUEUE_LEN) return;
+  int end_index = (md->start_index + md->sig_queue_len) % MG_SIG_QUEUE_LEN;
+  md->sig_queue[end_index].sig = sig;
+  md->sig_queue[end_index].nc = nc;
+  md->sig_queue_len++;
+}
+
+void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {
+  struct mg_ev_mgr_lwip_data *md = (struct mg_ev_mgr_lwip_data *) mgr->mgr_data;
+  while (md->sig_queue_len > 0) {
+    struct mg_connection *nc = md->sig_queue[md->start_index].nc;
+    struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+    switch (md->sig_queue[md->start_index].sig) {
+      case MG_SIG_CONNECT_RESULT: {
+        mg_if_connect_cb(nc, cs->err);
+        break;
+      }
+      case MG_SIG_CLOSE_CONN: {
+        nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+        mg_close_conn(nc);
+        break;
+      }
+      case MG_SIG_SENT_CB: {
+        if (cs->num_sent > 0) mg_if_sent_cb(nc, cs->num_sent);
+        cs->num_sent = 0;
+        break;
+      }
+      case MG_SIG_TOMBSTONE: {
+        break;
+      }
+    }
+    md->start_index = (md->start_index + 1) % MG_SIG_QUEUE_LEN;
+    md->sig_queue_len--;
+  }
+}
+
+void mg_ev_mgr_init(struct mg_mgr *mgr) {
+  LOG(LL_INFO, ("%p Mongoose init"));
+  mgr->mgr_data = MG_CALLOC(1, sizeof(struct mg_ev_mgr_lwip_data));
+}
+
+void mg_ev_mgr_free(struct mg_mgr *mgr) {
+  MG_FREE(mgr->mgr_data);
+  mgr->mgr_data = NULL;
+}
+
+void mg_ev_mgr_add_conn(struct mg_connection *nc) {
+  (void) nc;
+}
+
+void mg_ev_mgr_remove_conn(struct mg_connection *nc) {
+  struct mg_ev_mgr_lwip_data *md =
+      (struct mg_ev_mgr_lwip_data *) nc->mgr->mgr_data;
+  /* Walk the queue and null-out further signals for this conn. */
+  for (int i = 0; i < MG_SIG_QUEUE_LEN; i++) {
+    if (md->sig_queue[i].nc == nc) {
+      md->sig_queue[i].sig = MG_SIG_TOMBSTONE;
+    }
+  }
+}
+
+time_t mg_mgr_poll(struct mg_mgr *mgr, int timeout_ms) {
+  int n = 0;
+  double now = mg_time();
+  struct mg_connection *nc, *tmp;
+  double min_timer = 0;
+  int num_timers = 0;
+  DBG(("begin poll @%u", (unsigned int) (now * 1000)));
+  mg_ev_mgr_lwip_process_signals(mgr);
+  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {
+    struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+    (void) cs;
+    tmp = nc->next;
+    n++;
+    if (nc->flags & MG_F_CLOSE_IMMEDIATELY) {
+      mg_close_conn(nc);
+      continue;
+    }
+    mg_if_poll(nc, now);
+    mg_if_timer(nc, now);
+    if (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE) &&
+        !(nc->flags & MG_F_WANT_WRITE)) {
+      mg_close_conn(nc);
+      continue;
+    }
+#ifdef SSL_KRYPTON
+    if (nc->ssl != NULL && cs != NULL && cs->pcb.tcp != NULL &&
+        cs->pcb.tcp->state == ESTABLISHED) {
+      if (((nc->flags & MG_F_WANT_WRITE) || nc->send_mbuf.len > 0) &&
+          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 /* SSL_KRYPTON */
+    {
+      if (!(nc->flags & (MG_F_CONNECTING | MG_F_UDP))) {
+        if (nc->send_mbuf.len > 0) mg_lwip_send_more(nc);
+      }
+    }
+    if (nc->ev_timer_time > 0) {
+      if (num_timers == 0 || nc->ev_timer_time < min_timer) {
+        min_timer = nc->ev_timer_time;
+      }
+      num_timers++;
+    }
+  }
+  DBG(("end poll @%u, %d conns, %d timers (min %u), next in %d ms",
+       (unsigned int) (now * 1000), n, num_timers,
+       (unsigned int) (min_timer * 1000), timeout_ms));
+  return now;
+}
+
+uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr) {
+  struct mg_connection *nc;
+  double now = mg_time();
+  double min_timer = 0;
+  int num_timers = 0;
+  mg_ev_mgr_lwip_process_signals(mgr);
+  for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
+    if (nc->ev_timer_time > 0) {
+      if (num_timers == 0 || nc->ev_timer_time < min_timer) {
+        min_timer = nc->ev_timer_time;
+      }
+      num_timers++;
+    }
+  }
+  uint32_t timeout_ms = ~0;
+  if (num_timers > 0) {
+    double timer_timeout_ms = (min_timer - now) * 1000 + 1 /* rounding */;
+    if (timer_timeout_ms < timeout_ms) {
+      timeout_ms = timer_timeout_ms;
+    }
+  }
+  return timeout_ms;
+}
+
+#endif /* MG_NET_IF_LWIP */
+#ifdef MG_MODULE_LINES
+#line 1 "common/platforms/lwip/mg_lwip_ssl_krypton.c"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#if defined(MG_NET_IF_LWIP) && defined(SSL_KRYPTON)
+
+/* 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
+
+/*
+ * Stop processing incoming SSL traffic when recv_mbuf.size is this big.
+ * It'a a uick solution for SSL recv pushback.
+ */
+#ifndef MG_LWIP_SSL_RECV_MBUF_LIMIT
+#define MG_LWIP_SSL_RECV_MBUF_LIMIT 3072
+#endif
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+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);
+  int ret = server_side ? SSL_accept(nc->ssl) : SSL_connect(nc->ssl);
+  int err = SSL_get_error(nc->ssl, ret);
+  DBG(("%s %d %d", (server_side ? "SSL_accept" : "SSL_connect"), ret, err));
+  if (ret <= 0) {
+    if (err == SSL_ERROR_WANT_WRITE) {
+      nc->flags |= MG_F_WANT_WRITE;
+      cs->err = 0;
+    } else if (err == SSL_ERROR_WANT_READ) {
+      /* Nothing, we are callback-driven. */
+      cs->err = 0;
+    } else {
+      cs->err = err;
+      LOG(LL_ERROR, ("SSL handshake error: %d", cs->err));
+      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 = SSL_write(nc->ssl, nc->send_mbuf.buf, len);
+  int err = SSL_get_error(nc->ssl, ret);
+  DBG(("%p SSL_write %u = %d, %d", nc, len, ret, err));
+  if (ret > 0) {
+    mbuf_remove(&nc->send_mbuf, ret);
+    mbuf_trim(&nc->send_mbuf);
+    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 (err == SSL_ERROR_NONE) {
+    nc->flags &= ~MG_F_WANT_WRITE;
+  } else if (err == SSL_ERROR_WANT_WRITE) {
+    nc->flags |= MG_F_WANT_WRITE;
+  } else {
+    LOG(LL_ERROR, ("SSL write error: %d", err));
+    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 < MG_LWIP_SSL_RECV_MBUF_LIMIT) {
+    char *buf = (char *) malloc(MG_LWIP_SSL_IO_SIZE);
+    if (buf == NULL) return;
+    int ret = SSL_read(nc->ssl, buf, MG_LWIP_SSL_IO_SIZE);
+    int err = SSL_get_error(nc->ssl, ret);
+    DBG(("%p SSL_read %u = %d, %d", nc, MG_LWIP_SSL_IO_SIZE, ret, err));
+    if (ret <= 0) {
+      free(buf);
+      if (err == SSL_ERROR_WANT_WRITE) {
+        nc->flags |= MG_F_WANT_WRITE;
+        return;
+      } else if (err == SSL_ERROR_WANT_READ) {
+        /* Nothing, we are callback-driven. */
+        cs->err = 0;
+        return;
+      } else {
+        LOG(LL_ERROR, ("SSL read error: %d", err));
+        mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
+      }
+    } else {
+      mg_if_recv_tcp_cb(nc, buf, ret); /* callee takes over data */
+    }
+  }
+  if (nc->recv_mbuf.len >= MG_LWIP_SSL_RECV_MBUF_LIMIT) {
+    nc->flags |= MG_F_WANT_READ;
+  } else {
+    nc->flags &= ~MG_F_WANT_READ;
+  }
+}
+
+ssize_t kr_send(int fd, const void *buf, size_t len, int flags) {
+  struct mg_connection *nc = (struct mg_connection *) fd;
+  int ret = mg_lwip_tcp_write(nc, buf, len);
+  (void) flags;
+  DBG(("mg_lwip_tcp_write %u = %d", len, ret));
+  if (ret <= 0) {
+    errno = (ret == 0 ? EWOULDBLOCK : EIO);
+    ret = -1;
+  }
+  return ret;
+}
+
+ssize_t kr_recv(int fd, void *buf, size_t len, int flags) {
+  struct mg_connection *nc = (struct mg_connection *) fd;
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  struct pbuf *seg = cs->rx_chain;
+  (void) flags;
+  if (seg == NULL) {
+    DBG(("%u - nothing to read", len));
+    errno = EWOULDBLOCK;
+    return -1;
+  }
+  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;
+}
+
+#endif /* defined(MG_NET_IF_LWIP) && defined(SSL_KRYPTON) */
diff --git a/mongoose.h b/mongoose.h
index fc9996e31f46fea545275be9ce957d75fef4b42c..21fc170013338296d904a7ec47fcc6f6519a6bb5 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -433,6 +433,15 @@ typedef struct stat cs_stat_t;
 unsigned long os_random(void);
 #define random os_random
 
+#ifndef RTOS_SDK
+#define MG_NET_IF_LWIP
+struct mg_mgr;
+struct mg_connection;
+uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr);
+void mg_lwip_set_keepalive_params(struct mg_connection *nc, int idle,
+                                  int interval, int count);
+#endif
+
 #endif /* CS_PLATFORM == CS_P_ESP_LWIP */
 #endif /* CS_COMMON_PLATFORMS_PLATFORM_ESP_LWIP_H_ */
 #ifdef MG_MODULE_LINES