]> git.feebdaed.xyz Git - 0xmirror/mongoose.git/commitdiff
Introduce IPv6, unit tests pass
authorSergio R. Caprile <scaprile@cesanta.com>
Tue, 23 Sep 2025 16:47:26 +0000 (13:47 -0300)
committerSergio R. Caprile <scaprile@cesanta.com>
Wed, 5 Nov 2025 14:04:23 +0000 (11:04 -0300)
mongoose.c
mongoose.h
src/config.h
src/drivers/stm32h.h
src/net_builtin.c
src/net_builtin.h
src/util.c
src/util.h
test/mip_test.c
test/unit_test.c

index 519de7db354613efb564fb24ae2d67a17beed94f..de6bf32b1775151aee201fb39ae8676579932ae2 100644 (file)
@@ -4231,8 +4231,8 @@ struct ip6 {
   uint16_t plen;     // Payload length
   uint8_t next;      // Upper level protocol
   uint8_t hops;      // Hop limit
-  uint8_t src[16];   // Source IP
-  uint8_t dst[16];   // Destination IP
+  uint64_t src[2];   // Source IP
+  uint64_t dst[2];   // Destination IP
 };
 
 struct icmp {
@@ -4247,6 +4247,19 @@ struct icmp6 {
   uint16_t csum;
 };
 
+struct ndp_na {
+  uint8_t res[4];    // R S O, reserved
+  uint64_t addr[2];  // Target address
+};
+
+struct ndp_ra {
+  uint8_t cur_hop_limit;
+  uint8_t flags;  // M,O,Prf,Resvd
+  uint16_t router_lifetime;
+  uint32_t reachable_time;
+  uint32_t retrans_timer;
+};
+
 struct arp {
   uint16_t fmt;    // Format of hardware address
   uint16_t pro;    // Format of protocol address
@@ -4306,6 +4319,8 @@ struct dhcp6 {
 
 #pragma pack(pop)
 
+// pkt is 8-bit aligned, pointers to headers hint compilers to generate
+// byte-copy code for micros with alignment constraints
 struct pkt {
   struct mg_str raw;  // Raw packet data
   struct mg_str pay;  // Payload data
@@ -4324,18 +4339,11 @@ struct pkt {
 
 static void mg_tcpip_call(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
 #if MG_ENABLE_PROFILE
-  const char *names[] = {
-  "TCPIP_EV_ST_CHG",
-  "TCPIP_EV_DHCP_DNS",
-  "TCPIP_EV_DHCP_SNTP",
-  "TCPIP_EV_ARP",
-  "TCPIP_EV_TIMER_1S",
-  "TCPIP_EV_WIFI_SCAN_RESULT",
-  "TCPIP_EV_WIFI_SCAN_END",
-  "TCPIP_EV_WIFI_CONNECT_ERR",
-  "TCPIP_EV_DRIVER",
-  "TCPIP_EV_USER" 
-  };
+  const char *names[] = {"TCPIP_EV_ST_CHG",        "TCPIP_EV_DHCP_DNS",
+                         "TCPIP_EV_DHCP_SNTP",     "TCPIP_EV_ARP",
+                         "TCPIP_EV_TIMER_1S",      "TCPIP_EV_WIFI_SCAN_RESULT",
+                         "TCPIP_EV_WIFI_SCAN_END", "TCPIP_EV_WIFI_CONNECT_ERR",
+                         "TCPIP_EV_DRIVER",        "TCPIP_EV_USER"};
   if (ev != MG_TCPIP_EV_POLL && ev < (int) (sizeof(names) / sizeof(names[0]))) {
     MG_PROF_ADD(c, names[ev]);
   }
@@ -4369,6 +4377,61 @@ static uint16_t ipcsum(const void *buf, size_t len) {
   return csumfin(sum);
 }
 
+#if MG_ENABLE_IPV6
+static void meui64(uint8_t *addr, uint8_t *mac) {
+  *addr++ = *mac++ ^ (uint8_t) 0x02, *addr++ = *mac++, *addr++ = *mac++;
+  *addr++ = 0xff, *addr++ = 0xfe;
+  *addr++ = *mac++, *addr++ = *mac++, *addr = *mac;
+}
+static void ip6gen(uint8_t *addr, uint8_t *prefix, uint8_t *mac) {
+  memcpy(addr, prefix, 8);  // RFC-4291 2.5.4
+  meui64(addr + 8, mac);    // 2.5.1
+}
+static void ip6genll(uint8_t *addr, uint8_t *mac) {
+  uint8_t prefix[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0};  // RFC-4291 2.5.6
+  ip6gen(addr, prefix, mac);                           // 2.5.1
+}
+static void ip6sn(uint64_t *addr, uint64_t *sn_addr) {
+  // Build solicited-node multicast address from a given unicast IP
+  // RFC-4291 2.7
+  uint8_t *sn = (uint8_t *) sn_addr;
+  memset(sn_addr, 0, 16);
+  sn[0] = 0xff;
+  sn[1] = 0x02;
+  sn[11] = 0x01;
+  sn[12] = 0xff;
+  sn[13] = ((uint8_t *) addr)[13];
+  sn[14] = ((uint8_t *) addr)[14];
+  sn[15] = ((uint8_t *) addr)[15];
+}
+static void ip6_mcastmac(uint8_t *mac, uint64_t *ip6) {
+  // Build multicast MAC address from a solicited-node
+  // multicast IPv6 address, RFC-4291 2.7
+  uint8_t *ip = (uint8_t *) ip6;
+  mac[0] = 0x33;
+  mac[1] = 0x33;
+  mac[2] = ip[12];
+  mac[3] = ip[13];
+  mac[4] = ip[14];
+  mac[5] = ip[15];
+}
+
+union ip6addr {
+  uint64_t u[2];
+  uint8_t b[16];
+};
+
+static const union ip6addr ip6_allrouters = {
+    .b = {0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02}};
+static const union ip6addr ip6_allnodes = {
+    .b = {0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}};
+static const uint8_t ip6mac_allnodes[6] = {0x33, 0x33, 0x00, 0x00, 0x00, 0x01};
+static const uint8_t ip6mac_allrouters[6] = {0x33, 0x33, 0x00,
+                                             0x00, 0x00, 0x02};
+
+#define MG_IP6MATCH(a, b) (a[0] == b[0] && a[1] == b[1])
+#endif
+
 static void settmout(struct mg_connection *c, uint8_t type) {
   struct mg_tcpip_if *ifp = c->mgr->ifp;
   struct connstate *s = (struct connstate *) (c + 1);
@@ -4422,6 +4485,7 @@ static void onstatechange(struct mg_tcpip_if *ifp) {
 static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
                         uint8_t proto, uint32_t ip_src, uint32_t ip_dst,
                         size_t plen) {
+  // ifp->tx.buf is 8-bit aligned, keep other headers as pointers, see pkt
   struct eth *eth = (struct eth *) ifp->tx.buf;
   struct ip *ip = (struct ip *) (eth + 1);
   memcpy(eth->dst, mac_dst, sizeof(eth->dst));
@@ -4439,31 +4503,69 @@ static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
   return ip;
 }
 
-static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
-                   uint16_t sport, uint32_t ip_dst, uint16_t dport,
+#if MG_ENABLE_IPV6
+static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                          uint8_t next, uint64_t *ip_src, uint64_t *ip_dst,
+                          size_t plen);
+#endif
+
+static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                   struct mg_addr *ip_src, struct mg_addr *ip_dst,
                    const void *buf, size_t len) {
-  struct ip *ip =
-      tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp));
-  struct udp *udp = (struct udp *) (ip + 1);
+  struct ip *ip = NULL;
+  struct udp *udp;
   size_t eth_len;
   uint32_t cs;
-  // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
-  udp->sport = sport;
-  udp->dport = dport;
+#if MG_ENABLE_IPV6
+  struct ip6 *ip6 = NULL;
+  if (ip_dst->is_ip6) {
+    ip6 = tx_ip6(ifp, mac_dst, 17, ip_src->ip6, ip_dst->ip6,
+                 len + sizeof(struct udp));
+    udp = (struct udp *) (ip6 + 1);
+    eth_len = sizeof(struct eth) + sizeof(*ip6) + sizeof(*udp) + len;
+  } else
+#endif
+  {
+    ip = tx_ip(ifp, mac_dst, 17, ip_src->ip4, ip_dst->ip4,
+               len + sizeof(struct udp));
+    udp = (struct udp *) (ip + 1);
+    eth_len = sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len;
+  }
+  udp->sport = ip_src->port;
+  udp->dport = ip_dst->port;
   udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
   udp->csum = 0;
   cs = csumup(0, udp, sizeof(*udp));
   cs = csumup(cs, buf, len);
-  cs = csumup(cs, &ip->src, sizeof(ip->src));
-  cs = csumup(cs, &ip->dst, sizeof(ip->dst));
-  cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
+#if MG_ENABLE_IPV6
+  if (ip_dst->is_ip6) {
+    cs = csumup(cs, &ip6->src, sizeof(ip6->src));
+    cs = csumup(cs, &ip6->dst, sizeof(ip6->dst));
+  } else
+#endif
+  {
+    cs = csumup(cs, &ip->src, sizeof(ip->src));
+    cs = csumup(cs, &ip->dst, sizeof(ip->dst));
+  }
+  cs += (uint32_t) (17 + sizeof(*udp) + len);
   udp->csum = csumfin(cs);
   memmove(udp + 1, buf, len);
-  // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
-  eth_len = sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len;
   return (ether_output(ifp, eth_len) == eth_len);
 }
 
+static bool tx_udp4(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
+                    uint16_t sport, uint32_t ip_dst, uint16_t dport,
+                    const void *buf, size_t len) {
+  struct mg_addr ips, ipd;
+  memset(&ips, 0, sizeof(ips));
+  ips.ip4 = ip_src;
+  ips.port = sport;
+  memset(&ipd, 0, sizeof(ipd));
+  ipd.ip4 = ip_dst;
+  ipd.port = dport;
+  return tx_udp(ifp, mac_dst, &ips, &ipd, buf, len);
+}
+
 static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
                     uint32_t ip_dst, uint8_t *opts, size_t optslen,
                     bool ciaddr) {
@@ -4474,8 +4576,8 @@ static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
   memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
   memcpy(&dhcp.options, opts, optslen);
   if (ciaddr) dhcp.ciaddr = ip_src;
-  tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp,
-         sizeof(dhcp));
+  tx_udp4(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp,
+          sizeof(dhcp));
 }
 
 static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
@@ -4531,12 +4633,18 @@ static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt,
                                      bool lsn) {
   struct mg_connection *c = NULL;
   for (c = mgr->conns; c != NULL; c = c->next) {
-    if (c->is_arplooking && pkt->arp &&
-        memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0)
+    if (c->is_arplooking && pkt->arp && pkt->arp->spa == c->rem.ip4) break;
+#if MG_ENABLE_IPV6
+    if (c->is_arplooking && pkt->icmp6 && pkt->icmp6->type == 136) {
+      struct ndp_na *na = (struct ndp_na *) (pkt->icmp6 + 1);
+      if (MG_IP6MATCH(na->addr, c->rem.ip6)) break;
+    }
+#endif
+    if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport &&
+        (!c->loc.is_ip6 || pkt->ip6))
       break;
-    if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break;
     if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
-        lsn == (bool) c->is_listening &&
+        (!c->loc.is_ip6 || pkt->ip6) && lsn == (bool) c->is_listening &&
         (lsn || c->rem.port == pkt->tcp->sport))
       break;
   }
@@ -4548,8 +4656,7 @@ static void mac_resolved(struct mg_connection *c);
 static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
   if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
     // ARP request. Make a response, then send
-    // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op), mg_print_ip4,
-    //          &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa));
+    // MG_VERBOSE(("ARP req from %M", mg_print_ip4, &pkt->arp->spa));
     struct eth *eth = (struct eth *) ifp->tx.buf;
     struct arp *arp = (struct arp *) (eth + 1);
     memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
@@ -4566,6 +4673,7 @@ static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
     ether_output(ifp, PDIFF(eth, arp + 1));
   } else if (pkt->arp->op == mg_htons(2)) {
     if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return;
+    // MG_VERBOSE(("ARP resp from %M", mg_print_ip4, &pkt->arp->spa));
     if (pkt->arp->spa == ifp->gw) {
       // Got response for the GW ARP request. Set ifp->gwmac and IP -> READY
       memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac));
@@ -4588,7 +4696,6 @@ static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
 }
 
 static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
-  // MG_DEBUG(("ICMP %d", (int) len));
   if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) {
     size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct icmp);
     size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
@@ -4598,7 +4705,7 @@ static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
     ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip, pkt->ip->src,
                sizeof(*icmp) + plen);
     icmp = (struct icmp *) (ip + 1);
-    memset(icmp, 0, sizeof(*icmp));        // Set csum to 0
+    memset(icmp, 0, sizeof(*icmp));        // Set csum, type, code to 0
     memcpy(icmp + 1, pkt->pay.buf, plen);  // Copy RX payload to TX
     icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
     ether_output(ifp, hlen + plen);
@@ -4704,18 +4811,295 @@ static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
       ifp->gw = res.yiaddr;  // set gw IP, best-effort gwmac as DHCP server's
       memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
     }
-    tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
-           op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res));
+    tx_udp4(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
+            op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res));
   }
 }
 
+#if MG_ENABLE_IPV6
+static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                          uint8_t next, uint64_t *ip_src, uint64_t *ip_dst,
+                          size_t plen) {
+  // ifp->tx.buf is 8-bit aligned, keep other headers as pointers, see pkt
+  struct eth *eth = (struct eth *) ifp->tx.buf;
+  struct ip6 *ip6 = (struct ip6 *) (eth + 1);
+  memcpy(eth->dst, mac_dst, sizeof(eth->dst));
+  memcpy(eth->src, ifp->mac, sizeof(eth->src));  // Use our MAC
+  eth->type = mg_htons(0x86dd);
+  memset(ip6, 0, sizeof(*ip6));
+  ip6->ver = 0x60;  // Version 6, traffic class 0
+  ip6->plen = mg_htons((uint16_t) plen);
+  ip6->next = next;
+  ip6->hops = 255;  // NDP requires max
+  ip6->src[0] = *ip_src++;
+  ip6->src[1] = *ip_src;
+  ip6->dst[0] = *ip_dst++;
+  ip6->dst[1] = *ip_dst;
+  return ip6;
+}
+
+static void tx_icmp6(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                     uint64_t *ip_src, uint64_t *ip_dst, uint8_t type,
+                     uint8_t code, const void *buf, size_t len) {
+  struct ip6 *ip6;
+  struct icmp6 *icmp6;
+  uint32_t cs;
+  ip6 = tx_ip6(ifp, mac_dst, 58, ip_src, ip_dst, sizeof(*icmp6) + len);
+  icmp6 = (struct icmp6 *) (ip6 + 1);
+  memset(icmp6, 0, sizeof(*icmp6));  // Set csum to 0
+  icmp6->type = type;
+  icmp6->code = code;
+  memcpy(icmp6 + 1, buf, len);  // Copy payload
+  icmp6->csum = 0;              // RFC-4443 2.3, RFC-8200 8.1
+  cs = csumup(0, icmp6, sizeof(*icmp6));
+  cs = csumup(cs, buf, len);
+  cs = csumup(cs, ip_src, 16);
+  cs = csumup(cs, ip_dst, 16);
+  cs += (uint32_t) (58 + sizeof(*icmp6) + len);
+  icmp6->csum = csumfin(cs);
+  ether_output(ifp, sizeof(struct eth) + sizeof(*ip6) + sizeof(*icmp6) + len);
+}
+
+// Neighbor Discovery Protocol, RFC-4861
+// Neighbor Advertisement, 4.4
+static void tx_ndp_na(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                      uint64_t *ip_src, uint64_t *ip_dst, bool solicited,
+                      uint8_t *mac) {
+  uint8_t data[28];
+  memset(data, 0, sizeof(data));
+  data[0] = solicited ? 0x60 : 0x20;  // O + S
+  memcpy(data + 4, ip_src, 16);       // Target address
+  data[20] = 2, data[21] = 1;         // 4.6.1, target MAC
+  memcpy(data + 22, mac, 6);
+  tx_icmp6(ifp, mac_dst, ip_src, ip_dst, 136, 0, data, sizeof(data));
+}
+
+static void onstate6change(struct mg_tcpip_if *ifp);
+
+static void rx_ndp_na(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  struct ndp_na *na = (struct ndp_na *) (pkt->icmp6 + 1);
+  uint8_t *opts = (uint8_t *) (na + 1);
+  if ((na->res[0] & 0x40) == 0) return;  // not "solicited"
+  if (*opts++ != 2) return;              // no target hwaddr
+  if (*opts++ != 1) return;  // don't know what to do with this layer 2
+  MG_VERBOSE(("NDP NA resp from %M", mg_print_ip6, (char *) &na->addr));
+  if (MG_IP6MATCH(na->addr, ifp->gw6)) {
+    // Got response for the GW NS request. Set ifp->gw6mac and IP6 -> READY
+    memcpy(ifp->gw6mac, opts, sizeof(ifp->gw6mac));
+    if (ifp->state6 == MG_TCPIP_STATE_IP) {
+      ifp->state6 = MG_TCPIP_STATE_READY;
+      onstate6change(ifp);
+    }
+  } else {
+    struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
+    if (c != NULL && c->is_arplooking) {
+      struct connstate *s = (struct connstate *) (c + 1);
+      memcpy(s->mac, opts, sizeof(s->mac));
+      MG_DEBUG(("%lu NDP resolved %M -> %M", c->id, mg_print_ip6, c->rem.ip,
+                mg_print_mac, s->mac));
+      c->is_arplooking = 0;
+      mac_resolved(c);
+    }
+  }
+}
+
+// Neighbor Solicitation, 4.3
+static void rx_ndp_ns(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  uint64_t target[2];
+  if (pkt->pay.len < sizeof(target)) return;
+  memcpy(target, pkt->pay.buf + 4, sizeof(target));
+  if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6))
+    tx_ndp_na(ifp, (uint8_t *) pkt->pay.buf + 22, target, pkt->ip6->src, true,
+              ifp->mac);
+}
+
+static void tx_ndp_ns(struct mg_tcpip_if *ifp, uint64_t *ip_dst, uint8_t *mac) {
+  uint8_t payload[4 + 16 + 8];
+  uint64_t unspec_ip[2] = {0, 0};
+  size_t payload_len;
+  bool mcast_ns = true;
+  uint64_t mcast_ip[2] = {0, 0};
+  uint8_t mcast_mac[6];
+
+  memset(payload, 0, sizeof(payload));
+  memcpy(payload + 4, ip_dst, 16);
+  for (int i = 0; i < 6; i++) {
+    if (mac[i] != 0xff) {
+      mcast_ns = false;
+      break;
+    }
+  }
+  if (mcast_ns) {
+    ip6sn(ip_dst, mcast_ip);
+    ip6_mcastmac(mcast_mac, mcast_ip);
+  }
+  // TODO(robertc2000): using only link-local IP addr for now
+  // We might consider to add an option to use either link-local or global IP
+  if (!MG_IP6MATCH(ifp->ip6ll, unspec_ip)) {
+    payload[20] = payload[21] = 1;  // Type = 1; Length = 1
+    memcpy(payload + 22, ifp->mac, 6);
+    payload_len = sizeof(payload);
+  } else {
+    payload_len = sizeof(payload) - 8;
+  }
+  tx_icmp6(ifp, mcast_ns ? mcast_mac : mac, ifp->ip6ll,
+           mcast_ns ? mcast_ip : ip_dst, 135, 0, payload, payload_len);
+}
+
+// Router Solicitation, 4.1
+static void tx_ndp_rs(struct mg_tcpip_if *ifp, uint64_t *ip_dst, uint8_t *mac) {
+  // Note: currently, this function only sends multicast RS packets
+  (void) ip_dst;
+  (void) mac;
+  uint8_t payload[4 + 8];  // reserved + optional source MAC addr
+  size_t payload_len = 4;
+  uint64_t unspec_ip[2] = {0, 0};
+
+  memset(payload, 0, sizeof(payload));
+  if (!MG_IP6MATCH(ifp->ip6ll, unspec_ip)) {
+    payload[4] = payload[5] = 1;  // Type = 1; Length = 1
+    memcpy(payload + 6, ifp->mac, 6);
+    payload_len += 8;
+  }
+  tx_icmp6(ifp, (uint8_t *) ip6mac_allrouters, ifp->ip6ll,
+           (uint64_t *) ip6_allrouters.u, 133, 0, payload, payload_len);
+  MG_DEBUG(("NDP Router Solicitation sent"));
+}
+
+static bool fill_global(uint64_t *ip6, uint8_t *prefix, uint8_t prefix_len,
+                        uint8_t *mac) {
+  uint8_t full = prefix_len / 8;
+  uint8_t rem = prefix_len % 8;
+  if (full >= 8 && rem != 0) {
+    MG_ERROR(("Prefix length > 64, UNSUPPORTED"));
+    return false;
+  } else if (full == 8 && rem == 0) {
+    ip6gen((uint8_t *) ip6, prefix, mac);
+  } else {
+    ip6[0] = ip6[1] = 0;  // already zeroed before firing RS...
+    if (full) memcpy(ip6, prefix, full);
+    if (rem) {
+      uint8_t mask = (uint8_t) (0xFF << (8 - rem));
+      ((uint8_t *) ip6)[full] = prefix[full] & mask;
+    }
+    meui64(((uint8_t *) &ip6[1]), mac);  // RFC-4291 2.5.4, 2.5.1
+  }
+  return true;
+}
+
+// Router Advertisement, 4.2
+static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  if (pkt->pay.len < 12) return;
+  struct ndp_ra *ra = (struct ndp_ra *) (pkt->icmp6 + 1);
+  uint8_t *opts = (uint8_t *) (ra + 1);
+  size_t opt_left = pkt->pay.len - 12;
+
+  if (ifp->state6 == MG_TCPIP_STATE_UP) {
+    MG_DEBUG(("Received NDP RA"));
+    memcpy(ifp->gw6, (uint8_t *) pkt->ip6->src, 16);  // fill gw6 address
+    // parse options
+    while (opt_left >= 2) {
+      uint8_t type = opts[0], len = opts[1];
+      size_t length = (size_t) len * 8;
+      if (length == 0 || length > opt_left) break;  // malformed
+      if (type == 1 && length >= 8) {
+        // Received router's MAC address
+        ifp->state6 = MG_TCPIP_STATE_READY;
+        memcpy(ifp->gw6mac, opts + 2, 6);
+      } else if (type == 5 && length >= 8) {
+        // process MTU if available
+        uint32_t mtu = mg_ntohl(*(uint32_t *) (opts + 4));
+        MG_INFO(("got a nice MTU: %u, do you care ?", mtu));
+        // TODO(): **** This is an IPv6-given MTU, ifp->MTU is a LINK MTU, are
+        // we talkin'bout the same ? ***
+      } else if (type == 3 && length >= 32) {
+        // process prefix, 4.6.2
+        uint8_t prefix_len = opts[2];
+        uint8_t pfx_flags = opts[3];  // L=0x80, A=0x40
+        uint32_t valid = mg_ntohl(*(uint32_t *) (opts + 4));
+        uint32_t pref_lifetime = mg_ntohl(*(uint32_t *) (opts + 8));
+        uint8_t *prefix = opts + 16;
+
+        // TODO (robertc2000): handle prefix options if necessary
+        (void) prefix_len;
+        (void) pfx_flags;
+        (void) valid;
+        (void) pref_lifetime;
+        (void) prefix;
+
+        // fill prefix length and global
+        ifp->prefix_len = prefix_len;
+        if (!fill_global(ifp->ip6, prefix, prefix_len, ifp->mac)) return;
+      }
+      opts += length;
+      opt_left -= length;
+    }
+
+    if (ifp->state6 != MG_TCPIP_STATE_READY) {
+      tx_ndp_ns(ifp, ifp->gw6, ifp->gw6mac);  // unsolicited GW MAC resolution
+      ifp->state6 = MG_TCPIP_STATE_IP;
+    }
+    onstate6change(ifp);
+  }
+}
+
+static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  switch (pkt->icmp6->type) {
+    case 128: {  // Echo Request, RFC-4443 4.1
+      uint64_t target[2];
+      memcpy(target, pkt->ip6->dst, sizeof(target));
+      if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6)) {
+        size_t hlen =
+            sizeof(struct eth) + sizeof(struct ip6) + sizeof(struct icmp6);
+        size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
+        if (plen > space) plen = space;  // Copy (truncated) RX payload to TX
+        // Echo Reply, 4.2
+        tx_icmp6(ifp, pkt->eth->src, pkt->ip6->dst, pkt->ip6->src, 129, 0,
+                 pkt->pay.buf, plen);
+      }
+    } break;
+    case 134:  // Router Advertisement
+      rx_ndp_ra(ifp, pkt);
+      break;
+    case 135:  // Neighbor Solicitation
+      rx_ndp_ns(ifp, pkt);
+      break;
+    case 136:  // Neighbor Advertisement
+      rx_ndp_na(ifp, pkt);
+      break;
+  }
+}
+
+static void onstate6change(struct mg_tcpip_if *ifp) {
+  if (ifp->state6 == MG_TCPIP_STATE_READY) {
+    MG_INFO(("READY, IP: %M", mg_print_ip6, &ifp->ip6));
+    MG_INFO(("       GW: %M", mg_print_ip6, &ifp->gw6));
+    MG_INFO(("      MAC: %M", mg_print_mac, &ifp->mac));
+  } else if (ifp->state6 == MG_TCPIP_STATE_IP) {
+    tx_ndp_ns(ifp, ifp->gw6, ifp->gw6mac);  // unsolicited GW MAC resolution
+  } else if (ifp->state6 == MG_TCPIP_STATE_UP) {
+    MG_INFO(("IP: %M", mg_print_ip6, &ifp->ip6ll));
+  }
+  if (ifp->state6 != MG_TCPIP_STATE_UP && ifp->state6 != MG_TCPIP_STATE_DOWN)
+    mg_tcpip_call(ifp, MG_TCPIP_EV_ST6_CHG, &ifp->state6);
+}
+#endif
+
 static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
   struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
   struct connstate *s;
   if (c == NULL) return false;  // No UDP listener on this port
   s = (struct connstate *) (c + 1);
   c->rem.port = pkt->udp->sport;
-  memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
+#if MG_ENABLE_IPV6
+  if (c->loc.is_ip6) {
+    c->rem.ip6[0] = pkt->ip6->src[0], c->rem.ip6[1] = pkt->ip6->src[1],
+    c->rem.is_ip6 = true;
+  } else
+#endif
+  {
+    c->rem.ip4 = pkt->ip->src;
+  }
   memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
   if (c->recv.len >= MG_MAX_RECV_SIZE) {
     mg_error(c, "max_recv_buf_size reached");
@@ -4730,24 +5114,41 @@ static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
   return true;
 }
 
-static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip,
-                     uint8_t flags, uint16_t sport, uint16_t dport,
-                     uint32_t seq, uint32_t ack, const void *buf, size_t len) {
-  struct ip *ip;
+static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                     struct mg_addr *ip_src, struct mg_addr *ip_dst,
+                     uint8_t flags, uint32_t seq, uint32_t ack, const void *buf,
+                     size_t len) {
+  struct ip *ip = NULL;
   struct tcp *tcp;
-  uint16_t opts[4 / 2];
-  if (flags & TH_SYN) {                              // Send MSS, RFC-9293 3.7.1
-    opts[0] = mg_htons(0x0204);                      // RFC-9293 3.2
-    opts[1] = mg_htons((uint16_t) (ifp->mtu - 40));  // RFC-6691
+  uint16_t opts[4 / 2], mss;
+#if MG_ENABLE_IPV6
+  struct ip6 *ip6 = NULL;
+  mss = (uint16_t) (ifp->mtu - 60);  // RFC-9293 3.7.1; RFC-6691 2
+#else
+  mss = (uint16_t) (ifp->mtu - 40);  // RFC-9293 3.7.1; RFC-6691 2
+#endif
+  if (flags & TH_SYN) {          // Send MSS
+    opts[0] = mg_htons(0x0204);  // RFC-9293 3.2
+    opts[1] = mg_htons(mss);
     buf = opts;
     len = sizeof(opts);
   }
-  ip = tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len);
-  tcp = (struct tcp *) (ip + 1);
+#if MG_ENABLE_IPV6
+  if (ip_dst->is_ip6) {
+    ip6 = tx_ip6(ifp, mac_dst, 6, ip_src->ip6, ip_dst->ip6,
+                 sizeof(struct tcp) + len);
+    tcp = (struct tcp *) (ip6 + 1);
+  } else
+#endif
+  {
+    ip = tx_ip(ifp, mac_dst, 6, ip_src->ip4, ip_dst->ip4,
+               sizeof(struct tcp) + len);
+    tcp = (struct tcp *) (ip + 1);
+  }
   memset(tcp, 0, sizeof(*tcp));
   if (buf != NULL && len) memmove(tcp + 1, buf, len);
-  tcp->sport = sport;
-  tcp->dport = dport;
+  tcp->sport = ip_src->port;
+  tcp->dport = ip_dst->port;
   tcp->seq = seq;
   tcp->ack = ack;
   tcp->flags = flags;
@@ -4757,16 +5158,29 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip,
   {
     uint32_t cs = 0;
     uint16_t n = (uint16_t) (sizeof(*tcp) + len);
-    uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)};
     cs = csumup(cs, tcp, n);
-    cs = csumup(cs, &ip->src, sizeof(ip->src));
-    cs = csumup(cs, &ip->dst, sizeof(ip->dst));
-    cs = csumup(cs, pseudo, sizeof(pseudo));
+#if MG_ENABLE_IPV6
+    if (ip_dst->is_ip6) {
+      cs = csumup(cs, &ip6->src, sizeof(ip6->src));
+      cs = csumup(cs, &ip6->dst, sizeof(ip6->dst));
+      cs += (uint32_t) (6 + n);
+      MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip6, &ip6->src,
+                  mg_ntohs(tcp->sport), mg_print_ip6, &ip6->dst,
+                  mg_ntohs(tcp->dport), tcp->flags, len));
+    } else
+#endif
+    {
+      uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8),
+                          (uint8_t) (n & 255)};
+      cs = csumup(cs, &ip->src, sizeof(ip->src));
+      cs = csumup(cs, &ip->dst, sizeof(ip->dst));
+      cs = csumup(cs, pseudo, sizeof(pseudo));
+      MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src,
+                  mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst,
+                  mg_ntohs(tcp->dport), tcp->flags, len));
+    }
     tcp->csum = csumfin(cs);
   }
-  MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src,
-              mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst,
-              mg_ntohs(tcp->dport), tcp->flags, len));
   return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len);
 }
 
@@ -4774,8 +5188,21 @@ static size_t tx_tcp_ctrlresp(struct mg_tcpip_if *ifp, struct pkt *pkt,
                               uint8_t flags, uint32_t seqno) {
   uint32_t ackno = mg_htonl(mg_ntohl(pkt->tcp->seq) + (uint32_t) pkt->pay.len +
                             ((pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0));
-  return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags, pkt->tcp->dport,
-                pkt->tcp->sport, seqno, ackno, NULL, 0);
+  struct mg_addr ips, ipd;
+  memset(&ips, 0, sizeof(ips));
+  memset(&ipd, 0, sizeof(ipd));
+  if (pkt->ip != NULL) {
+    ips.ip4 = pkt->ip->dst;
+    ipd.ip4 = pkt->ip->src;
+  } else {
+    ips.ip6[0] = pkt->ip6->dst[0], ips.ip6[1] = pkt->ip6->dst[1];
+    ipd.ip6[0] = pkt->ip6->src[0], ipd.ip6[1] = pkt->ip6->src[1];
+    ips.is_ip6 = true;
+    ipd.is_ip6 = true;
+  }
+  ips.port = pkt->tcp->dport;
+  ipd.port = pkt->tcp->sport;
+  return tx_tcp(ifp, pkt->eth->src, &ips, &ipd, flags, seqno, ackno, NULL, 0);
 }
 
 static size_t tx_tcp_rst(struct mg_tcpip_if *ifp, struct pkt *pkt, bool toack) {
@@ -4796,7 +5223,15 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn,
   s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
   memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
   settmout(c, MIP_TTYPE_KEEPALIVE);
-  memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
+#if MG_ENABLE_IPV6
+  if (lsn->loc.is_ip6) {
+    c->rem.ip6[0] = pkt->ip6->src[0], c->rem.ip6[1] = pkt->ip6->src[1],
+    c->rem.is_ip6 = true;
+  } else
+#endif
+  {
+    c->rem.ip4 = pkt->ip->src;
+  }
   c->rem.port = pkt->tcp->sport;
   MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem));
   LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
@@ -4816,10 +5251,13 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn,
 
 static size_t trim_len(struct mg_connection *c, size_t len) {
   struct mg_tcpip_if *ifp = c->mgr->ifp;
-  size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60, udp_h_len = 8;
+  size_t eth_h_len = 14, tcp_max_h_len = 60, udp_h_len = 8;
+  size_t ip_max_h_len = c->rem.is_ip6 ? 40 : 24;  // we don't send options
   size_t max_headers_len =
       eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len);
-  size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len - eth_h_len;
+  size_t min_mtu = c->rem.is_ip6 ? 1280
+                   : c->is_udp   ? 68 /* RFC-791 */
+                                 : max_headers_len - eth_h_len;
 
   // If the frame exceeds the available buffer, trim the length
   if (len + max_headers_len > ifp->tx.len) {
@@ -4844,17 +5282,14 @@ static size_t trim_len(struct mg_connection *c, size_t len) {
 long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
   struct mg_tcpip_if *ifp = c->mgr->ifp;
   struct connstate *s = (struct connstate *) (c + 1);
-  uint32_t dst_ip = c->rem.ip4;
   len = trim_len(c, len);
   if (c->is_udp) {
-    if (!tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port, buf,
-                len))
-      return MG_IO_WAIT;
+    if (!tx_udp(ifp, s->mac, &c->loc, &c->rem, buf, len)) return MG_IO_WAIT;
   } else {  // TCP, cap to peer's MSS
     size_t sent;
     if (len > s->dmss) len = s->dmss;  // RFC-6691: reduce if sending opts
-    sent = tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port,
-                  c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), buf, len);
+    sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_PUSH | TH_ACK,
+                  mg_htonl(s->seq), mg_htonl(s->ack), buf, len);
     if (sent == 0) {
       return MG_IO_WAIT;
     } else if (sent == (size_t) -1) {
@@ -4891,13 +5326,12 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
   struct connstate *s = (struct connstate *) (c + 1);
   struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv;
   uint32_t seq = mg_ntohl(pkt->tcp->seq);
-  uint32_t rem_ip = c->rem.ip4;
   if (pkt->tcp->flags & TH_FIN) {
     uint8_t flags = TH_ACK;
     if (mg_ntohl(pkt->tcp->seq) != s->ack) {
       MG_VERBOSE(("ignoring FIN, %x != %x", mg_ntohl(pkt->tcp->seq), s->ack));
-      tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-             mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
+      tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
+             mg_htonl(s->ack), "", 0);
       return;
     }
     // If we initiated the closure, we reply with ACK upon receiving FIN
@@ -4917,14 +5351,14 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
       c->is_draining = 1;
       settmout(c, MIP_TTYPE_FIN);
     }
-    tx_tcp(c->mgr->ifp, s->mac, rem_ip, flags, c->loc.port, c->rem.port,
-           mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
+    tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, flags, mg_htonl(s->seq),
+           mg_htonl(s->ack), "", 0);
     if (pkt->pay.len == 0) return;  // if no data, we're done
   } else if (pkt->pay.len <= 1 && mg_ntohl(pkt->tcp->seq) == s->ack - 1) {
     // Keep-Alive (RFC-9293 3.8.4, allow erroneous implementations)
     MG_VERBOSE(("%lu keepalive ACK", c->id));
-    tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-           mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
+    tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
+           mg_htonl(s->ack), NULL, 0);
     return;                        // no data to process
   } else if (pkt->pay.len == 0) {  // this is an ACK
     if (s->fin_rcvd && s->ttype == MIP_TTYPE_FIN) s->twclosure = true;
@@ -4935,8 +5369,8 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
       MG_VERBOSE(("ignoring duplicate pkt"));
     } else {
       MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack));
-      tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-             mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
+      tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
+             mg_htonl(s->ack), "", 0);
     }
     return;  // drop it
   } else if (io->size - io->len < pkt->pay.len &&
@@ -4959,8 +5393,8 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
   if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) {
     // Send ACK immediately
     MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked));
-    tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-           mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
+    tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
+           mg_htonl(s->ack), NULL, 0);
     s->unacked = 0;
     s->acked = s->ack;
     if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE);
@@ -5027,11 +5461,11 @@ static void backlog_poll(struct mg_mgr *mgr) {
 }
 
 // process options (MSS)
-static void handle_opt(struct connstate *s, struct tcp *tcp) {
+static void handle_opt(struct connstate *s, struct tcp *tcp, bool ip6) {
   uint8_t *opts = (uint8_t *) (tcp + 1);
   int len = 4 * ((int) (tcp->off >> 4) - ((int) sizeof(*tcp) / 4));
-  s->dmss = 536;     // assume default, RFC-9293 3.7.1
-  while (len > 0) {  // RFC-9293 3.1 3.2
+  s->dmss = ip6 ? 1220 : 536;  // assume default, RFC-9293 3.7.1
+  while (len > 0) {            // RFC-9293 3.1 3.2
     uint8_t kind = opts[0], optlen = 1;
     if (kind != 1) {         // No-Operation
       if (kind == 0) break;  // End of Option List
@@ -5052,7 +5486,7 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
   // - check clients (Group 1) and established connections (Group 3)
   if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN | TH_ACK)) {
     // client got a server connection accept
-    handle_opt(s, pkt->tcp);  // process options (MSS)
+    handle_opt(s, pkt->tcp, pkt->ip6 != NULL);  // process options (MSS)
     s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1;
     tx_tcp_ctrlresp(ifp, pkt, TH_ACK, pkt->tcp->ack);
     c->is_connecting = 0;  // Client connected
@@ -5086,7 +5520,7 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
       int key;
       uint32_t isn;
       if (pkt->tcp->sport != 0) {
-        handle_opt(&cs, pkt->tcp);  // process options (MSS)
+        handle_opt(&cs, pkt->tcp, pkt->ip6 != NULL);  // process options (MSS)
         key = backlog_insert(c, pkt->tcp->sport,
                              cs.dmss);  // backlog options (MSS)
         if (key < 0) return;  // no room in backlog, discard SYN, client retries
@@ -5124,13 +5558,13 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
   if (pkt->pay.len < sizeof(*pkt->ip)) return;  // Truncated
   if ((pkt->ip->ver >> 4) != 4) return;         // Not IP
   ihl = pkt->ip->ver & 0x0F;
-  if (ihl < 5) return;                     // bad IHL
-  if (pkt->pay.len < (uint16_t)(ihl * 4)) return;    // Truncated / malformed
+  if (ihl < 5) return;                              // bad IHL
+  if (pkt->pay.len < (uint16_t) (ihl * 4)) return;  // Truncated / malformed
   // There can be link padding, take length from IP header
-  len = mg_ntohs(pkt->ip->len); // IP datagram length
-  if (len < (ihl * 4) || len > pkt->pay.len) return; // malformed
-  pkt->pay.len = len; // strip padding
-  mkpay(pkt, (uint32_t *) pkt->ip + ihl);  // account for opts
+  len = mg_ntohs(pkt->ip->len);                       // IP datagram length
+  if (len < (uint16_t) (ihl * 4) || len > pkt->pay.len) return;  // malformed
+  pkt->pay.len = len;                                 // strip padding
+  mkpay(pkt, (uint32_t *) pkt->ip + ihl);             // account for opts
   frag = mg_ntohs(pkt->ip->frag);
   if (frag & IP_MORE_FRAGS_MSK || frag & IP_FRAG_OFFSET_MSK) {
     struct mg_connection *c;
@@ -5145,11 +5579,11 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
     rx_icmp(ifp, pkt);
   } else if (pkt->ip->proto == 17) {
     pkt->udp = (struct udp *) (pkt->pay.buf);
-    if (pkt->pay.len < sizeof(*pkt->udp)) return; // truncated
+    if (pkt->pay.len < sizeof(*pkt->udp)) return;  // truncated
     // Take length from UDP header
-    len = mg_ntohs(pkt->udp->len); // UDP datagram length
-    if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return; // malformed
-    pkt->pay.len = len; // strip excess data
+    len = mg_ntohs(pkt->udp->len);  // UDP datagram length
+    if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return;  // malformed
+    pkt->pay.len = len;  // strip excess data
     mkpay(pkt, pkt->udp + 1);
     MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
                 mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst,
@@ -5163,14 +5597,15 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
       mkpay(pkt, pkt->dhcp + 1);
       rx_dhcp_server(ifp, pkt);
     } else if (!rx_udp(ifp, pkt)) {
-      // Should send ICMP Destination Unreachable for unicasts, but keep silent
+      // Should send ICMP Destination Unreachable for unicasts, but keep
+      // silent
     }
   } else if (pkt->ip->proto == 6) {
     uint8_t off;
     pkt->tcp = (struct tcp *) (pkt->pay.buf);
     if (pkt->pay.len < sizeof(*pkt->tcp)) return;
     off = pkt->tcp->off >> 4;  // account for opts
-    if (pkt->pay.len < (uint16_t)(4 * off)) return;
+    if (pkt->pay.len < (uint16_t) (4 * off)) return;
     mkpay(pkt, (uint32_t *) pkt->tcp + off);
     MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
                 mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst,
@@ -5183,12 +5618,15 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
   }
 }
 
+#if MG_ENABLE_IPV6
 static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
-  uint16_t len = 0;
+  uint16_t len = 0, plen;
   uint8_t next, *nhdr;
   bool loop = true;
   if (pkt->pay.len < sizeof(*pkt->ip6)) return;  // Truncated
   if ((pkt->ip6->ver >> 4) != 0x6) return;       // Not IPv6
+  plen = mg_ntohs(pkt->ip6->plen);
+  if (plen > (pkt->pay.len - sizeof(*pkt->ip6))) return;  // malformed
   next = pkt->ip6->next;
   nhdr = (uint8_t *) (pkt->ip6 + 1);
   while (loop) {
@@ -5199,7 +5637,7 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
       case 51:  // Authentication RFC-4302
         MG_INFO(("IPv6 extension header %d", (int) next));
         next = nhdr[0];
-        len += (uint16_t)(8 * (nhdr[1] + 1));
+        len += (uint16_t) (8 * (nhdr[1] + 1));
         nhdr += 8 * (nhdr[1] + 1);
         break;
       case 44:  // Fragment 4.5
@@ -5219,31 +5657,38 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
         break;
     }
   }
+  if (len >= plen) return;
   // There can be link padding, take payload length from IPv6 header - options
   pkt->pay.buf = (char *) nhdr;
-  pkt->pay.len = mg_ntohs(pkt->ip6->plen) - len;
+  pkt->pay.len = plen - len;
   if (next == 58) {
     pkt->icmp6 = (struct icmp6 *) (pkt->pay.buf);
     if (pkt->pay.len < sizeof(*pkt->icmp6)) return;
     mkpay(pkt, pkt->icmp6 + 1);
     MG_DEBUG(("ICMPv6 %M -> %M len %u", mg_print_ip6, &pkt->ip6->src,
               mg_print_ip6, &pkt->ip6->dst, (int) pkt->pay.len));
-    // rx_icmp6(ifp, pkt);
+    rx_icmp6(ifp, pkt);
   } else if (next == 17) {
     pkt->udp = (struct udp *) (pkt->pay.buf);
     if (pkt->pay.len < sizeof(*pkt->udp)) return;
+    // Take length from UDP header
+    len = mg_ntohs(pkt->udp->len);  // UDP datagram length
+    if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return;  // malformed
+    pkt->pay.len = len;  // strip excess data
     mkpay(pkt, pkt->udp + 1);
     MG_DEBUG(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src,
               mg_ntohs(pkt->udp->sport), mg_print_ip6, &pkt->ip6->dst,
               mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
-    if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(546)) {
+    if (ifp->enable_dhcp6_client && pkt->udp->dport == mg_htons(546)) {
       pkt->dhcp6 = (struct dhcp6 *) (pkt->udp + 1);
       mkpay(pkt, pkt->dhcp6 + 1);
       // rx_dhcp6_client(ifp, pkt);
+#if 0
     } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(547)) {
       pkt->dhcp6 = (struct dhcp6 *) (pkt->udp + 1);
       mkpay(pkt, pkt->dhcp6 + 1);
-      // rx_dhcp6_server(ifp, pkt);
+      rx_dhcp6_server(ifp, pkt);
+#endif
     } else if (!rx_udp(ifp, pkt)) {
       // Should send ICMPv6 Destination Unreachable for unicasts, keep silent
     }
@@ -5252,7 +5697,7 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
     pkt->tcp = (struct tcp *) (pkt->pay.buf);
     if (pkt->pay.len < sizeof(*pkt->tcp)) return;
     off = pkt->tcp->off >> 4;  // account for opts
-    if (pkt->pay.len < sizeof(*pkt->tcp) + 4 * off) return;
+    if (pkt->pay.len < (uint16_t) (4 * off)) return;
     mkpay(pkt, (uint32_t *) pkt->tcp + off);
     MG_DEBUG(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src,
               mg_ntohs(pkt->tcp->sport), mg_print_ip6, &pkt->ip6->dst,
@@ -5264,13 +5709,16 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
       mg_hexdump(pkt->ip6, pkt->pay.len >= 32 ? 32 : pkt->pay.len);
   }
 }
+#else
+#define rx_ip6(x, y)
+#endif
 
 static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
   struct pkt pkt;
   memset(&pkt, 0, sizeof(pkt));
   pkt.pay.buf = pkt.raw.buf = (char *) buf;
-  pkt.pay.len = pkt.raw.len = len; // payload = raw
-  pkt.eth = (struct eth *) buf;   // Ethernet = raw
+  pkt.pay.len = pkt.raw.len = len;             // payload = raw
+  pkt.eth = (struct eth *) buf;                // Ethernet = raw
   if (pkt.raw.len < sizeof(*pkt.eth)) return;  // Truncated - runt?
   if (ifp->enable_mac_check &&
       memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
@@ -5286,7 +5734,7 @@ static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
   mkpay(&pkt, pkt.eth + 1);
   if (pkt.eth->type == mg_htons(0x806)) {
     pkt.arp = (struct arp *) (pkt.pay.buf);
-    if (pkt.pay.len < sizeof(*pkt.arp)) return; // Truncated
+    if (pkt.pay.len < sizeof(*pkt.arp)) return;  // Truncated
     mg_tcpip_call(ifp, MG_TCPIP_EV_ARP, &pkt.raw);
     rx_arp(ifp, &pkt);
   } else if (pkt.eth->type == mg_htons(0x86dd)) {
@@ -5301,6 +5749,65 @@ static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
   }
 }
 
+static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) {
+  if (ifp->state == MG_TCPIP_STATE_DOWN) return;
+  // DHCP RFC-2131 (4.4)
+  if (ifp->enable_dhcp_client && s1) {
+    if (ifp->state == MG_TCPIP_STATE_UP) {
+      tx_dhcp_discover(ifp);  // INIT (4.4.1)
+    } else if (ifp->state == MG_TCPIP_STATE_READY &&
+               ifp->lease_expire > 0) {  // BOUND / RENEWING / REBINDING
+      if (ifp->now >= ifp->lease_expire) {
+        ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;  // expired, release IP
+        onstatechange(ifp);
+      } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
+                 ((ifp->now / 1000) % 60) == 0) {
+        // hack: 30 min before deadline, try to rebind (4.3.6) every min
+        tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff);
+      }  // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
+    }
+  }
+}
+static void mg_ip_link(struct mg_tcpip_if *ifp, bool up) {
+  bool current = ifp->state != MG_TCPIP_STATE_DOWN;
+  if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
+  if (up != current) {  // link state has changed
+    ifp->state = up == false                               ? MG_TCPIP_STATE_DOWN
+                 : ifp->enable_dhcp_client || ifp->ip == 0 ? MG_TCPIP_STATE_UP
+                                                           : MG_TCPIP_STATE_IP;
+    onstatechange(ifp);
+  } else if (!ifp->enable_dhcp_client && ifp->state == MG_TCPIP_STATE_UP &&
+             ifp->ip) {
+    ifp->state = MG_TCPIP_STATE_IP;  // ifp->fn has set an IP
+    onstatechange(ifp);
+  }
+}
+
+#if MG_ENABLE_IPV6
+static void mg_ip6_poll(struct mg_tcpip_if *ifp, bool s1) {
+  if (ifp->state6 == MG_TCPIP_STATE_DOWN) return;
+  if (ifp->enable_slaac && s1 && ifp->state6 == MG_TCPIP_STATE_UP)
+    tx_ndp_rs(ifp, ifp->gw6, ifp->gw6mac);
+}
+static void mg_ip6_link(struct mg_tcpip_if *ifp, bool up) {
+  bool current = ifp->state6 != MG_TCPIP_STATE_DOWN;
+  if (!up && ifp->enable_slaac) ifp->ip6[0] = ifp->ip6[1] = 0;
+  if (up != current) {  // link state has changed
+    ifp->state6 = !up                                     ? MG_TCPIP_STATE_DOWN
+                  : ifp->enable_slaac || ifp->ip6[0] == 0 ? MG_TCPIP_STATE_UP
+                                                          : MG_TCPIP_STATE_IP;
+    onstate6change(ifp);
+  } else if (!ifp->enable_slaac && ifp->state6 == MG_TCPIP_STATE_UP &&
+             ifp->ip6[0]) {
+    ifp->state6 = MG_TCPIP_STATE_IP;  // ifp->fn has set an IP
+    onstate6change(ifp);
+  }
+}
+#else
+#define mg_ip6_poll(x, y)
+#define mg_ip6_link(x, y)
+#endif
+
 static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
   struct mg_connection *c;
   bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, now);
@@ -5309,9 +5816,16 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
   if (expired_1000ms) {
 #if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
     const char *names[] = {"down", "up", "req", "ip", "ready"};
-    MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u",
-             names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent,
-             ifp->ndrop, ifp->nerr));
+    size_t max = sizeof(names) / sizeof(char *);
+    unsigned int state = ifp->state >= max ? max - 1 : ifp->state;
+    MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u", names[state],
+             mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent, ifp->ndrop,
+             ifp->nerr));
+#if MG_ENABLE_IPV6
+    state = ifp->state6 >= max ? max - 1 : ifp->state6;
+    if (state > MG_TCPIP_STATE_UP)
+      MG_INFO(("Status: %s, IPv6: %M", names[state], mg_print_ip6, &ifp->ip6));
+#endif
 #endif
     backlog_poll(ifp->mgr);
   }
@@ -5320,47 +5834,30 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
     ifp->state = MG_TCPIP_STATE_READY;  // keep best-effort MAC
     onstatechange(ifp);
   }
+#if MG_ENABLE_IPV6
+  // Handle gw NS/NA req/resp timeout, order is important
+  if (expired_1000ms && ifp->state6 == MG_TCPIP_STATE_IP) {
+    ifp->state6 = MG_TCPIP_STATE_READY;  // keep best-effort MAC
+    onstate6change(ifp);
+  }
+#endif
+
   // poll driver
   if (ifp->driver->poll) {
     bool up = ifp->driver->poll(ifp, expired_1000ms);
-    // Handle physical interface up/down status
+    // Handle physical interface up/down status, ifp->state rules over state6
     if (expired_1000ms) {
-      bool current = ifp->state != MG_TCPIP_STATE_DOWN;
-      if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
-      if (up != current) {  // link state has changed
-        ifp->state = up == false ? MG_TCPIP_STATE_DOWN
-                     : ifp->enable_dhcp_client || ifp->ip == 0
-                         ? MG_TCPIP_STATE_UP
-                         : MG_TCPIP_STATE_IP;
-        onstatechange(ifp);
-      } else if (!ifp->enable_dhcp_client && ifp->state == MG_TCPIP_STATE_UP &&
-                 ifp->ip) {
-        ifp->state = MG_TCPIP_STATE_IP;  // ifp->fn has set an IP
-        onstatechange(ifp);
-      }
+      mg_ip_link(ifp, up);   // Handle IPv4
+      mg_ip6_link(ifp, up);  // Handle IPv6
       if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down"));
       mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL);
     }
   }
-  if (ifp->state == MG_TCPIP_STATE_DOWN) return;
 
-  // DHCP RFC-2131 (4.4)
-  if (ifp->enable_dhcp_client && expired_1000ms) {
-    if (ifp->state == MG_TCPIP_STATE_UP) {
-      tx_dhcp_discover(ifp);  // INIT (4.4.1)
-    } else if (ifp->state == MG_TCPIP_STATE_READY &&
-               ifp->lease_expire > 0) {  // BOUND / RENEWING / REBINDING
-      if (ifp->now >= ifp->lease_expire) {
-        ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;  // expired, release IP
-        onstatechange(ifp);
-      } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
-                 ((ifp->now / 1000) % 60) == 0) {
-        // hack: 30 min before deadline, try to rebind (4.3.6) every min
-        tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff);
-      }  // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
-    }
-  }
+  mg_ip_poll(ifp, expired_1000ms);   // Handle IPv4
+  mg_ip6_poll(ifp, expired_1000ms);  // Handle IPv6
 
+  if (ifp->state == MG_TCPIP_STATE_DOWN) return;
   // Read data from the network
   if (ifp->driver->rx != NULL) {  // Simple polling driver, returns one frame
     size_t len =
@@ -5381,10 +5878,8 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
   // Process timeouts
   for (c = ifp->mgr->conns; c != NULL; c = c->next) {
     struct connstate *s = (struct connstate *) (c + 1);
-    uint32_t rem_ip;
     if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving)
       continue;
-    rem_ip = c->rem.ip4;
     if (ifp->now > s->timer) {
       if (s->ttype == MIP_TTYPE_ARP) {
         mg_error(c, "ARP timeout");
@@ -5392,8 +5887,8 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
         continue;
       } else if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) {
         MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack));
-        tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-               mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
+        tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
+               mg_htonl(s->ack), NULL, 0);
         s->acked = s->ack;
       } else if (s->ttype == MIP_TTYPE_SYN) {
         mg_error(c, "Connection timeout");
@@ -5405,8 +5900,8 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
           mg_error(c, "keepalive");
         } else {
           MG_VERBOSE(("%lu keepalive", c->id));
-          tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-                 mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0);
+          tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq - 1),
+                 mg_htonl(s->ack), NULL, 0);
         }
       }
 
@@ -5463,6 +5958,15 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
     ifp->eport |= MG_EPHEMERAL_PORT_BASE;         // Random from
                                            // MG_EPHEMERAL_PORT_BASE to 65535
     if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM"));
+#if MG_ENABLE_IPV6
+    // If static configuration is used, link-local and global addresses,
+    // prefix length, and gw are already filled at this point.
+    if (ifp->ip6[0] == 0 && ifp->ip6[1] == 0) {
+      ifp->enable_slaac = true;
+      ip6genll((uint8_t *) ifp->ip6ll, ifp->mac);  // build link-local address
+    }
+    memset(ifp->gw6mac, 255, sizeof(ifp->gw6mac));  // Set best-effort to bcast
+#endif
   }
 }
 
@@ -5475,9 +5979,7 @@ void mg_tcpip_free(struct mg_tcpip_if *ifp) {
 static void send_syn(struct mg_connection *c) {
   struct connstate *s = (struct connstate *) (c + 1);
   uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
-  uint32_t rem_ip = c->rem.ip4;
-  tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port, isn, 0,
-         NULL, 0);
+  tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_SYN, isn, 0, NULL, 0);
 }
 
 static void mac_resolved(struct mg_connection *c) {
@@ -5499,34 +6001,72 @@ static void ip4_mcastmac(uint8_t *mac, uint32_t *ip) {
 
 void mg_connect_resolved(struct mg_connection *c) {
   struct mg_tcpip_if *ifp = c->mgr->ifp;
-  uint32_t rem_ip = c->rem.ip4;
   c->is_resolving = 0;
   if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE;
-  c->loc.ip4 = ifp->ip;
   c->loc.port = mg_htons(ifp->eport++);
+#if MG_ENABLE_IPV6
+  if (c->rem.is_ip6) {
+    c->loc.ip6[0] = ifp->ip6[0], c->loc.ip6[1] = ifp->ip6[1],
+    c->loc.is_ip6 = true;
+  } else
+#endif
+  {
+    c->loc.ip4 = ifp->ip;
+  }
   MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port,
             &c->rem));
   mg_call(c, MG_EV_RESOLVE, NULL);
   c->is_connecting = 1;
-  if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) {
-    struct connstate *s = (struct connstate *) (c + 1);
-    memset(s->mac, 0xFF, sizeof(s->mac));  // global or local broadcast
-    mac_resolved(c);
-  } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask)) &&
-             rem_ip != ifp->gw) {  // skip if gw (onstatechange -> READY -> ARP)
-    // If we're in the same LAN, fire an ARP lookup.
-    MG_DEBUG(("%lu ARP lookup...", c->id));
-    mg_tcpip_arp_request(ifp, rem_ip, NULL);
-    settmout(c, MIP_TTYPE_ARP);
-    c->is_arplooking = 1;
-  } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
-    struct connstate *s = (struct connstate *) (c + 1);  // 224 to 239, E0 to EF
-    ip4_mcastmac(s->mac, &rem_ip);                       // multicast group
-    mac_resolved(c);
-  } else {
-    struct connstate *s = (struct connstate *) (c + 1);
-    memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac));
-    mac_resolved(c);
+#if MG_ENABLE_IPV6
+  if (c->rem.is_ip6) {
+    if (c->is_udp &&
+        MG_IP6MATCH(c->rem.ip6, ip6_allnodes.u)) {  // local broadcast
+      struct connstate *s = (struct connstate *) (c + 1);
+      memcpy(s->mac, ip6mac_allnodes, sizeof(s->mac));
+      mac_resolved(c);
+    } else if (c->rem.ip6[0] == ifp->ip6[0] &&
+               !MG_IP6MATCH(c->rem.ip6,
+                            ifp->gw6)) {  // skip if gw (onstate6change -> NS)
+      // If we're in the same LAN, fire a Neighbor Solicitation
+      MG_DEBUG(("%lu NS lookup...", c->id));
+      tx_ndp_ns(ifp, c->rem.ip6, ifp->mac);
+      settmout(c, MIP_TTYPE_ARP);
+      c->is_arplooking = 1;
+    } else if (*((uint8_t *) c->rem.ip6) == 0xFF) {  // multicast
+      struct connstate *s = (struct connstate *) (c + 1);
+      ip6_mcastmac(s->mac, c->rem.ip6);
+      mac_resolved(c);
+    } else {
+      struct connstate *s = (struct connstate *) (c + 1);
+      memcpy(s->mac, ifp->gw6mac, sizeof(s->mac));
+      mac_resolved(c);
+    }
+  } else
+#endif
+  {
+    uint32_t rem_ip = c->rem.ip4;
+    if (c->is_udp &&
+        (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) {
+      struct connstate *s = (struct connstate *) (c + 1);
+      memset(s->mac, 0xFF, sizeof(s->mac));  // global or local broadcast
+      mac_resolved(c);
+    } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask)) &&
+               rem_ip != ifp->gw) {  // skip if gw (onstatechange -> ARP)
+      // If we're in the same LAN, fire an ARP lookup.
+      MG_DEBUG(("%lu ARP lookup...", c->id));
+      mg_tcpip_arp_request(ifp, rem_ip, NULL);
+      settmout(c, MIP_TTYPE_ARP);
+      c->is_arplooking = 1;
+    } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
+      struct connstate *s =
+          (struct connstate *) (c + 1);  // 224 to 239, E0 to EF
+      ip4_mcastmac(s->mac, &rem_ip);     // multicast group
+      mac_resolved(c);
+    } else {
+      struct connstate *s = (struct connstate *) (c + 1);
+      memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac));
+      mac_resolved(c);
+    }
   }
 }
 
@@ -5535,6 +6075,12 @@ bool mg_open_listener(struct mg_connection *c, const char *url) {
   if (!mg_aton(mg_url_host(url), &c->loc)) {
     MG_ERROR(("invalid listening URL: %s", url));
     return false;
+#if MG_ENABLE_IPV6
+  } else if (c->loc.is_ip6) {
+    c->loc.ip6[0] = c->mgr->ifp->ip6[0], c->loc.ip6[1] = c->mgr->ifp->ip6[1];
+#endif
+  } else {
+    c->loc.ip4 = c->mgr->ifp->ip;
   }
   return true;
 }
@@ -5554,9 +6100,8 @@ static void init_closure(struct mg_connection *c) {
   struct connstate *s = (struct connstate *) (c + 1);
   if (c->is_udp == false && c->is_listening == false &&
       c->is_connecting == false) {  // For TCP conns,
-  uint32_t rem_ip = c->rem.ip4;
-    tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port,
-           c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
+    tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_FIN | TH_ACK,
+           mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
     settmout(c, MIP_TTYPE_FIN);
   }
 }
@@ -5608,17 +6153,19 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
 bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
   struct mg_tcpip_if *ifp = c->mgr->ifp;
   bool res = false;
-  uint32_t rem_ip = c->rem.ip4;
-  if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) {
+  if (!c->loc.is_ip6 && (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY)) {
+    mg_error(c, "net down");
+#if MG_ENABLE_IPV6
+  } else if (c->loc.is_ip6 && ifp->state6 != MG_TCPIP_STATE_READY) {
     mg_error(c, "net down");
+#endif
   } else if (c->is_udp && (c->is_arplooking || c->is_resolving)) {
     // Fail to send, no target MAC or IP
     MG_VERBOSE(("still resolving..."));
   } else if (c->is_udp) {
     struct connstate *s = (struct connstate *) (c + 1);
     len = trim_len(c, len);  // Trimming length if necessary
-    res = tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf,
-                 len);
+    res = tx_udp(ifp, s->mac, &c->loc, &c->rem, buf, len);
   } else {
     res = mg_iobuf_add(&c->send, c->send.len, buf, len);
   }
@@ -5627,7 +6174,7 @@ bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
 
 uint8_t mcast_addr[6] = {0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb};
 void mg_multicast_add(struct mg_connection *c, char *ip) {
-  (void) ip;  // ip4_mcastmac(mcast_mac, &ip);
+  (void) ip;  // ip4/6_mcastmac(mcast_mac, &ip); ipv6 param
   // TODO(): actual IP -> MAC; check database, update
   c->mgr->ifp->update_mac_hash_table = true;  // mark dirty
 }
@@ -20241,6 +20788,10 @@ uint32_t mg_ntohl(uint32_t net) {
   return MG_LOAD_BE32(&net);
 }
 
+uint64_t mg_ntohll(uint64_t net) {
+  return MG_LOAD_BE64(&net);
+}
+
 void mg_delayms(unsigned int ms) {
   uint64_t to = mg_millis() + ms + 1;
   while (mg_millis() < to) (void) 0;
index 22a075f3393e5f109057a3c5775ff768ddaca336..6445d43a7828c836d55efaea2a9108e68d88c642 100644 (file)
@@ -1057,6 +1057,26 @@ struct timeval {
 #define MG_TCPIP_GW MG_IPV4(0, 0, 0, 0)  // Default is 0.0.0.0 (DHCP)
 #endif
 
+#if MG_ENABLE_IPV6
+
+#ifndef MG_TCPIP_GLOBAL
+#define MG_TCPIP_GLOBAL MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0)
+#endif
+
+#ifndef MG_TCPIP_IPV6_LINKLOCAL
+#define MG_TCPIP_IPV6_LINKLOCAL MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0)
+#endif
+
+#ifndef MG_TCPIP_PREFIX_LEN
+#define MG_TCPIP_PREFIX_LEN 0
+#endif
+
+#ifndef MG_TCPIP_GW6
+#define MG_TCPIP_GW6 MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0)
+#endif
+
+#endif
+
 #ifndef MG_SET_MAC_ADDRESS
 #define MG_SET_MAC_ADDRESS(mac)
 #endif
@@ -1368,6 +1388,16 @@ void mg_delayms(unsigned int ms);
 
 #define MG_IPV4(a, b, c, d) mg_htonl(MG_U32(a, b, c, d))
 
+#define MG_IPV6(a, b, c, d, e, f, g ,h) \
+  { (uint8_t)((a)>>8),(uint8_t)(a), \
+    (uint8_t)((b)>>8),(uint8_t)(b), \
+    (uint8_t)((c)>>8),(uint8_t)(c), \
+    (uint8_t)((d)>>8),(uint8_t)(d), \
+    (uint8_t)((e)>>8),(uint8_t)(e), \
+    (uint8_t)((f)>>8),(uint8_t)(f), \
+    (uint8_t)((g)>>8),(uint8_t)(g), \
+    (uint8_t)((h)>>8),(uint8_t)(h) }
+
 // For printing IPv4 addresses: printf("%d.%d.%d.%d\n", MG_IPADDR_PARTS(&ip))
 #define MG_U8P(ADDR) ((uint8_t *) (ADDR))
 #define MG_IPADDR_PARTS(ADDR) \
@@ -1382,6 +1412,14 @@ void mg_delayms(unsigned int ms);
   ((uint32_t) (((uint32_t) MG_U8P(p)[0] << 24U) | \
                ((uint32_t) MG_U8P(p)[1] << 16U) | \
                ((uint32_t) MG_U8P(p)[2] << 8U) | MG_U8P(p)[3]))
+#define MG_LOAD_BE64(p)                           \
+  ((uint64_t) (((uint64_t) MG_U8P(p)[0] << 56U) | \
+               ((uint64_t) MG_U8P(p)[1] << 48U) | \
+               ((uint64_t) MG_U8P(p)[2] << 40U) | \
+               ((uint64_t) MG_U8P(p)[3] << 32U) | \
+               ((uint64_t) MG_U8P(p)[4] << 24U) | \
+               ((uint64_t) MG_U8P(p)[5] << 16U) | \
+               ((uint64_t) MG_U8P(p)[6] << 8U) | MG_U8P(p)[7]))
 #define MG_STORE_BE16(p, n)           \
   do {                                \
     MG_U8P(p)[0] = ((n) >> 8U) & 255; \
@@ -1400,11 +1438,24 @@ void mg_delayms(unsigned int ms);
     MG_U8P(p)[2] = ((n) >> 8U) & 255;  \
     MG_U8P(p)[3] = (n) &255;           \
   } while (0)
+#define MG_STORE_BE64(p, n)            \
+  do {                                 \
+    MG_U8P(p)[0] = ((n) >> 56U) & 255; \
+    MG_U8P(p)[1] = ((n) >> 48U) & 255; \
+    MG_U8P(p)[2] = ((n) >> 40U) & 255; \
+    MG_U8P(p)[3] = ((n) >> 32U) & 255; \
+    MG_U8P(p)[4] = ((n) >> 24U) & 255; \
+    MG_U8P(p)[5] = ((n) >> 16U) & 255; \
+    MG_U8P(p)[6] = ((n) >> 8U) & 255;  \
+    MG_U8P(p)[7] = (n) &255;           \
+  } while (0)
 
 uint16_t mg_ntohs(uint16_t net);
 uint32_t mg_ntohl(uint32_t net);
+uint64_t mg_ntohll(uint64_t net);
 #define mg_htons(x) mg_ntohs(x)
 #define mg_htonl(x) mg_ntohl(x)
+#define mg_htonll(x) mg_ntohll(x)
 
 #define MG_REG(x) ((volatile uint32_t *) (x))[0]
 #define MG_BIT(x) (((uint32_t) 1U) << (x))
@@ -3084,18 +3135,17 @@ typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev,
                                          void *ev_data);
 
 enum {
-  MG_TCPIP_EV_ST_CHG,  // state change                   uint8_t * (&ifp->state)
-  MG_TCPIP_EV_DHCP_DNS,   // DHCP DNS assignment            uint32_t *ipaddr
-  MG_TCPIP_EV_DHCP_SNTP,  // DHCP SNTP assignment           uint32_t *ipaddr
-  MG_TCPIP_EV_ARP,        // Got ARP packet                 struct mg_str *
-  MG_TCPIP_EV_TIMER_1S,   // 1 second timer                 NULL
-  MG_TCPIP_EV_WIFI_SCAN_RESULT,  // Wi-Fi scan results             struct
-                                 // mg_wifi_scan_bss_data *
-  MG_TCPIP_EV_WIFI_SCAN_END,     // Wi-Fi scan has finished        NULL
-  MG_TCPIP_EV_WIFI_CONNECT_ERR,  // Wi-Fi connect has failed       driver and
-                                 // chip specific
-  MG_TCPIP_EV_DRIVER,  // Driver event                   driver specific
-  MG_TCPIP_EV_USER     // Starting ID for user events
+  MG_TCPIP_EV_ST_CHG,           // state change                   uint8_t * (&ifp->state)
+  MG_TCPIP_EV_DHCP_DNS,         // DHCP DNS assignment            uint32_t *ipaddr
+  MG_TCPIP_EV_DHCP_SNTP,        // DHCP SNTP assignment           uint32_t *ipaddr
+  MG_TCPIP_EV_ARP,              // Got ARP packet                 struct mg_str *
+  MG_TCPIP_EV_TIMER_1S,         // 1 second timer                 NULL
+  MG_TCPIP_EV_WIFI_SCAN_RESULT, // Wi-Fi scan results             struct mg_wifi_scan_bss_data *
+  MG_TCPIP_EV_WIFI_SCAN_END,    // Wi-Fi scan has finished        NULL
+  MG_TCPIP_EV_WIFI_CONNECT_ERR, // Wi-Fi connect has failed       driver and chip specific
+  MG_TCPIP_EV_DRIVER,           // Driver event                   driver specific
+  MG_TCPIP_EV_ST6_CHG,          // state6 change                  uint8_t * (&ifp->state6)
+  MG_TCPIP_EV_USER              // Starting ID for user events
 };
 
 // Network interface
@@ -3120,6 +3170,13 @@ struct mg_tcpip_if {
   char dhcp_name[MG_TCPIP_DHCPNAME_SIZE];  // Name for DHCP, "mip" if unset
   uint16_t mtu;                            // Interface MTU
 #define MG_TCPIP_MTU_DEFAULT 1500
+#if MG_ENABLE_IPV6
+  uint64_t ip6ll[2], ip6[2];       // IPv6 link-local and global addresses
+  uint8_t prefix_len;              // Prefix length
+  uint64_t gw6[2];                 // Default gateway
+  bool enable_slaac;               // Enable IPv6 address autoconfiguration
+  bool enable_dhcp6_client;        // Enable DCHPv6 client
+#endif
 
   // Internal state, user can use it but should not change it
   uint8_t gwmac[6];             // Router's MAC
@@ -3132,14 +3189,17 @@ struct mg_tcpip_if {
   volatile uint32_t nrecv;      // Number of received frames
   volatile uint32_t nsent;      // Number of transmitted frames
   volatile uint32_t nerr;       // Number of driver errors
-  uint8_t state;                // Current state
+  uint8_t state;                // Current link and IPv4 state
 #define MG_TCPIP_STATE_DOWN 0   // Interface is down
 #define MG_TCPIP_STATE_UP 1     // Interface is up
 #define MG_TCPIP_STATE_REQ 2    // Interface is up, DHCP REQUESTING state
 #define MG_TCPIP_STATE_IP 3     // Interface is up and has an IP assigned
 #define MG_TCPIP_STATE_READY 4  // Interface has fully come up, ready to work
+#if MG_ENABLE_IPV6
+  uint8_t gw6mac[6];             // IPv6 Router's MAC
+  uint8_t state6;                // Current IPv6 state
+#endif
 };
-
 void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
 void mg_tcpip_free(struct mg_tcpip_if *);
 void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp);
@@ -3675,6 +3735,18 @@ struct mg_tcpip_driver_stm32h_data {
 #define MG_ENABLE_ETH_IRQ()
 #endif
 
+#if MG_ENABLE_IPV6
+#define MG_IPV6_INIT(mif)                                         \
+  do {                                                            \
+    memcpy(mif.ip6ll, (uint8_t[16]) MG_TCPIP_IPV6_LINKLOCAL, 16);     \
+    memcpy(mif.ip6, (uint8_t[16]) MG_TCPIP_GLOBAL, 16);           \
+    memcpy(mif.gw6, (uint8_t[16]) MG_TCPIP_GW6, 16);              \
+    mif.prefix_len = MG_TCPIP_PREFIX_LEN;                        \
+  } while(0)
+#else
+#define MG_IPV6_INIT(mif)
+#endif
+
 #define MG_TCPIP_DRIVER_INIT(mgr)                                 \
   do {                                                            \
     static struct mg_tcpip_driver_stm32h_data driver_data_;       \
@@ -3688,6 +3760,7 @@ struct mg_tcpip_driver_stm32h_data {
     mif_.driver = &mg_tcpip_driver_stm32h;                        \
     mif_.driver_data = &driver_data_;                             \
     MG_SET_MAC_ADDRESS(mif_.mac);                                 \
+    MG_IPV6_INIT(mif_);                                           \
     mg_tcpip_init(mgr, &mif_);                                    \
     MG_ENABLE_ETH_IRQ();                                          \
     MG_INFO(("Driver: stm32h, MAC: %M", mg_print_mac, mif_.mac)); \
index 0a8dea4d923078b27fb7b6b1858c4934dc5f9a76..7a6020ac8530b694c2c954f965fc4e077081faba 100644 (file)
 #define MG_TCPIP_GW MG_IPV4(0, 0, 0, 0)  // Default is 0.0.0.0 (DHCP)
 #endif
 
+#if MG_ENABLE_IPV6
+
+#ifndef MG_TCPIP_GLOBAL
+#define MG_TCPIP_GLOBAL MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0)
+#endif
+
+#ifndef MG_TCPIP_IPV6_LINKLOCAL
+#define MG_TCPIP_IPV6_LINKLOCAL MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0)
+#endif
+
+#ifndef MG_TCPIP_PREFIX_LEN
+#define MG_TCPIP_PREFIX_LEN 0
+#endif
+
+#ifndef MG_TCPIP_GW6
+#define MG_TCPIP_GW6 MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0)
+#endif
+
+#endif
+
 #ifndef MG_SET_MAC_ADDRESS
 #define MG_SET_MAC_ADDRESS(mac)
 #endif
index 3d406264ce0b7dd655f64f51157e8334dd5ac60c..a732ba1fbaf80a3a6a72c7cfd1da6e877be4e73d 100644 (file)
@@ -49,6 +49,18 @@ struct mg_tcpip_driver_stm32h_data {
 #define MG_ENABLE_ETH_IRQ()
 #endif
 
+#if MG_ENABLE_IPV6
+#define MG_IPV6_INIT(mif)                                         \
+  do {                                                            \
+    memcpy(mif.ip6ll, (uint8_t[16]) MG_TCPIP_IPV6_LINKLOCAL, 16);     \
+    memcpy(mif.ip6, (uint8_t[16]) MG_TCPIP_GLOBAL, 16);           \
+    memcpy(mif.gw6, (uint8_t[16]) MG_TCPIP_GW6, 16);              \
+    mif.prefix_len = MG_TCPIP_PREFIX_LEN;                        \
+  } while(0)
+#else
+#define MG_IPV6_INIT(mif)
+#endif
+
 #define MG_TCPIP_DRIVER_INIT(mgr)                                 \
   do {                                                            \
     static struct mg_tcpip_driver_stm32h_data driver_data_;       \
@@ -62,6 +74,7 @@ struct mg_tcpip_driver_stm32h_data {
     mif_.driver = &mg_tcpip_driver_stm32h;                        \
     mif_.driver_data = &driver_data_;                             \
     MG_SET_MAC_ADDRESS(mif_.mac);                                 \
+    MG_IPV6_INIT(mif_);                                           \
     mg_tcpip_init(mgr, &mif_);                                    \
     MG_ENABLE_ETH_IRQ();                                          \
     MG_INFO(("Driver: stm32h, MAC: %M", mg_print_mac, mif_.mac)); \
index c62864a7b94eadeb321dc8eb3388ee673c964767..b5b0f1faa2283284ab91f3620c4581682488d6ed 100644 (file)
@@ -67,8 +67,8 @@ struct ip6 {
   uint16_t plen;     // Payload length
   uint8_t next;      // Upper level protocol
   uint8_t hops;      // Hop limit
-  uint8_t src[16];   // Source IP
-  uint8_t dst[16];   // Destination IP
+  uint64_t src[2];   // Source IP
+  uint64_t dst[2];   // Destination IP
 };
 
 struct icmp {
@@ -83,6 +83,19 @@ struct icmp6 {
   uint16_t csum;
 };
 
+struct ndp_na {
+  uint8_t res[4];    // R S O, reserved
+  uint64_t addr[2];  // Target address
+};
+
+struct ndp_ra {
+  uint8_t cur_hop_limit;
+  uint8_t flags;  // M,O,Prf,Resvd
+  uint16_t router_lifetime;
+  uint32_t reachable_time;
+  uint32_t retrans_timer;
+};
+
 struct arp {
   uint16_t fmt;    // Format of hardware address
   uint16_t pro;    // Format of protocol address
@@ -142,6 +155,8 @@ struct dhcp6 {
 
 #pragma pack(pop)
 
+// pkt is 8-bit aligned, pointers to headers hint compilers to generate
+// byte-copy code for micros with alignment constraints
 struct pkt {
   struct mg_str raw;  // Raw packet data
   struct mg_str pay;  // Payload data
@@ -160,18 +175,11 @@ struct pkt {
 
 static void mg_tcpip_call(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
 #if MG_ENABLE_PROFILE
-  const char *names[] = {
-  "TCPIP_EV_ST_CHG",
-  "TCPIP_EV_DHCP_DNS",
-  "TCPIP_EV_DHCP_SNTP",
-  "TCPIP_EV_ARP",
-  "TCPIP_EV_TIMER_1S",
-  "TCPIP_EV_WIFI_SCAN_RESULT",
-  "TCPIP_EV_WIFI_SCAN_END",
-  "TCPIP_EV_WIFI_CONNECT_ERR",
-  "TCPIP_EV_DRIVER",
-  "TCPIP_EV_USER" 
-  };
+  const char *names[] = {"TCPIP_EV_ST_CHG",        "TCPIP_EV_DHCP_DNS",
+                         "TCPIP_EV_DHCP_SNTP",     "TCPIP_EV_ARP",
+                         "TCPIP_EV_TIMER_1S",      "TCPIP_EV_WIFI_SCAN_RESULT",
+                         "TCPIP_EV_WIFI_SCAN_END", "TCPIP_EV_WIFI_CONNECT_ERR",
+                         "TCPIP_EV_DRIVER",        "TCPIP_EV_USER"};
   if (ev != MG_TCPIP_EV_POLL && ev < (int) (sizeof(names) / sizeof(names[0]))) {
     MG_PROF_ADD(c, names[ev]);
   }
@@ -205,6 +213,61 @@ static uint16_t ipcsum(const void *buf, size_t len) {
   return csumfin(sum);
 }
 
+#if MG_ENABLE_IPV6
+static void meui64(uint8_t *addr, uint8_t *mac) {
+  *addr++ = *mac++ ^ (uint8_t) 0x02, *addr++ = *mac++, *addr++ = *mac++;
+  *addr++ = 0xff, *addr++ = 0xfe;
+  *addr++ = *mac++, *addr++ = *mac++, *addr = *mac;
+}
+static void ip6gen(uint8_t *addr, uint8_t *prefix, uint8_t *mac) {
+  memcpy(addr, prefix, 8);  // RFC-4291 2.5.4
+  meui64(addr + 8, mac);    // 2.5.1
+}
+static void ip6genll(uint8_t *addr, uint8_t *mac) {
+  uint8_t prefix[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0};  // RFC-4291 2.5.6
+  ip6gen(addr, prefix, mac);                           // 2.5.1
+}
+static void ip6sn(uint64_t *addr, uint64_t *sn_addr) {
+  // Build solicited-node multicast address from a given unicast IP
+  // RFC-4291 2.7
+  uint8_t *sn = (uint8_t *) sn_addr;
+  memset(sn_addr, 0, 16);
+  sn[0] = 0xff;
+  sn[1] = 0x02;
+  sn[11] = 0x01;
+  sn[12] = 0xff;
+  sn[13] = ((uint8_t *) addr)[13];
+  sn[14] = ((uint8_t *) addr)[14];
+  sn[15] = ((uint8_t *) addr)[15];
+}
+static void ip6_mcastmac(uint8_t *mac, uint64_t *ip6) {
+  // Build multicast MAC address from a solicited-node
+  // multicast IPv6 address, RFC-4291 2.7
+  uint8_t *ip = (uint8_t *) ip6;
+  mac[0] = 0x33;
+  mac[1] = 0x33;
+  mac[2] = ip[12];
+  mac[3] = ip[13];
+  mac[4] = ip[14];
+  mac[5] = ip[15];
+}
+
+union ip6addr {
+  uint64_t u[2];
+  uint8_t b[16];
+};
+
+static const union ip6addr ip6_allrouters = {
+    .b = {0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02}};
+static const union ip6addr ip6_allnodes = {
+    .b = {0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}};
+static const uint8_t ip6mac_allnodes[6] = {0x33, 0x33, 0x00, 0x00, 0x00, 0x01};
+static const uint8_t ip6mac_allrouters[6] = {0x33, 0x33, 0x00,
+                                             0x00, 0x00, 0x02};
+
+#define MG_IP6MATCH(a, b) (a[0] == b[0] && a[1] == b[1])
+#endif
+
 static void settmout(struct mg_connection *c, uint8_t type) {
   struct mg_tcpip_if *ifp = c->mgr->ifp;
   struct connstate *s = (struct connstate *) (c + 1);
@@ -258,6 +321,7 @@ static void onstatechange(struct mg_tcpip_if *ifp) {
 static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
                         uint8_t proto, uint32_t ip_src, uint32_t ip_dst,
                         size_t plen) {
+  // ifp->tx.buf is 8-bit aligned, keep other headers as pointers, see pkt
   struct eth *eth = (struct eth *) ifp->tx.buf;
   struct ip *ip = (struct ip *) (eth + 1);
   memcpy(eth->dst, mac_dst, sizeof(eth->dst));
@@ -275,31 +339,69 @@ static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
   return ip;
 }
 
-static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
-                   uint16_t sport, uint32_t ip_dst, uint16_t dport,
+#if MG_ENABLE_IPV6
+static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                          uint8_t next, uint64_t *ip_src, uint64_t *ip_dst,
+                          size_t plen);
+#endif
+
+static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                   struct mg_addr *ip_src, struct mg_addr *ip_dst,
                    const void *buf, size_t len) {
-  struct ip *ip =
-      tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp));
-  struct udp *udp = (struct udp *) (ip + 1);
+  struct ip *ip = NULL;
+  struct udp *udp;
   size_t eth_len;
   uint32_t cs;
-  // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
-  udp->sport = sport;
-  udp->dport = dport;
+#if MG_ENABLE_IPV6
+  struct ip6 *ip6 = NULL;
+  if (ip_dst->is_ip6) {
+    ip6 = tx_ip6(ifp, mac_dst, 17, ip_src->ip6, ip_dst->ip6,
+                 len + sizeof(struct udp));
+    udp = (struct udp *) (ip6 + 1);
+    eth_len = sizeof(struct eth) + sizeof(*ip6) + sizeof(*udp) + len;
+  } else
+#endif
+  {
+    ip = tx_ip(ifp, mac_dst, 17, ip_src->ip4, ip_dst->ip4,
+               len + sizeof(struct udp));
+    udp = (struct udp *) (ip + 1);
+    eth_len = sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len;
+  }
+  udp->sport = ip_src->port;
+  udp->dport = ip_dst->port;
   udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
   udp->csum = 0;
   cs = csumup(0, udp, sizeof(*udp));
   cs = csumup(cs, buf, len);
-  cs = csumup(cs, &ip->src, sizeof(ip->src));
-  cs = csumup(cs, &ip->dst, sizeof(ip->dst));
-  cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
+#if MG_ENABLE_IPV6
+  if (ip_dst->is_ip6) {
+    cs = csumup(cs, &ip6->src, sizeof(ip6->src));
+    cs = csumup(cs, &ip6->dst, sizeof(ip6->dst));
+  } else
+#endif
+  {
+    cs = csumup(cs, &ip->src, sizeof(ip->src));
+    cs = csumup(cs, &ip->dst, sizeof(ip->dst));
+  }
+  cs += (uint32_t) (17 + sizeof(*udp) + len);
   udp->csum = csumfin(cs);
   memmove(udp + 1, buf, len);
-  // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len));
-  eth_len = sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len;
   return (ether_output(ifp, eth_len) == eth_len);
 }
 
+static bool tx_udp4(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
+                    uint16_t sport, uint32_t ip_dst, uint16_t dport,
+                    const void *buf, size_t len) {
+  struct mg_addr ips, ipd;
+  memset(&ips, 0, sizeof(ips));
+  ips.ip4 = ip_src;
+  ips.port = sport;
+  memset(&ipd, 0, sizeof(ipd));
+  ipd.ip4 = ip_dst;
+  ipd.port = dport;
+  return tx_udp(ifp, mac_dst, &ips, &ipd, buf, len);
+}
+
 static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
                     uint32_t ip_dst, uint8_t *opts, size_t optslen,
                     bool ciaddr) {
@@ -310,8 +412,8 @@ static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
   memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
   memcpy(&dhcp.options, opts, optslen);
   if (ciaddr) dhcp.ciaddr = ip_src;
-  tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp,
-         sizeof(dhcp));
+  tx_udp4(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp,
+          sizeof(dhcp));
 }
 
 static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
@@ -367,12 +469,18 @@ static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt,
                                      bool lsn) {
   struct mg_connection *c = NULL;
   for (c = mgr->conns; c != NULL; c = c->next) {
-    if (c->is_arplooking && pkt->arp &&
-        memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0)
+    if (c->is_arplooking && pkt->arp && pkt->arp->spa == c->rem.ip4) break;
+#if MG_ENABLE_IPV6
+    if (c->is_arplooking && pkt->icmp6 && pkt->icmp6->type == 136) {
+      struct ndp_na *na = (struct ndp_na *) (pkt->icmp6 + 1);
+      if (MG_IP6MATCH(na->addr, c->rem.ip6)) break;
+    }
+#endif
+    if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport &&
+        (!c->loc.is_ip6 || pkt->ip6))
       break;
-    if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break;
     if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
-        lsn == (bool) c->is_listening &&
+        (!c->loc.is_ip6 || pkt->ip6) && lsn == (bool) c->is_listening &&
         (lsn || c->rem.port == pkt->tcp->sport))
       break;
   }
@@ -384,8 +492,7 @@ static void mac_resolved(struct mg_connection *c);
 static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
   if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
     // ARP request. Make a response, then send
-    // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op), mg_print_ip4,
-    //          &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa));
+    // MG_VERBOSE(("ARP req from %M", mg_print_ip4, &pkt->arp->spa));
     struct eth *eth = (struct eth *) ifp->tx.buf;
     struct arp *arp = (struct arp *) (eth + 1);
     memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
@@ -402,6 +509,7 @@ static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
     ether_output(ifp, PDIFF(eth, arp + 1));
   } else if (pkt->arp->op == mg_htons(2)) {
     if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return;
+    // MG_VERBOSE(("ARP resp from %M", mg_print_ip4, &pkt->arp->spa));
     if (pkt->arp->spa == ifp->gw) {
       // Got response for the GW ARP request. Set ifp->gwmac and IP -> READY
       memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac));
@@ -424,7 +532,6 @@ static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
 }
 
 static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
-  // MG_DEBUG(("ICMP %d", (int) len));
   if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) {
     size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct icmp);
     size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
@@ -434,7 +541,7 @@ static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
     ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip, pkt->ip->src,
                sizeof(*icmp) + plen);
     icmp = (struct icmp *) (ip + 1);
-    memset(icmp, 0, sizeof(*icmp));        // Set csum to 0
+    memset(icmp, 0, sizeof(*icmp));        // Set csum, type, code to 0
     memcpy(icmp + 1, pkt->pay.buf, plen);  // Copy RX payload to TX
     icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
     ether_output(ifp, hlen + plen);
@@ -540,18 +647,295 @@ static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
       ifp->gw = res.yiaddr;  // set gw IP, best-effort gwmac as DHCP server's
       memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
     }
-    tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
-           op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res));
+    tx_udp4(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
+            op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res));
   }
 }
 
+#if MG_ENABLE_IPV6
+static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                          uint8_t next, uint64_t *ip_src, uint64_t *ip_dst,
+                          size_t plen) {
+  // ifp->tx.buf is 8-bit aligned, keep other headers as pointers, see pkt
+  struct eth *eth = (struct eth *) ifp->tx.buf;
+  struct ip6 *ip6 = (struct ip6 *) (eth + 1);
+  memcpy(eth->dst, mac_dst, sizeof(eth->dst));
+  memcpy(eth->src, ifp->mac, sizeof(eth->src));  // Use our MAC
+  eth->type = mg_htons(0x86dd);
+  memset(ip6, 0, sizeof(*ip6));
+  ip6->ver = 0x60;  // Version 6, traffic class 0
+  ip6->plen = mg_htons((uint16_t) plen);
+  ip6->next = next;
+  ip6->hops = 255;  // NDP requires max
+  ip6->src[0] = *ip_src++;
+  ip6->src[1] = *ip_src;
+  ip6->dst[0] = *ip_dst++;
+  ip6->dst[1] = *ip_dst;
+  return ip6;
+}
+
+static void tx_icmp6(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                     uint64_t *ip_src, uint64_t *ip_dst, uint8_t type,
+                     uint8_t code, const void *buf, size_t len) {
+  struct ip6 *ip6;
+  struct icmp6 *icmp6;
+  uint32_t cs;
+  ip6 = tx_ip6(ifp, mac_dst, 58, ip_src, ip_dst, sizeof(*icmp6) + len);
+  icmp6 = (struct icmp6 *) (ip6 + 1);
+  memset(icmp6, 0, sizeof(*icmp6));  // Set csum to 0
+  icmp6->type = type;
+  icmp6->code = code;
+  memcpy(icmp6 + 1, buf, len);  // Copy payload
+  icmp6->csum = 0;              // RFC-4443 2.3, RFC-8200 8.1
+  cs = csumup(0, icmp6, sizeof(*icmp6));
+  cs = csumup(cs, buf, len);
+  cs = csumup(cs, ip_src, 16);
+  cs = csumup(cs, ip_dst, 16);
+  cs += (uint32_t) (58 + sizeof(*icmp6) + len);
+  icmp6->csum = csumfin(cs);
+  ether_output(ifp, sizeof(struct eth) + sizeof(*ip6) + sizeof(*icmp6) + len);
+}
+
+// Neighbor Discovery Protocol, RFC-4861
+// Neighbor Advertisement, 4.4
+static void tx_ndp_na(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                      uint64_t *ip_src, uint64_t *ip_dst, bool solicited,
+                      uint8_t *mac) {
+  uint8_t data[28];
+  memset(data, 0, sizeof(data));
+  data[0] = solicited ? 0x60 : 0x20;  // O + S
+  memcpy(data + 4, ip_src, 16);       // Target address
+  data[20] = 2, data[21] = 1;         // 4.6.1, target MAC
+  memcpy(data + 22, mac, 6);
+  tx_icmp6(ifp, mac_dst, ip_src, ip_dst, 136, 0, data, sizeof(data));
+}
+
+static void onstate6change(struct mg_tcpip_if *ifp);
+
+static void rx_ndp_na(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  struct ndp_na *na = (struct ndp_na *) (pkt->icmp6 + 1);
+  uint8_t *opts = (uint8_t *) (na + 1);
+  if ((na->res[0] & 0x40) == 0) return;  // not "solicited"
+  if (*opts++ != 2) return;              // no target hwaddr
+  if (*opts++ != 1) return;  // don't know what to do with this layer 2
+  MG_VERBOSE(("NDP NA resp from %M", mg_print_ip6, (char *) &na->addr));
+  if (MG_IP6MATCH(na->addr, ifp->gw6)) {
+    // Got response for the GW NS request. Set ifp->gw6mac and IP6 -> READY
+    memcpy(ifp->gw6mac, opts, sizeof(ifp->gw6mac));
+    if (ifp->state6 == MG_TCPIP_STATE_IP) {
+      ifp->state6 = MG_TCPIP_STATE_READY;
+      onstate6change(ifp);
+    }
+  } else {
+    struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
+    if (c != NULL && c->is_arplooking) {
+      struct connstate *s = (struct connstate *) (c + 1);
+      memcpy(s->mac, opts, sizeof(s->mac));
+      MG_DEBUG(("%lu NDP resolved %M -> %M", c->id, mg_print_ip6, c->rem.ip,
+                mg_print_mac, s->mac));
+      c->is_arplooking = 0;
+      mac_resolved(c);
+    }
+  }
+}
+
+// Neighbor Solicitation, 4.3
+static void rx_ndp_ns(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  uint64_t target[2];
+  if (pkt->pay.len < sizeof(target)) return;
+  memcpy(target, pkt->pay.buf + 4, sizeof(target));
+  if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6))
+    tx_ndp_na(ifp, (uint8_t *) pkt->pay.buf + 22, target, pkt->ip6->src, true,
+              ifp->mac);
+}
+
+static void tx_ndp_ns(struct mg_tcpip_if *ifp, uint64_t *ip_dst, uint8_t *mac) {
+  uint8_t payload[4 + 16 + 8];
+  uint64_t unspec_ip[2] = {0, 0};
+  size_t payload_len;
+  bool mcast_ns = true;
+  uint64_t mcast_ip[2] = {0, 0};
+  uint8_t mcast_mac[6];
+
+  memset(payload, 0, sizeof(payload));
+  memcpy(payload + 4, ip_dst, 16);
+  for (int i = 0; i < 6; i++) {
+    if (mac[i] != 0xff) {
+      mcast_ns = false;
+      break;
+    }
+  }
+  if (mcast_ns) {
+    ip6sn(ip_dst, mcast_ip);
+    ip6_mcastmac(mcast_mac, mcast_ip);
+  }
+  // TODO(robertc2000): using only link-local IP addr for now
+  // We might consider to add an option to use either link-local or global IP
+  if (!MG_IP6MATCH(ifp->ip6ll, unspec_ip)) {
+    payload[20] = payload[21] = 1;  // Type = 1; Length = 1
+    memcpy(payload + 22, ifp->mac, 6);
+    payload_len = sizeof(payload);
+  } else {
+    payload_len = sizeof(payload) - 8;
+  }
+  tx_icmp6(ifp, mcast_ns ? mcast_mac : mac, ifp->ip6ll,
+           mcast_ns ? mcast_ip : ip_dst, 135, 0, payload, payload_len);
+}
+
+// Router Solicitation, 4.1
+static void tx_ndp_rs(struct mg_tcpip_if *ifp, uint64_t *ip_dst, uint8_t *mac) {
+  // Note: currently, this function only sends multicast RS packets
+  (void) ip_dst;
+  (void) mac;
+  uint8_t payload[4 + 8];  // reserved + optional source MAC addr
+  size_t payload_len = 4;
+  uint64_t unspec_ip[2] = {0, 0};
+
+  memset(payload, 0, sizeof(payload));
+  if (!MG_IP6MATCH(ifp->ip6ll, unspec_ip)) {
+    payload[4] = payload[5] = 1;  // Type = 1; Length = 1
+    memcpy(payload + 6, ifp->mac, 6);
+    payload_len += 8;
+  }
+  tx_icmp6(ifp, (uint8_t *) ip6mac_allrouters, ifp->ip6ll,
+           (uint64_t *) ip6_allrouters.u, 133, 0, payload, payload_len);
+  MG_DEBUG(("NDP Router Solicitation sent"));
+}
+
+static bool fill_global(uint64_t *ip6, uint8_t *prefix, uint8_t prefix_len,
+                        uint8_t *mac) {
+  uint8_t full = prefix_len / 8;
+  uint8_t rem = prefix_len % 8;
+  if (full >= 8 && rem != 0) {
+    MG_ERROR(("Prefix length > 64, UNSUPPORTED"));
+    return false;
+  } else if (full == 8 && rem == 0) {
+    ip6gen((uint8_t *) ip6, prefix, mac);
+  } else {
+    ip6[0] = ip6[1] = 0;  // already zeroed before firing RS...
+    if (full) memcpy(ip6, prefix, full);
+    if (rem) {
+      uint8_t mask = (uint8_t) (0xFF << (8 - rem));
+      ((uint8_t *) ip6)[full] = prefix[full] & mask;
+    }
+    meui64(((uint8_t *) &ip6[1]), mac);  // RFC-4291 2.5.4, 2.5.1
+  }
+  return true;
+}
+
+// Router Advertisement, 4.2
+static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  if (pkt->pay.len < 12) return;
+  struct ndp_ra *ra = (struct ndp_ra *) (pkt->icmp6 + 1);
+  uint8_t *opts = (uint8_t *) (ra + 1);
+  size_t opt_left = pkt->pay.len - 12;
+
+  if (ifp->state6 == MG_TCPIP_STATE_UP) {
+    MG_DEBUG(("Received NDP RA"));
+    memcpy(ifp->gw6, (uint8_t *) pkt->ip6->src, 16);  // fill gw6 address
+    // parse options
+    while (opt_left >= 2) {
+      uint8_t type = opts[0], len = opts[1];
+      size_t length = (size_t) len * 8;
+      if (length == 0 || length > opt_left) break;  // malformed
+      if (type == 1 && length >= 8) {
+        // Received router's MAC address
+        ifp->state6 = MG_TCPIP_STATE_READY;
+        memcpy(ifp->gw6mac, opts + 2, 6);
+      } else if (type == 5 && length >= 8) {
+        // process MTU if available
+        uint32_t mtu = mg_ntohl(*(uint32_t *) (opts + 4));
+        MG_INFO(("got a nice MTU: %u, do you care ?", mtu));
+        // TODO(): **** This is an IPv6-given MTU, ifp->MTU is a LINK MTU, are
+        // we talkin'bout the same ? ***
+      } else if (type == 3 && length >= 32) {
+        // process prefix, 4.6.2
+        uint8_t prefix_len = opts[2];
+        uint8_t pfx_flags = opts[3];  // L=0x80, A=0x40
+        uint32_t valid = mg_ntohl(*(uint32_t *) (opts + 4));
+        uint32_t pref_lifetime = mg_ntohl(*(uint32_t *) (opts + 8));
+        uint8_t *prefix = opts + 16;
+
+        // TODO (robertc2000): handle prefix options if necessary
+        (void) prefix_len;
+        (void) pfx_flags;
+        (void) valid;
+        (void) pref_lifetime;
+        (void) prefix;
+
+        // fill prefix length and global
+        ifp->prefix_len = prefix_len;
+        if (!fill_global(ifp->ip6, prefix, prefix_len, ifp->mac)) return;
+      }
+      opts += length;
+      opt_left -= length;
+    }
+
+    if (ifp->state6 != MG_TCPIP_STATE_READY) {
+      tx_ndp_ns(ifp, ifp->gw6, ifp->gw6mac);  // unsolicited GW MAC resolution
+      ifp->state6 = MG_TCPIP_STATE_IP;
+    }
+    onstate6change(ifp);
+  }
+}
+
+static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
+  switch (pkt->icmp6->type) {
+    case 128: {  // Echo Request, RFC-4443 4.1
+      uint64_t target[2];
+      memcpy(target, pkt->ip6->dst, sizeof(target));
+      if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6)) {
+        size_t hlen =
+            sizeof(struct eth) + sizeof(struct ip6) + sizeof(struct icmp6);
+        size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
+        if (plen > space) plen = space;  // Copy (truncated) RX payload to TX
+        // Echo Reply, 4.2
+        tx_icmp6(ifp, pkt->eth->src, pkt->ip6->dst, pkt->ip6->src, 129, 0,
+                 pkt->pay.buf, plen);
+      }
+    } break;
+    case 134:  // Router Advertisement
+      rx_ndp_ra(ifp, pkt);
+      break;
+    case 135:  // Neighbor Solicitation
+      rx_ndp_ns(ifp, pkt);
+      break;
+    case 136:  // Neighbor Advertisement
+      rx_ndp_na(ifp, pkt);
+      break;
+  }
+}
+
+static void onstate6change(struct mg_tcpip_if *ifp) {
+  if (ifp->state6 == MG_TCPIP_STATE_READY) {
+    MG_INFO(("READY, IP: %M", mg_print_ip6, &ifp->ip6));
+    MG_INFO(("       GW: %M", mg_print_ip6, &ifp->gw6));
+    MG_INFO(("      MAC: %M", mg_print_mac, &ifp->mac));
+  } else if (ifp->state6 == MG_TCPIP_STATE_IP) {
+    tx_ndp_ns(ifp, ifp->gw6, ifp->gw6mac);  // unsolicited GW MAC resolution
+  } else if (ifp->state6 == MG_TCPIP_STATE_UP) {
+    MG_INFO(("IP: %M", mg_print_ip6, &ifp->ip6ll));
+  }
+  if (ifp->state6 != MG_TCPIP_STATE_UP && ifp->state6 != MG_TCPIP_STATE_DOWN)
+    mg_tcpip_call(ifp, MG_TCPIP_EV_ST6_CHG, &ifp->state6);
+}
+#endif
+
 static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
   struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
   struct connstate *s;
   if (c == NULL) return false;  // No UDP listener on this port
   s = (struct connstate *) (c + 1);
   c->rem.port = pkt->udp->sport;
-  memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
+#if MG_ENABLE_IPV6
+  if (c->loc.is_ip6) {
+    c->rem.ip6[0] = pkt->ip6->src[0], c->rem.ip6[1] = pkt->ip6->src[1],
+    c->rem.is_ip6 = true;
+  } else
+#endif
+  {
+    c->rem.ip4 = pkt->ip->src;
+  }
   memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
   if (c->recv.len >= MG_MAX_RECV_SIZE) {
     mg_error(c, "max_recv_buf_size reached");
@@ -566,24 +950,41 @@ static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
   return true;
 }
 
-static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip,
-                     uint8_t flags, uint16_t sport, uint16_t dport,
-                     uint32_t seq, uint32_t ack, const void *buf, size_t len) {
-  struct ip *ip;
+static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
+                     struct mg_addr *ip_src, struct mg_addr *ip_dst,
+                     uint8_t flags, uint32_t seq, uint32_t ack, const void *buf,
+                     size_t len) {
+  struct ip *ip = NULL;
   struct tcp *tcp;
-  uint16_t opts[4 / 2];
-  if (flags & TH_SYN) {                              // Send MSS, RFC-9293 3.7.1
-    opts[0] = mg_htons(0x0204);                      // RFC-9293 3.2
-    opts[1] = mg_htons((uint16_t) (ifp->mtu - 40));  // RFC-6691
+  uint16_t opts[4 / 2], mss;
+#if MG_ENABLE_IPV6
+  struct ip6 *ip6 = NULL;
+  mss = (uint16_t) (ifp->mtu - 60);  // RFC-9293 3.7.1; RFC-6691 2
+#else
+  mss = (uint16_t) (ifp->mtu - 40);  // RFC-9293 3.7.1; RFC-6691 2
+#endif
+  if (flags & TH_SYN) {          // Send MSS
+    opts[0] = mg_htons(0x0204);  // RFC-9293 3.2
+    opts[1] = mg_htons(mss);
     buf = opts;
     len = sizeof(opts);
   }
-  ip = tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len);
-  tcp = (struct tcp *) (ip + 1);
+#if MG_ENABLE_IPV6
+  if (ip_dst->is_ip6) {
+    ip6 = tx_ip6(ifp, mac_dst, 6, ip_src->ip6, ip_dst->ip6,
+                 sizeof(struct tcp) + len);
+    tcp = (struct tcp *) (ip6 + 1);
+  } else
+#endif
+  {
+    ip = tx_ip(ifp, mac_dst, 6, ip_src->ip4, ip_dst->ip4,
+               sizeof(struct tcp) + len);
+    tcp = (struct tcp *) (ip + 1);
+  }
   memset(tcp, 0, sizeof(*tcp));
   if (buf != NULL && len) memmove(tcp + 1, buf, len);
-  tcp->sport = sport;
-  tcp->dport = dport;
+  tcp->sport = ip_src->port;
+  tcp->dport = ip_dst->port;
   tcp->seq = seq;
   tcp->ack = ack;
   tcp->flags = flags;
@@ -593,16 +994,29 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip,
   {
     uint32_t cs = 0;
     uint16_t n = (uint16_t) (sizeof(*tcp) + len);
-    uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)};
     cs = csumup(cs, tcp, n);
-    cs = csumup(cs, &ip->src, sizeof(ip->src));
-    cs = csumup(cs, &ip->dst, sizeof(ip->dst));
-    cs = csumup(cs, pseudo, sizeof(pseudo));
+#if MG_ENABLE_IPV6
+    if (ip_dst->is_ip6) {
+      cs = csumup(cs, &ip6->src, sizeof(ip6->src));
+      cs = csumup(cs, &ip6->dst, sizeof(ip6->dst));
+      cs += (uint32_t) (6 + n);
+      MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip6, &ip6->src,
+                  mg_ntohs(tcp->sport), mg_print_ip6, &ip6->dst,
+                  mg_ntohs(tcp->dport), tcp->flags, len));
+    } else
+#endif
+    {
+      uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8),
+                          (uint8_t) (n & 255)};
+      cs = csumup(cs, &ip->src, sizeof(ip->src));
+      cs = csumup(cs, &ip->dst, sizeof(ip->dst));
+      cs = csumup(cs, pseudo, sizeof(pseudo));
+      MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src,
+                  mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst,
+                  mg_ntohs(tcp->dport), tcp->flags, len));
+    }
     tcp->csum = csumfin(cs);
   }
-  MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src,
-              mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst,
-              mg_ntohs(tcp->dport), tcp->flags, len));
   return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len);
 }
 
@@ -610,8 +1024,21 @@ static size_t tx_tcp_ctrlresp(struct mg_tcpip_if *ifp, struct pkt *pkt,
                               uint8_t flags, uint32_t seqno) {
   uint32_t ackno = mg_htonl(mg_ntohl(pkt->tcp->seq) + (uint32_t) pkt->pay.len +
                             ((pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0));
-  return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags, pkt->tcp->dport,
-                pkt->tcp->sport, seqno, ackno, NULL, 0);
+  struct mg_addr ips, ipd;
+  memset(&ips, 0, sizeof(ips));
+  memset(&ipd, 0, sizeof(ipd));
+  if (pkt->ip != NULL) {
+    ips.ip4 = pkt->ip->dst;
+    ipd.ip4 = pkt->ip->src;
+  } else {
+    ips.ip6[0] = pkt->ip6->dst[0], ips.ip6[1] = pkt->ip6->dst[1];
+    ipd.ip6[0] = pkt->ip6->src[0], ipd.ip6[1] = pkt->ip6->src[1];
+    ips.is_ip6 = true;
+    ipd.is_ip6 = true;
+  }
+  ips.port = pkt->tcp->dport;
+  ipd.port = pkt->tcp->sport;
+  return tx_tcp(ifp, pkt->eth->src, &ips, &ipd, flags, seqno, ackno, NULL, 0);
 }
 
 static size_t tx_tcp_rst(struct mg_tcpip_if *ifp, struct pkt *pkt, bool toack) {
@@ -632,7 +1059,15 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn,
   s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
   memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
   settmout(c, MIP_TTYPE_KEEPALIVE);
-  memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
+#if MG_ENABLE_IPV6
+  if (lsn->loc.is_ip6) {
+    c->rem.ip6[0] = pkt->ip6->src[0], c->rem.ip6[1] = pkt->ip6->src[1],
+    c->rem.is_ip6 = true;
+  } else
+#endif
+  {
+    c->rem.ip4 = pkt->ip->src;
+  }
   c->rem.port = pkt->tcp->sport;
   MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem));
   LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
@@ -652,10 +1087,13 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn,
 
 static size_t trim_len(struct mg_connection *c, size_t len) {
   struct mg_tcpip_if *ifp = c->mgr->ifp;
-  size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60, udp_h_len = 8;
+  size_t eth_h_len = 14, tcp_max_h_len = 60, udp_h_len = 8;
+  size_t ip_max_h_len = c->rem.is_ip6 ? 40 : 24;  // we don't send options
   size_t max_headers_len =
       eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len);
-  size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len - eth_h_len;
+  size_t min_mtu = c->rem.is_ip6 ? 1280
+                   : c->is_udp   ? 68 /* RFC-791 */
+                                 : max_headers_len - eth_h_len;
 
   // If the frame exceeds the available buffer, trim the length
   if (len + max_headers_len > ifp->tx.len) {
@@ -680,17 +1118,14 @@ static size_t trim_len(struct mg_connection *c, size_t len) {
 long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
   struct mg_tcpip_if *ifp = c->mgr->ifp;
   struct connstate *s = (struct connstate *) (c + 1);
-  uint32_t dst_ip = c->rem.ip4;
   len = trim_len(c, len);
   if (c->is_udp) {
-    if (!tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port, buf,
-                len))
-      return MG_IO_WAIT;
+    if (!tx_udp(ifp, s->mac, &c->loc, &c->rem, buf, len)) return MG_IO_WAIT;
   } else {  // TCP, cap to peer's MSS
     size_t sent;
     if (len > s->dmss) len = s->dmss;  // RFC-6691: reduce if sending opts
-    sent = tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port,
-                  c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), buf, len);
+    sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_PUSH | TH_ACK,
+                  mg_htonl(s->seq), mg_htonl(s->ack), buf, len);
     if (sent == 0) {
       return MG_IO_WAIT;
     } else if (sent == (size_t) -1) {
@@ -727,13 +1162,12 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
   struct connstate *s = (struct connstate *) (c + 1);
   struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv;
   uint32_t seq = mg_ntohl(pkt->tcp->seq);
-  uint32_t rem_ip = c->rem.ip4;
   if (pkt->tcp->flags & TH_FIN) {
     uint8_t flags = TH_ACK;
     if (mg_ntohl(pkt->tcp->seq) != s->ack) {
       MG_VERBOSE(("ignoring FIN, %x != %x", mg_ntohl(pkt->tcp->seq), s->ack));
-      tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-             mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
+      tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
+             mg_htonl(s->ack), "", 0);
       return;
     }
     // If we initiated the closure, we reply with ACK upon receiving FIN
@@ -753,14 +1187,14 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
       c->is_draining = 1;
       settmout(c, MIP_TTYPE_FIN);
     }
-    tx_tcp(c->mgr->ifp, s->mac, rem_ip, flags, c->loc.port, c->rem.port,
-           mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
+    tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, flags, mg_htonl(s->seq),
+           mg_htonl(s->ack), "", 0);
     if (pkt->pay.len == 0) return;  // if no data, we're done
   } else if (pkt->pay.len <= 1 && mg_ntohl(pkt->tcp->seq) == s->ack - 1) {
     // Keep-Alive (RFC-9293 3.8.4, allow erroneous implementations)
     MG_VERBOSE(("%lu keepalive ACK", c->id));
-    tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-           mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
+    tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
+           mg_htonl(s->ack), NULL, 0);
     return;                        // no data to process
   } else if (pkt->pay.len == 0) {  // this is an ACK
     if (s->fin_rcvd && s->ttype == MIP_TTYPE_FIN) s->twclosure = true;
@@ -771,8 +1205,8 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
       MG_VERBOSE(("ignoring duplicate pkt"));
     } else {
       MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack));
-      tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-             mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
+      tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
+             mg_htonl(s->ack), "", 0);
     }
     return;  // drop it
   } else if (io->size - io->len < pkt->pay.len &&
@@ -795,8 +1229,8 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
   if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) {
     // Send ACK immediately
     MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked));
-    tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-           mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
+    tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
+           mg_htonl(s->ack), NULL, 0);
     s->unacked = 0;
     s->acked = s->ack;
     if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE);
@@ -863,11 +1297,11 @@ static void backlog_poll(struct mg_mgr *mgr) {
 }
 
 // process options (MSS)
-static void handle_opt(struct connstate *s, struct tcp *tcp) {
+static void handle_opt(struct connstate *s, struct tcp *tcp, bool ip6) {
   uint8_t *opts = (uint8_t *) (tcp + 1);
   int len = 4 * ((int) (tcp->off >> 4) - ((int) sizeof(*tcp) / 4));
-  s->dmss = 536;     // assume default, RFC-9293 3.7.1
-  while (len > 0) {  // RFC-9293 3.1 3.2
+  s->dmss = ip6 ? 1220 : 536;  // assume default, RFC-9293 3.7.1
+  while (len > 0) {            // RFC-9293 3.1 3.2
     uint8_t kind = opts[0], optlen = 1;
     if (kind != 1) {         // No-Operation
       if (kind == 0) break;  // End of Option List
@@ -888,7 +1322,7 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
   // - check clients (Group 1) and established connections (Group 3)
   if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN | TH_ACK)) {
     // client got a server connection accept
-    handle_opt(s, pkt->tcp);  // process options (MSS)
+    handle_opt(s, pkt->tcp, pkt->ip6 != NULL);  // process options (MSS)
     s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1;
     tx_tcp_ctrlresp(ifp, pkt, TH_ACK, pkt->tcp->ack);
     c->is_connecting = 0;  // Client connected
@@ -922,7 +1356,7 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
       int key;
       uint32_t isn;
       if (pkt->tcp->sport != 0) {
-        handle_opt(&cs, pkt->tcp);  // process options (MSS)
+        handle_opt(&cs, pkt->tcp, pkt->ip6 != NULL);  // process options (MSS)
         key = backlog_insert(c, pkt->tcp->sport,
                              cs.dmss);  // backlog options (MSS)
         if (key < 0) return;  // no room in backlog, discard SYN, client retries
@@ -960,13 +1394,13 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
   if (pkt->pay.len < sizeof(*pkt->ip)) return;  // Truncated
   if ((pkt->ip->ver >> 4) != 4) return;         // Not IP
   ihl = pkt->ip->ver & 0x0F;
-  if (ihl < 5) return;                     // bad IHL
-  if (pkt->pay.len < (uint16_t)(ihl * 4)) return;    // Truncated / malformed
+  if (ihl < 5) return;                              // bad IHL
+  if (pkt->pay.len < (uint16_t) (ihl * 4)) return;  // Truncated / malformed
   // There can be link padding, take length from IP header
-  len = mg_ntohs(pkt->ip->len); // IP datagram length
-  if (len < (ihl * 4) || len > pkt->pay.len) return; // malformed
-  pkt->pay.len = len; // strip padding
-  mkpay(pkt, (uint32_t *) pkt->ip + ihl);  // account for opts
+  len = mg_ntohs(pkt->ip->len);                       // IP datagram length
+  if (len < (uint16_t) (ihl * 4) || len > pkt->pay.len) return;  // malformed
+  pkt->pay.len = len;                                 // strip padding
+  mkpay(pkt, (uint32_t *) pkt->ip + ihl);             // account for opts
   frag = mg_ntohs(pkt->ip->frag);
   if (frag & IP_MORE_FRAGS_MSK || frag & IP_FRAG_OFFSET_MSK) {
     struct mg_connection *c;
@@ -981,11 +1415,11 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
     rx_icmp(ifp, pkt);
   } else if (pkt->ip->proto == 17) {
     pkt->udp = (struct udp *) (pkt->pay.buf);
-    if (pkt->pay.len < sizeof(*pkt->udp)) return; // truncated
+    if (pkt->pay.len < sizeof(*pkt->udp)) return;  // truncated
     // Take length from UDP header
-    len = mg_ntohs(pkt->udp->len); // UDP datagram length
-    if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return; // malformed
-    pkt->pay.len = len; // strip excess data
+    len = mg_ntohs(pkt->udp->len);  // UDP datagram length
+    if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return;  // malformed
+    pkt->pay.len = len;  // strip excess data
     mkpay(pkt, pkt->udp + 1);
     MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
                 mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst,
@@ -999,14 +1433,15 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
       mkpay(pkt, pkt->dhcp + 1);
       rx_dhcp_server(ifp, pkt);
     } else if (!rx_udp(ifp, pkt)) {
-      // Should send ICMP Destination Unreachable for unicasts, but keep silent
+      // Should send ICMP Destination Unreachable for unicasts, but keep
+      // silent
     }
   } else if (pkt->ip->proto == 6) {
     uint8_t off;
     pkt->tcp = (struct tcp *) (pkt->pay.buf);
     if (pkt->pay.len < sizeof(*pkt->tcp)) return;
     off = pkt->tcp->off >> 4;  // account for opts
-    if (pkt->pay.len < (uint16_t)(4 * off)) return;
+    if (pkt->pay.len < (uint16_t) (4 * off)) return;
     mkpay(pkt, (uint32_t *) pkt->tcp + off);
     MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
                 mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst,
@@ -1019,12 +1454,15 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
   }
 }
 
+#if MG_ENABLE_IPV6
 static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
-  uint16_t len = 0;
+  uint16_t len = 0, plen;
   uint8_t next, *nhdr;
   bool loop = true;
   if (pkt->pay.len < sizeof(*pkt->ip6)) return;  // Truncated
   if ((pkt->ip6->ver >> 4) != 0x6) return;       // Not IPv6
+  plen = mg_ntohs(pkt->ip6->plen);
+  if (plen > (pkt->pay.len - sizeof(*pkt->ip6))) return;  // malformed
   next = pkt->ip6->next;
   nhdr = (uint8_t *) (pkt->ip6 + 1);
   while (loop) {
@@ -1035,7 +1473,7 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
       case 51:  // Authentication RFC-4302
         MG_INFO(("IPv6 extension header %d", (int) next));
         next = nhdr[0];
-        len += (uint16_t)(8 * (nhdr[1] + 1));
+        len += (uint16_t) (8 * (nhdr[1] + 1));
         nhdr += 8 * (nhdr[1] + 1);
         break;
       case 44:  // Fragment 4.5
@@ -1055,31 +1493,38 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
         break;
     }
   }
+  if (len >= plen) return;
   // There can be link padding, take payload length from IPv6 header - options
   pkt->pay.buf = (char *) nhdr;
-  pkt->pay.len = mg_ntohs(pkt->ip6->plen) - len;
+  pkt->pay.len = plen - len;
   if (next == 58) {
     pkt->icmp6 = (struct icmp6 *) (pkt->pay.buf);
     if (pkt->pay.len < sizeof(*pkt->icmp6)) return;
     mkpay(pkt, pkt->icmp6 + 1);
     MG_DEBUG(("ICMPv6 %M -> %M len %u", mg_print_ip6, &pkt->ip6->src,
               mg_print_ip6, &pkt->ip6->dst, (int) pkt->pay.len));
-    // rx_icmp6(ifp, pkt);
+    rx_icmp6(ifp, pkt);
   } else if (next == 17) {
     pkt->udp = (struct udp *) (pkt->pay.buf);
     if (pkt->pay.len < sizeof(*pkt->udp)) return;
+    // Take length from UDP header
+    len = mg_ntohs(pkt->udp->len);  // UDP datagram length
+    if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return;  // malformed
+    pkt->pay.len = len;  // strip excess data
     mkpay(pkt, pkt->udp + 1);
     MG_DEBUG(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src,
               mg_ntohs(pkt->udp->sport), mg_print_ip6, &pkt->ip6->dst,
               mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
-    if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(546)) {
+    if (ifp->enable_dhcp6_client && pkt->udp->dport == mg_htons(546)) {
       pkt->dhcp6 = (struct dhcp6 *) (pkt->udp + 1);
       mkpay(pkt, pkt->dhcp6 + 1);
       // rx_dhcp6_client(ifp, pkt);
+#if 0
     } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(547)) {
       pkt->dhcp6 = (struct dhcp6 *) (pkt->udp + 1);
       mkpay(pkt, pkt->dhcp6 + 1);
-      // rx_dhcp6_server(ifp, pkt);
+      rx_dhcp6_server(ifp, pkt);
+#endif
     } else if (!rx_udp(ifp, pkt)) {
       // Should send ICMPv6 Destination Unreachable for unicasts, keep silent
     }
@@ -1088,7 +1533,7 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
     pkt->tcp = (struct tcp *) (pkt->pay.buf);
     if (pkt->pay.len < sizeof(*pkt->tcp)) return;
     off = pkt->tcp->off >> 4;  // account for opts
-    if (pkt->pay.len < sizeof(*pkt->tcp) + 4 * off) return;
+    if (pkt->pay.len < (uint16_t) (4 * off)) return;
     mkpay(pkt, (uint32_t *) pkt->tcp + off);
     MG_DEBUG(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src,
               mg_ntohs(pkt->tcp->sport), mg_print_ip6, &pkt->ip6->dst,
@@ -1100,13 +1545,16 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
       mg_hexdump(pkt->ip6, pkt->pay.len >= 32 ? 32 : pkt->pay.len);
   }
 }
+#else
+#define rx_ip6(x, y)
+#endif
 
 static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
   struct pkt pkt;
   memset(&pkt, 0, sizeof(pkt));
   pkt.pay.buf = pkt.raw.buf = (char *) buf;
-  pkt.pay.len = pkt.raw.len = len; // payload = raw
-  pkt.eth = (struct eth *) buf;   // Ethernet = raw
+  pkt.pay.len = pkt.raw.len = len;             // payload = raw
+  pkt.eth = (struct eth *) buf;                // Ethernet = raw
   if (pkt.raw.len < sizeof(*pkt.eth)) return;  // Truncated - runt?
   if (ifp->enable_mac_check &&
       memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
@@ -1122,7 +1570,7 @@ static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
   mkpay(&pkt, pkt.eth + 1);
   if (pkt.eth->type == mg_htons(0x806)) {
     pkt.arp = (struct arp *) (pkt.pay.buf);
-    if (pkt.pay.len < sizeof(*pkt.arp)) return; // Truncated
+    if (pkt.pay.len < sizeof(*pkt.arp)) return;  // Truncated
     mg_tcpip_call(ifp, MG_TCPIP_EV_ARP, &pkt.raw);
     rx_arp(ifp, &pkt);
   } else if (pkt.eth->type == mg_htons(0x86dd)) {
@@ -1137,6 +1585,65 @@ static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
   }
 }
 
+static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) {
+  if (ifp->state == MG_TCPIP_STATE_DOWN) return;
+  // DHCP RFC-2131 (4.4)
+  if (ifp->enable_dhcp_client && s1) {
+    if (ifp->state == MG_TCPIP_STATE_UP) {
+      tx_dhcp_discover(ifp);  // INIT (4.4.1)
+    } else if (ifp->state == MG_TCPIP_STATE_READY &&
+               ifp->lease_expire > 0) {  // BOUND / RENEWING / REBINDING
+      if (ifp->now >= ifp->lease_expire) {
+        ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;  // expired, release IP
+        onstatechange(ifp);
+      } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
+                 ((ifp->now / 1000) % 60) == 0) {
+        // hack: 30 min before deadline, try to rebind (4.3.6) every min
+        tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff);
+      }  // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
+    }
+  }
+}
+static void mg_ip_link(struct mg_tcpip_if *ifp, bool up) {
+  bool current = ifp->state != MG_TCPIP_STATE_DOWN;
+  if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
+  if (up != current) {  // link state has changed
+    ifp->state = up == false                               ? MG_TCPIP_STATE_DOWN
+                 : ifp->enable_dhcp_client || ifp->ip == 0 ? MG_TCPIP_STATE_UP
+                                                           : MG_TCPIP_STATE_IP;
+    onstatechange(ifp);
+  } else if (!ifp->enable_dhcp_client && ifp->state == MG_TCPIP_STATE_UP &&
+             ifp->ip) {
+    ifp->state = MG_TCPIP_STATE_IP;  // ifp->fn has set an IP
+    onstatechange(ifp);
+  }
+}
+
+#if MG_ENABLE_IPV6
+static void mg_ip6_poll(struct mg_tcpip_if *ifp, bool s1) {
+  if (ifp->state6 == MG_TCPIP_STATE_DOWN) return;
+  if (ifp->enable_slaac && s1 && ifp->state6 == MG_TCPIP_STATE_UP)
+    tx_ndp_rs(ifp, ifp->gw6, ifp->gw6mac);
+}
+static void mg_ip6_link(struct mg_tcpip_if *ifp, bool up) {
+  bool current = ifp->state6 != MG_TCPIP_STATE_DOWN;
+  if (!up && ifp->enable_slaac) ifp->ip6[0] = ifp->ip6[1] = 0;
+  if (up != current) {  // link state has changed
+    ifp->state6 = !up                                     ? MG_TCPIP_STATE_DOWN
+                  : ifp->enable_slaac || ifp->ip6[0] == 0 ? MG_TCPIP_STATE_UP
+                                                          : MG_TCPIP_STATE_IP;
+    onstate6change(ifp);
+  } else if (!ifp->enable_slaac && ifp->state6 == MG_TCPIP_STATE_UP &&
+             ifp->ip6[0]) {
+    ifp->state6 = MG_TCPIP_STATE_IP;  // ifp->fn has set an IP
+    onstate6change(ifp);
+  }
+}
+#else
+#define mg_ip6_poll(x, y)
+#define mg_ip6_link(x, y)
+#endif
+
 static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
   struct mg_connection *c;
   bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, now);
@@ -1145,9 +1652,16 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
   if (expired_1000ms) {
 #if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
     const char *names[] = {"down", "up", "req", "ip", "ready"};
-    MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u",
-             names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent,
-             ifp->ndrop, ifp->nerr));
+    size_t max = sizeof(names) / sizeof(char *);
+    unsigned int state = ifp->state >= max ? max - 1 : ifp->state;
+    MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u", names[state],
+             mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent, ifp->ndrop,
+             ifp->nerr));
+#if MG_ENABLE_IPV6
+    state = ifp->state6 >= max ? max - 1 : ifp->state6;
+    if (state > MG_TCPIP_STATE_UP)
+      MG_INFO(("Status: %s, IPv6: %M", names[state], mg_print_ip6, &ifp->ip6));
+#endif
 #endif
     backlog_poll(ifp->mgr);
   }
@@ -1156,47 +1670,30 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
     ifp->state = MG_TCPIP_STATE_READY;  // keep best-effort MAC
     onstatechange(ifp);
   }
+#if MG_ENABLE_IPV6
+  // Handle gw NS/NA req/resp timeout, order is important
+  if (expired_1000ms && ifp->state6 == MG_TCPIP_STATE_IP) {
+    ifp->state6 = MG_TCPIP_STATE_READY;  // keep best-effort MAC
+    onstate6change(ifp);
+  }
+#endif
+
   // poll driver
   if (ifp->driver->poll) {
     bool up = ifp->driver->poll(ifp, expired_1000ms);
-    // Handle physical interface up/down status
+    // Handle physical interface up/down status, ifp->state rules over state6
     if (expired_1000ms) {
-      bool current = ifp->state != MG_TCPIP_STATE_DOWN;
-      if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
-      if (up != current) {  // link state has changed
-        ifp->state = up == false ? MG_TCPIP_STATE_DOWN
-                     : ifp->enable_dhcp_client || ifp->ip == 0
-                         ? MG_TCPIP_STATE_UP
-                         : MG_TCPIP_STATE_IP;
-        onstatechange(ifp);
-      } else if (!ifp->enable_dhcp_client && ifp->state == MG_TCPIP_STATE_UP &&
-                 ifp->ip) {
-        ifp->state = MG_TCPIP_STATE_IP;  // ifp->fn has set an IP
-        onstatechange(ifp);
-      }
+      mg_ip_link(ifp, up);   // Handle IPv4
+      mg_ip6_link(ifp, up);  // Handle IPv6
       if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down"));
       mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL);
     }
   }
-  if (ifp->state == MG_TCPIP_STATE_DOWN) return;
 
-  // DHCP RFC-2131 (4.4)
-  if (ifp->enable_dhcp_client && expired_1000ms) {
-    if (ifp->state == MG_TCPIP_STATE_UP) {
-      tx_dhcp_discover(ifp);  // INIT (4.4.1)
-    } else if (ifp->state == MG_TCPIP_STATE_READY &&
-               ifp->lease_expire > 0) {  // BOUND / RENEWING / REBINDING
-      if (ifp->now >= ifp->lease_expire) {
-        ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;  // expired, release IP
-        onstatechange(ifp);
-      } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
-                 ((ifp->now / 1000) % 60) == 0) {
-        // hack: 30 min before deadline, try to rebind (4.3.6) every min
-        tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff);
-      }  // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
-    }
-  }
+  mg_ip_poll(ifp, expired_1000ms);   // Handle IPv4
+  mg_ip6_poll(ifp, expired_1000ms);  // Handle IPv6
 
+  if (ifp->state == MG_TCPIP_STATE_DOWN) return;
   // Read data from the network
   if (ifp->driver->rx != NULL) {  // Simple polling driver, returns one frame
     size_t len =
@@ -1217,10 +1714,8 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
   // Process timeouts
   for (c = ifp->mgr->conns; c != NULL; c = c->next) {
     struct connstate *s = (struct connstate *) (c + 1);
-    uint32_t rem_ip;
     if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving)
       continue;
-    rem_ip = c->rem.ip4;
     if (ifp->now > s->timer) {
       if (s->ttype == MIP_TTYPE_ARP) {
         mg_error(c, "ARP timeout");
@@ -1228,8 +1723,8 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
         continue;
       } else if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) {
         MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack));
-        tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-               mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
+        tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq),
+               mg_htonl(s->ack), NULL, 0);
         s->acked = s->ack;
       } else if (s->ttype == MIP_TTYPE_SYN) {
         mg_error(c, "Connection timeout");
@@ -1241,8 +1736,8 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
           mg_error(c, "keepalive");
         } else {
           MG_VERBOSE(("%lu keepalive", c->id));
-          tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-                 mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0);
+          tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq - 1),
+                 mg_htonl(s->ack), NULL, 0);
         }
       }
 
@@ -1299,6 +1794,15 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
     ifp->eport |= MG_EPHEMERAL_PORT_BASE;         // Random from
                                            // MG_EPHEMERAL_PORT_BASE to 65535
     if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM"));
+#if MG_ENABLE_IPV6
+    // If static configuration is used, link-local and global addresses,
+    // prefix length, and gw are already filled at this point.
+    if (ifp->ip6[0] == 0 && ifp->ip6[1] == 0) {
+      ifp->enable_slaac = true;
+      ip6genll((uint8_t *) ifp->ip6ll, ifp->mac);  // build link-local address
+    }
+    memset(ifp->gw6mac, 255, sizeof(ifp->gw6mac));  // Set best-effort to bcast
+#endif
   }
 }
 
@@ -1311,9 +1815,7 @@ void mg_tcpip_free(struct mg_tcpip_if *ifp) {
 static void send_syn(struct mg_connection *c) {
   struct connstate *s = (struct connstate *) (c + 1);
   uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
-  uint32_t rem_ip = c->rem.ip4;
-  tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port, isn, 0,
-         NULL, 0);
+  tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_SYN, isn, 0, NULL, 0);
 }
 
 static void mac_resolved(struct mg_connection *c) {
@@ -1335,34 +1837,72 @@ static void ip4_mcastmac(uint8_t *mac, uint32_t *ip) {
 
 void mg_connect_resolved(struct mg_connection *c) {
   struct mg_tcpip_if *ifp = c->mgr->ifp;
-  uint32_t rem_ip = c->rem.ip4;
   c->is_resolving = 0;
   if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE;
-  c->loc.ip4 = ifp->ip;
   c->loc.port = mg_htons(ifp->eport++);
+#if MG_ENABLE_IPV6
+  if (c->rem.is_ip6) {
+    c->loc.ip6[0] = ifp->ip6[0], c->loc.ip6[1] = ifp->ip6[1],
+    c->loc.is_ip6 = true;
+  } else
+#endif
+  {
+    c->loc.ip4 = ifp->ip;
+  }
   MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port,
             &c->rem));
   mg_call(c, MG_EV_RESOLVE, NULL);
   c->is_connecting = 1;
-  if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) {
-    struct connstate *s = (struct connstate *) (c + 1);
-    memset(s->mac, 0xFF, sizeof(s->mac));  // global or local broadcast
-    mac_resolved(c);
-  } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask)) &&
-             rem_ip != ifp->gw) {  // skip if gw (onstatechange -> READY -> ARP)
-    // If we're in the same LAN, fire an ARP lookup.
-    MG_DEBUG(("%lu ARP lookup...", c->id));
-    mg_tcpip_arp_request(ifp, rem_ip, NULL);
-    settmout(c, MIP_TTYPE_ARP);
-    c->is_arplooking = 1;
-  } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
-    struct connstate *s = (struct connstate *) (c + 1);  // 224 to 239, E0 to EF
-    ip4_mcastmac(s->mac, &rem_ip);                       // multicast group
-    mac_resolved(c);
-  } else {
-    struct connstate *s = (struct connstate *) (c + 1);
-    memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac));
-    mac_resolved(c);
+#if MG_ENABLE_IPV6
+  if (c->rem.is_ip6) {
+    if (c->is_udp &&
+        MG_IP6MATCH(c->rem.ip6, ip6_allnodes.u)) {  // local broadcast
+      struct connstate *s = (struct connstate *) (c + 1);
+      memcpy(s->mac, ip6mac_allnodes, sizeof(s->mac));
+      mac_resolved(c);
+    } else if (c->rem.ip6[0] == ifp->ip6[0] &&
+               !MG_IP6MATCH(c->rem.ip6,
+                            ifp->gw6)) {  // skip if gw (onstate6change -> NS)
+      // If we're in the same LAN, fire a Neighbor Solicitation
+      MG_DEBUG(("%lu NS lookup...", c->id));
+      tx_ndp_ns(ifp, c->rem.ip6, ifp->mac);
+      settmout(c, MIP_TTYPE_ARP);
+      c->is_arplooking = 1;
+    } else if (*((uint8_t *) c->rem.ip6) == 0xFF) {  // multicast
+      struct connstate *s = (struct connstate *) (c + 1);
+      ip6_mcastmac(s->mac, c->rem.ip6);
+      mac_resolved(c);
+    } else {
+      struct connstate *s = (struct connstate *) (c + 1);
+      memcpy(s->mac, ifp->gw6mac, sizeof(s->mac));
+      mac_resolved(c);
+    }
+  } else
+#endif
+  {
+    uint32_t rem_ip = c->rem.ip4;
+    if (c->is_udp &&
+        (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) {
+      struct connstate *s = (struct connstate *) (c + 1);
+      memset(s->mac, 0xFF, sizeof(s->mac));  // global or local broadcast
+      mac_resolved(c);
+    } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask)) &&
+               rem_ip != ifp->gw) {  // skip if gw (onstatechange -> ARP)
+      // If we're in the same LAN, fire an ARP lookup.
+      MG_DEBUG(("%lu ARP lookup...", c->id));
+      mg_tcpip_arp_request(ifp, rem_ip, NULL);
+      settmout(c, MIP_TTYPE_ARP);
+      c->is_arplooking = 1;
+    } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
+      struct connstate *s =
+          (struct connstate *) (c + 1);  // 224 to 239, E0 to EF
+      ip4_mcastmac(s->mac, &rem_ip);     // multicast group
+      mac_resolved(c);
+    } else {
+      struct connstate *s = (struct connstate *) (c + 1);
+      memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac));
+      mac_resolved(c);
+    }
   }
 }
 
@@ -1371,6 +1911,12 @@ bool mg_open_listener(struct mg_connection *c, const char *url) {
   if (!mg_aton(mg_url_host(url), &c->loc)) {
     MG_ERROR(("invalid listening URL: %s", url));
     return false;
+#if MG_ENABLE_IPV6
+  } else if (c->loc.is_ip6) {
+    c->loc.ip6[0] = c->mgr->ifp->ip6[0], c->loc.ip6[1] = c->mgr->ifp->ip6[1];
+#endif
+  } else {
+    c->loc.ip4 = c->mgr->ifp->ip;
   }
   return true;
 }
@@ -1390,9 +1936,8 @@ static void init_closure(struct mg_connection *c) {
   struct connstate *s = (struct connstate *) (c + 1);
   if (c->is_udp == false && c->is_listening == false &&
       c->is_connecting == false) {  // For TCP conns,
-  uint32_t rem_ip = c->rem.ip4;
-    tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port,
-           c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
+    tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_FIN | TH_ACK,
+           mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
     settmout(c, MIP_TTYPE_FIN);
   }
 }
@@ -1444,17 +1989,19 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
 bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
   struct mg_tcpip_if *ifp = c->mgr->ifp;
   bool res = false;
-  uint32_t rem_ip = c->rem.ip4;
-  if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) {
+  if (!c->loc.is_ip6 && (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY)) {
     mg_error(c, "net down");
+#if MG_ENABLE_IPV6
+  } else if (c->loc.is_ip6 && ifp->state6 != MG_TCPIP_STATE_READY) {
+    mg_error(c, "net down");
+#endif
   } else if (c->is_udp && (c->is_arplooking || c->is_resolving)) {
     // Fail to send, no target MAC or IP
     MG_VERBOSE(("still resolving..."));
   } else if (c->is_udp) {
     struct connstate *s = (struct connstate *) (c + 1);
     len = trim_len(c, len);  // Trimming length if necessary
-    res = tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf,
-                 len);
+    res = tx_udp(ifp, s->mac, &c->loc, &c->rem, buf, len);
   } else {
     res = mg_iobuf_add(&c->send, c->send.len, buf, len);
   }
@@ -1463,7 +2010,7 @@ bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
 
 uint8_t mcast_addr[6] = {0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb};
 void mg_multicast_add(struct mg_connection *c, char *ip) {
-  (void) ip;  // ip4_mcastmac(mcast_mac, &ip);
+  (void) ip;  // ip4/6_mcastmac(mcast_mac, &ip); ipv6 param
   // TODO(): actual IP -> MAC; check database, update
   c->mgr->ifp->update_mac_hash_table = true;  // mark dirty
 }
index e5d08482a1788ae0d32d6d174b2c441504dac9fb..ec88f51c59792e54d1885c149b84e4d7a648ae09 100644 (file)
@@ -20,18 +20,17 @@ typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev,
                                          void *ev_data);
 
 enum {
-  MG_TCPIP_EV_ST_CHG,  // state change                   uint8_t * (&ifp->state)
-  MG_TCPIP_EV_DHCP_DNS,   // DHCP DNS assignment            uint32_t *ipaddr
-  MG_TCPIP_EV_DHCP_SNTP,  // DHCP SNTP assignment           uint32_t *ipaddr
-  MG_TCPIP_EV_ARP,        // Got ARP packet                 struct mg_str *
-  MG_TCPIP_EV_TIMER_1S,   // 1 second timer                 NULL
-  MG_TCPIP_EV_WIFI_SCAN_RESULT,  // Wi-Fi scan results             struct
-                                 // mg_wifi_scan_bss_data *
-  MG_TCPIP_EV_WIFI_SCAN_END,     // Wi-Fi scan has finished        NULL
-  MG_TCPIP_EV_WIFI_CONNECT_ERR,  // Wi-Fi connect has failed       driver and
-                                 // chip specific
-  MG_TCPIP_EV_DRIVER,  // Driver event                   driver specific
-  MG_TCPIP_EV_USER     // Starting ID for user events
+  MG_TCPIP_EV_ST_CHG,           // state change                   uint8_t * (&ifp->state)
+  MG_TCPIP_EV_DHCP_DNS,         // DHCP DNS assignment            uint32_t *ipaddr
+  MG_TCPIP_EV_DHCP_SNTP,        // DHCP SNTP assignment           uint32_t *ipaddr
+  MG_TCPIP_EV_ARP,              // Got ARP packet                 struct mg_str *
+  MG_TCPIP_EV_TIMER_1S,         // 1 second timer                 NULL
+  MG_TCPIP_EV_WIFI_SCAN_RESULT, // Wi-Fi scan results             struct mg_wifi_scan_bss_data *
+  MG_TCPIP_EV_WIFI_SCAN_END,    // Wi-Fi scan has finished        NULL
+  MG_TCPIP_EV_WIFI_CONNECT_ERR, // Wi-Fi connect has failed       driver and chip specific
+  MG_TCPIP_EV_DRIVER,           // Driver event                   driver specific
+  MG_TCPIP_EV_ST6_CHG,          // state6 change                  uint8_t * (&ifp->state6)
+  MG_TCPIP_EV_USER              // Starting ID for user events
 };
 
 // Network interface
@@ -56,6 +55,13 @@ struct mg_tcpip_if {
   char dhcp_name[MG_TCPIP_DHCPNAME_SIZE];  // Name for DHCP, "mip" if unset
   uint16_t mtu;                            // Interface MTU
 #define MG_TCPIP_MTU_DEFAULT 1500
+#if MG_ENABLE_IPV6
+  uint64_t ip6ll[2], ip6[2];       // IPv6 link-local and global addresses
+  uint8_t prefix_len;              // Prefix length
+  uint64_t gw6[2];                 // Default gateway
+  bool enable_slaac;               // Enable IPv6 address autoconfiguration
+  bool enable_dhcp6_client;        // Enable DCHPv6 client
+#endif
 
   // Internal state, user can use it but should not change it
   uint8_t gwmac[6];             // Router's MAC
@@ -68,14 +74,17 @@ struct mg_tcpip_if {
   volatile uint32_t nrecv;      // Number of received frames
   volatile uint32_t nsent;      // Number of transmitted frames
   volatile uint32_t nerr;       // Number of driver errors
-  uint8_t state;                // Current state
+  uint8_t state;                // Current link and IPv4 state
 #define MG_TCPIP_STATE_DOWN 0   // Interface is down
 #define MG_TCPIP_STATE_UP 1     // Interface is up
 #define MG_TCPIP_STATE_REQ 2    // Interface is up, DHCP REQUESTING state
 #define MG_TCPIP_STATE_IP 3     // Interface is up and has an IP assigned
 #define MG_TCPIP_STATE_READY 4  // Interface has fully come up, ready to work
+#if MG_ENABLE_IPV6
+  uint8_t gw6mac[6];             // IPv6 Router's MAC
+  uint8_t state6;                // Current IPv6 state
+#endif
 };
-
 void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
 void mg_tcpip_free(struct mg_tcpip_if *);
 void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp);
index 0ba74a4e98adc5254e298b6d769181f0e8eca5c7..f5b6b6a34fc4162a4d83b871c4a17a3af9fe0047 100644 (file)
@@ -204,6 +204,10 @@ uint32_t mg_ntohl(uint32_t net) {
   return MG_LOAD_BE32(&net);
 }
 
+uint64_t mg_ntohll(uint64_t net) {
+  return MG_LOAD_BE64(&net);
+}
+
 void mg_delayms(unsigned int ms) {
   uint64_t to = mg_millis() + ms + 1;
   while (mg_millis() < to) (void) 0;
index b1ffaa34805b8b0c66e0515a6047c8d03b3e8778..cb14c1cea457cb8212a1aad17b39b367cf4a912a 100644 (file)
@@ -27,6 +27,16 @@ void mg_delayms(unsigned int ms);
 
 #define MG_IPV4(a, b, c, d) mg_htonl(MG_U32(a, b, c, d))
 
+#define MG_IPV6(a, b, c, d, e, f, g ,h) \
+  { (uint8_t)((a)>>8),(uint8_t)(a), \
+    (uint8_t)((b)>>8),(uint8_t)(b), \
+    (uint8_t)((c)>>8),(uint8_t)(c), \
+    (uint8_t)((d)>>8),(uint8_t)(d), \
+    (uint8_t)((e)>>8),(uint8_t)(e), \
+    (uint8_t)((f)>>8),(uint8_t)(f), \
+    (uint8_t)((g)>>8),(uint8_t)(g), \
+    (uint8_t)((h)>>8),(uint8_t)(h) }
+
 // For printing IPv4 addresses: printf("%d.%d.%d.%d\n", MG_IPADDR_PARTS(&ip))
 #define MG_U8P(ADDR) ((uint8_t *) (ADDR))
 #define MG_IPADDR_PARTS(ADDR) \
@@ -41,6 +51,14 @@ void mg_delayms(unsigned int ms);
   ((uint32_t) (((uint32_t) MG_U8P(p)[0] << 24U) | \
                ((uint32_t) MG_U8P(p)[1] << 16U) | \
                ((uint32_t) MG_U8P(p)[2] << 8U) | MG_U8P(p)[3]))
+#define MG_LOAD_BE64(p)                           \
+  ((uint64_t) (((uint64_t) MG_U8P(p)[0] << 56U) | \
+               ((uint64_t) MG_U8P(p)[1] << 48U) | \
+               ((uint64_t) MG_U8P(p)[2] << 40U) | \
+               ((uint64_t) MG_U8P(p)[3] << 32U) | \
+               ((uint64_t) MG_U8P(p)[4] << 24U) | \
+               ((uint64_t) MG_U8P(p)[5] << 16U) | \
+               ((uint64_t) MG_U8P(p)[6] << 8U) | MG_U8P(p)[7]))
 #define MG_STORE_BE16(p, n)           \
   do {                                \
     MG_U8P(p)[0] = ((n) >> 8U) & 255; \
@@ -59,11 +77,24 @@ void mg_delayms(unsigned int ms);
     MG_U8P(p)[2] = ((n) >> 8U) & 255;  \
     MG_U8P(p)[3] = (n) &255;           \
   } while (0)
+#define MG_STORE_BE64(p, n)            \
+  do {                                 \
+    MG_U8P(p)[0] = ((n) >> 56U) & 255; \
+    MG_U8P(p)[1] = ((n) >> 48U) & 255; \
+    MG_U8P(p)[2] = ((n) >> 40U) & 255; \
+    MG_U8P(p)[3] = ((n) >> 32U) & 255; \
+    MG_U8P(p)[4] = ((n) >> 24U) & 255; \
+    MG_U8P(p)[5] = ((n) >> 16U) & 255; \
+    MG_U8P(p)[6] = ((n) >> 8U) & 255;  \
+    MG_U8P(p)[7] = (n) &255;           \
+  } while (0)
 
 uint16_t mg_ntohs(uint16_t net);
 uint32_t mg_ntohl(uint32_t net);
+uint64_t mg_ntohll(uint64_t net);
 #define mg_htons(x) mg_ntohs(x)
 #define mg_htonl(x) mg_ntohl(x)
+#define mg_htonll(x) mg_ntohll(x)
 
 #define MG_REG(x) ((volatile uint32_t *) (x))[0]
 #define MG_BIT(x) (((uint32_t) 1U) << (x))
index d24d8692bfb1c8e6c31cc4ea54e29a03cf9d58df..7097b1d1b243de5fb59ea92aaaf880e53e08237e 100644 (file)
@@ -33,6 +33,10 @@ static int s_abort = 0;
     }                                                           \
   } while (0)
 
+struct ipp {
+  struct ip *ip4;
+  struct ip6 *ip6;
+};
 
 static void test_csum(void) {
   uint8_t ip[20] = {0x45, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x28, 0x11,
@@ -62,6 +66,28 @@ static void test_statechange(void) {
   ASSERT(executed == true);
   executed = false;
 }
+#if MG_ENABLE_IPV6
+static void mif6_fn(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
+  if (ev == MG_TCPIP_EV_ST6_CHG) {
+    ASSERT(*(uint8_t *) ev_data == MG_TCPIP_STATE_READY);
+    executed = true;
+  }
+  (void) ifp;
+}
+
+static void test_state6change(void) {
+  struct mg_tcpip_if iface;
+  memset(&iface, 0, sizeof(iface));
+  iface.ip6[0] = (uint64_t) mg_htonl(0x01020304);
+  iface.ip6[1] = (uint64_t) mg_htonl(0x05060708);
+  iface.state6 = MG_TCPIP_STATE_READY;
+  iface.driver = &mg_tcpip_driver_mock;
+  iface.fn = mif6_fn;
+  onstate6change(&iface);
+  ASSERT(executed == true);
+  executed = false;
+}
+#endif
 
 static void ph(struct mg_connection *c, int ev, void *ev_data) {
   if (ev == MG_EV_POLL) ++(*(int *) c->fn_data);
@@ -78,8 +104,7 @@ static void tcpclosure_fn(struct mg_connection *c, int ev, void *ev_data) {
 }
 
 static void client_fn(struct mg_connection *c, int ev, void *ev_data) {
-  if (ev == MG_EV_ERROR || ev == MG_EV_CONNECT)
-    (*(int *) c->fn_data) = ev;
+  if (ev == MG_EV_ERROR || ev == MG_EV_CONNECT) (*(int *) c->fn_data) = ev;
   (void) c, (void) ev_data;
 }
 
@@ -112,7 +137,6 @@ static void frag_send_fn(struct mg_connection *c, int ev, void *ev_data) {
   (void) c, (void) ev_data;
 }
 
-
 static void test_poll(void) {
   int count = 0, i;
   struct mg_tcpip_if mif;
@@ -156,6 +180,7 @@ static size_t if_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
   if (len > driver_data->len) len = driver_data->len;
   memcpy(buf, driver_data->buf, len);
   driver_data->len = 0;  // cleaning up the buffer
+  driver_data->tx_ready = false; 
   return len;
 }
 
@@ -165,9 +190,10 @@ static bool received_response(struct driver_data *driver) {
   return was_ready;
 }
 
-static void create_tcp_seg(struct eth *e, struct ip *ip, uint32_t seq,
+static void create_tcp_seg(struct eth *e, struct ipp *ipp, uint32_t seq,
                            uint32_t ack, uint8_t flags, uint16_t sport,
-                           uint16_t dport, size_t payload_len, void *opts, unsigned int opts_len) {
+                           uint16_t dport, size_t payload_len, void *opts,
+                           unsigned int opts_len) {
   struct tcp t;
   memset(&t, 0, sizeof(struct tcp));
   t.flags = flags;
@@ -177,26 +203,44 @@ static void create_tcp_seg(struct eth *e, struct ip *ip, uint32_t seq,
   t.dport = mg_htons(dport);
   t.off = (uint8_t) ((sizeof(t) / 4) << 4) + (uint8_t) ((opts_len / 4) << 4);
   memcpy(s_driver_data.buf, e, sizeof(*e));
-  ip->len =
-      mg_htons((uint16_t) (sizeof(*ip) + 4 * (t.off >> 4) + payload_len));
-  memcpy(s_driver_data.buf + sizeof(*e), ip, sizeof(*ip));
-  memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip), &t, sizeof(t));
-  if (opts != NULL && opts_len)
-    memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip) + sizeof(t), opts, opts_len);
-  s_driver_data.len = sizeof(*e) + sizeof(*ip) + sizeof(t) + payload_len + opts_len;
-  if (s_driver_data.len < 64) s_driver_data.len = 64; // add padding when needed
+#if MG_ENABLE_IPV6
+  if (ipp->ip6 != NULL) {
+    struct ip6 *ip = ipp->ip6;
+    ip->plen = mg_htons((uint16_t) (4 * (t.off >> 4) + payload_len));
+    memcpy(s_driver_data.buf + sizeof(*e), ip, sizeof(*ip));
+    memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip), &t, sizeof(t));
+    if (opts != NULL && opts_len)
+      memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip) + sizeof(t), opts,
+             opts_len);
+    s_driver_data.len =
+        sizeof(*e) + sizeof(*ip) + sizeof(t) + payload_len + opts_len;
+  } else
+#endif
+  {
+    struct ip *ip = ipp->ip4;
+    ip->len =
+        mg_htons((uint16_t) (sizeof(*ip) + 4 * (t.off >> 4) + payload_len));
+    memcpy(s_driver_data.buf + sizeof(*e), ip, sizeof(*ip));
+    memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip), &t, sizeof(t));
+    if (opts != NULL && opts_len)
+      memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip) + sizeof(t), opts,
+             opts_len);
+    s_driver_data.len =
+        sizeof(*e) + sizeof(*ip) + sizeof(t) + payload_len + opts_len;
+  }
+  if (s_driver_data.len < 64) s_driver_data.len = 64;  // add padding if needed
 }
 
-static void create_tcp_simpleseg(struct eth *e, struct ip *ip, uint32_t seq,
+static void create_tcp_simpleseg(struct eth *e, struct ipp *ipp, uint32_t seq,
                                  uint32_t ack, uint8_t flags,
                                  size_t payload_len) {
   // use sport=1 to ease seqno stuff, dport=80 due to init_tcp_tests() below
-  create_tcp_seg(e, ip, seq, ack, flags, 1, 80, payload_len, NULL, 0);
+  create_tcp_seg(e, ipp, seq, ack, flags, 1, 80, payload_len, NULL, 0);
 }
 
-static void init_tests(struct mg_mgr *mgr, struct eth *e, struct ip *ip,
-                           struct mg_tcpip_driver *driver,
-                           struct mg_tcpip_if *mif, uint8_t proto) {
+static void init_tests(struct mg_mgr *mgr, struct eth *e, struct ipp *ipp,
+                       struct mg_tcpip_driver *driver, struct mg_tcpip_if *mif,
+                       uint8_t proto) {
   mg_mgr_init(mgr);
   memset(mif, 0, sizeof(*mif));
   memset(&s_driver_data, 0, sizeof(struct driver_data));
@@ -204,37 +248,74 @@ static void init_tests(struct mg_mgr *mgr, struct eth *e, struct ip *ip,
   driver->rx = if_rx;
   mif->driver = driver;
   mif->driver_data = &s_driver_data;
+#if MG_ENABLE_IPV6
+  if (ipp->ip6 != NULL) {
+    mif->ip6[0] = 1;
+    mif->state = MG_TCPIP_STATE_READY;  // so DHCP stops
+    mif->state6 = MG_TCPIP_STATE_READY;  // so mg_send() works and RS stops
+  } else
+#endif
+  {
+    mif->ip = 1;
+    mif->mask = 255; // use router, to avoid firing an ARP request
+    mif->state = MG_TCPIP_STATE_READY;  // so mg_send() works and DHCP stops
+  }
   mg_tcpip_init(mgr, mif);
 
   // setting the Ethernet header
   memset(e, 0, sizeof(*e));
   memcpy(e->dst, mif->mac, 6 * sizeof(uint8_t));
-  e->type = mg_htons(0x800);
+  e->type = mg_htons(ipp->ip4 != NULL ? 0x800 : 0x86dd);
 
   // setting the IP header
-  memset(ip, 0, sizeof(*ip));
-  ip->ver = (4 << 4) | 5;
-  ip->proto = proto;
+#if MG_ENABLE_IPV6
+  if (ipp->ip6 != NULL) {
+    struct ip6 *ip = ipp->ip6;
+    memset(ip, 0, sizeof(*ip));
+    ip->ver = 0x60;
+    ip->next = proto;
+    // must be outside of Mongoose network to avoid firing NS requests
+    ip->src[0] = 2;
+    ip->dst[0] = mif->ip6[0];
+  } else
+#endif
+  {
+    struct ip *ip = ipp->ip4;
+    memset(ip, 0, sizeof(*ip));
+    ip->ver = (4 << 4) | 5;
+    ip->proto = proto;
+    // must be outside of Mongoose network to avoid firing ARP requests
+    ip->src = 2;
+    ip->dst = mif->ip;
+  }
 }
 
-static void init_tcp_tests(struct mg_mgr *mgr, struct eth *e, struct ip *ip,
+static void init_tcp_tests(struct mg_mgr *mgr, struct eth *e, struct ipp *ipp,
                            struct mg_tcpip_driver *driver,
                            struct mg_tcpip_if *mif, mg_event_handler_t f) {
-
-  init_tests(mgr, e, ip, driver, mif, 6); // 6 -> TCP                            
-  mg_http_listen(mgr, "http://0.0.0.0:80", f, NULL);
+  init_tests(mgr, e, ipp, driver, mif, 6);  // 6 -> TCP
+#if MG_ENABLE_IPV6
+  if (ipp->ip6 != NULL) {
+    mg_http_listen(mgr, "http://[::]:80", f, NULL);
+  } else
+#endif
+  {
+    mg_http_listen(mgr, "http://0.0.0.0:80", f, NULL);
+  }
   mgr->conns->pfn = NULL;  // HTTP handler not needed
   mg_mgr_poll(mgr, 0);
 }
 
-static void init_tcp_handshake(struct eth *e, struct ip *ip,
+static void init_tcp_handshake(struct eth *e, struct ipp *ipp,
                                struct mg_mgr *mgr) {
-  struct tcp *t = (struct tcp *)(s_driver_data.buf + sizeof(*e) + sizeof(*ip));
+  struct tcp *t =
+      (struct tcp *) (s_driver_data.buf + sizeof(*e) +
+                      (ipp->ip4 ? sizeof(struct ip) : sizeof(struct ip6)));
 
   // SYN
-  create_tcp_simpleseg(e, ip, 1000, 0, TH_SYN, 0);
+  create_tcp_simpleseg(e, ipp, 1000, 0, TH_SYN, 0);
   MG_VERBOSE(("SYN     -->"));
-  mg_mgr_poll(mgr, 0); // make sure we clean former stuff in buffer
+  mg_mgr_poll(mgr, 0);  // make sure we clean former stuff in buffer
 
   // SYN-ACK
   while (!received_response(&s_driver_data)) mg_mgr_poll(mgr, 0);
@@ -243,7 +324,7 @@ static void init_tcp_handshake(struct eth *e, struct ip *ip,
   MG_VERBOSE(("SYN+ACK <--"));
 
   // ACK
-  create_tcp_simpleseg(e, ip, 1001, 2, TH_ACK, 0);
+  create_tcp_simpleseg(e, ipp, 1001, 2, TH_ACK, 0);
   MG_VERBOSE(("ACK     -->"));
   mg_mgr_poll(mgr, 0);  // this may have data on return !
 }
@@ -251,36 +332,46 @@ static void init_tcp_handshake(struct eth *e, struct ip *ip,
 // DHCP discovery works as a 1 second timeout, we take advantage of it
 // (something is received within 1s) and we mask it when doing longer waits
 // (verify received data is TCP by checking IP's protocol field)
-static void test_tcp_basics(void) {
+static void test_tcp_basics(bool ipv6) {
   struct mg_mgr mgr;
   struct eth e;
   struct ip ip;
-  struct tcp *t = (struct tcp *) (s_driver_data.buf + sizeof(e) + sizeof(ip));
+  struct ip6 ip6;
+  struct ipp ipp;
+  struct tcp *t = (struct tcp *) (s_driver_data.buf + sizeof(e) + (!ipv6 ? sizeof(ip) : sizeof(ip6)));
   struct ip *i = (struct ip *) (s_driver_data.buf + sizeof(e));
+  struct ip6 *i6 = (struct ip6 *) (s_driver_data.buf + sizeof(e));
   uint64_t start, now;
   struct mg_tcpip_driver driver;
   struct mg_tcpip_if mif;
 
-  init_tcp_tests(&mgr, &e, &ip, &driver, &mif, fn);
+  ipp.ip4 = !ipv6 ? &ip : NULL;
+  ipp.ip6 = ipv6 ? &ip6 : NULL;
+
+  init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, fn);
 
-// https://datatracker.ietf.org/doc/html/rfc9293#section-3.5.2 Reset Generation
-  // non-used port. Group 1 in RFC
-  // send SYN, expect RST + ACK
-  create_tcp_seg(&e, &ip, 1234, 4321, TH_SYN, 1, 69, 0, NULL, 0);
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  // https://datatracker.ietf.org/doc/html/rfc9293#section-3.5.2       Reset
+  // Generation non-used port. Group 1 in RFC send SYN, expect RST + ACK
+  create_tcp_seg(&e, &ipp, 1234, 4321, TH_SYN, 1, 69, 0, NULL, 0);
+  mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT(t->flags == (TH_RST | TH_ACK));
   ASSERT(t->seq == mg_htonl(0));
   ASSERT(t->ack == mg_htonl(1235));
-
+  if (ipv6) {
+    ASSERT(i6->src[0] == 1 && i6->dst[0] == 2);
+  } else {
+    ASSERT(i->src == 1 && i->dst == 2);
+  }
+  
   // send SYN+ACK, expect RST
-  create_tcp_seg(&e, &ip, 1234, 4321, TH_SYN | TH_ACK, 1, 69, 0, NULL, 0);
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  create_tcp_seg(&e, &ipp, 1234, 4321, TH_SYN | TH_ACK, 1, 69, 0, NULL, 0);
+  mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT(t->flags == TH_RST);
   ASSERT(t->seq == mg_htonl(4321));
   // send data, expect RST + ACK
-  create_tcp_seg(&e, &ip, 1234, 4321, TH_PUSH, 1, 69, 2, NULL, 0);
+  create_tcp_seg(&e, &ipp, 1234, 4321, TH_PUSH, 1, 69, 2, NULL, 0);
   mg_mgr_poll(&mgr, 0);
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT(t->flags == (TH_RST | TH_ACK));
@@ -288,95 +379,104 @@ static void test_tcp_basics(void) {
   ASSERT(t->ack == mg_htonl(1236));
 
   // send ACK, expect RST
-  create_tcp_seg(&e, &ip, 1234, 4321, TH_ACK, 1, 69, 0, NULL, 0);
+  create_tcp_seg(&e, &ipp, 1234, 4321, TH_ACK, 1, 69, 0, NULL, 0);
   mg_mgr_poll(&mgr, 0);
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT(t->flags == TH_RST);
   ASSERT(t->seq == mg_htonl(4321));
 
   // send FIN, expect RST + ACK
-  create_tcp_seg(&e, &ip, 1234, 4321, TH_FIN, 1, 69, 0, NULL, 0);
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  create_tcp_seg(&e, &ipp, 1234, 4321, TH_FIN, 1, 69, 0, NULL, 0);
+  mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
-  ASSERT(t->flags == (TH_RST | TH_ACK)); // Linux answers RST only
+  ASSERT(t->flags == (TH_RST | TH_ACK));  // Linux answers RST only
   ASSERT(t->seq == mg_htonl(0));
   ASSERT(t->ack == mg_htonl(1235));
 
   // send FIN+ACK, expect RST
-  create_tcp_seg(&e, &ip, 1234, 4321, TH_FIN | TH_ACK, 1, 69, 0, NULL, 0);
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  create_tcp_seg(&e, &ipp, 1234, 4321, TH_FIN | TH_ACK, 1, 69, 0, NULL, 0);
+  mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT(t->flags == TH_RST);
   ASSERT(t->seq == mg_htonl(4321));
 
   // listening, non-connected port. Group 2 in RFC
   // send data, expect no response
-  create_tcp_seg(&e, &ip, 1234, 4321, TH_PUSH, 1, 80, 2, NULL, 0);
+  create_tcp_seg(&e, &ipp, 1234, 4321, TH_PUSH, 1, 80, 2, NULL, 0);
   mg_mgr_poll(&mgr, 0);
   ASSERT(!received_response(&s_driver_data));
 
   // send ACK, expect RST
-  create_tcp_seg(&e, &ip, 1234, 4321, TH_ACK, 1, 80, 0, NULL, 0);
+  create_tcp_seg(&e, &ipp, 1234, 4321, TH_ACK, 1, 80, 0, NULL, 0);
   mg_mgr_poll(&mgr, 0);
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT(t->flags == TH_RST);
   ASSERT(t->seq == mg_htonl(4321));
+  if (ipv6) {
+    ASSERT(i6->src[0] == 1 && i6->dst[0] == 2);
+  } else {
+    ASSERT(i->src == 1 && i->dst == 2);
+  }
 
   // send SYN+ACK, expect RST
-  create_tcp_seg(&e, &ip, 1234, 4321, TH_SYN | TH_ACK, 1, 80, 0, NULL, 0);
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  create_tcp_seg(&e, &ipp, 1234, 4321, TH_SYN | TH_ACK, 1, 80, 0, NULL, 0);
+  mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT(t->flags == TH_RST);
   ASSERT(t->seq == mg_htonl(4321));
 
   // send FIN, expect no response
-  create_tcp_seg(&e, &ip, 1234, 4321, TH_FIN, 1, 80, 0, NULL, 0);
+  create_tcp_seg(&e, &ipp, 1234, 4321, TH_FIN, 1, 80, 0, NULL, 0);
   mg_mgr_poll(&mgr, 0);
   ASSERT(!received_response(&s_driver_data));
 
   // send FIN+ACK, expect RST
-  create_tcp_seg(&e, &ip, 1234, 4321, TH_FIN | TH_ACK, 1, 80, 0, NULL, 0);
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  create_tcp_seg(&e, &ipp, 1234, 4321, TH_FIN | TH_ACK, 1, 80, 0, NULL, 0);
+  mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT(t->flags == TH_RST);
   ASSERT(t->seq == mg_htonl(4321));
 
-
   // we currently don't validate checksum, no silently discarded segment test
 
+  init_tcp_handshake(&e, &ipp, &mgr);  // starts with seq_no=1000, ackno=2
 
-  init_tcp_handshake(&e, &ip, &mgr);   // starts with seq_no=1000, ackno=2
-
-  // no MSS sent, so it must default to 536 (RFC-9293 3.7.1)
-  ASSERT((((struct connstate *)(mgr.conns + 1))->dmss == 536));
+  // no MSS sent, so it must default to 536/1220 (RFC-9293 3.7.1)
+  ASSERT(((struct connstate *) (mgr.conns + 1))->dmss == (ipv6 ? 1220 : 536));
 
   // segment with seq_no within window
-  create_tcp_simpleseg(&e, &ip, 1010, 2, TH_PUSH, 2);
+  create_tcp_simpleseg(&e, &ipp, 1010, 2, TH_PUSH, 2);
   mg_mgr_poll(&mgr, 0);
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT((t->flags == TH_ACK));
   ASSERT((t->ack == mg_htonl(1001)));  // expecting 1001, dude
+  if (ipv6) {
+    ASSERT(i6->src[0] == 1 && i6->dst[0] == 2);
+  } else {
+    ASSERT(i->src == 1 && i->dst == 2);
+  }
 
   // segment with seq_no way out of window
-  create_tcp_simpleseg(&e, &ip, 1000000, 2, TH_PUSH, 2);
+  create_tcp_simpleseg(&e, &ipp, 1000000, 2, TH_PUSH, 2);
   mg_mgr_poll(&mgr, 0);
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT((t->flags == TH_ACK));
   ASSERT((t->ack == mg_htonl(1001)));  // expecting 1001, dude
 
   // Initiate closure, send FIN (test client-initiated closure)
-  // https://datatracker.ietf.org/doc/html/rfc9293#section-3.6 
+  // https://datatracker.ietf.org/doc/html/rfc9293#section-3.6
   // We are case 1, Mongoose is case 2
-  create_tcp_simpleseg(&e, &ip, 1001, 2, TH_FIN, 0);
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  create_tcp_simpleseg(&e, &ipp, 1001, 2, TH_FIN, 0);
+  mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   // Mongoose does a fast reduced ("3-way instead of 4-way" closure)
-  ASSERT((t->flags == (TH_FIN | TH_ACK))); // Mongoose ACKs our FIN, sends FIN
+  ASSERT((t->flags == (TH_FIN | TH_ACK)));  // Mongoose ACKs our FIN, sends FIN
   ASSERT((t->seq == mg_htonl(2)));
   ASSERT((t->ack == mg_htonl(1002)));
   // make sure it is still open
-  ASSERT(mgr.conns->next != NULL);  // more than one connection: the listener + us
-  create_tcp_simpleseg(&e, &ip, 1002, 3, TH_ACK, 0); // ACK Mongoose FIN
+  ASSERT(mgr.conns->next !=
+         NULL);  // more than one connection: the listener + us
+  create_tcp_simpleseg(&e, &ipp, 1002, 3, TH_ACK, 0);  // ACK Mongoose FIN
   mg_mgr_poll(&mgr, 0);
   ASSERT(!received_response(&s_driver_data));
   // make sure it is closed
@@ -386,25 +486,27 @@ static void test_tcp_basics(void) {
   mg_mgr_free(&mgr);
 
   // Test client-initiated closure timeout, do not ACK
-  init_tcp_tests(&mgr, &e, &ip, &driver, &mif, fn);
-  init_tcp_handshake(&e, &ip, &mgr);   // starts with seq_no=1000, ackno=2
-  create_tcp_simpleseg(&e, &ip, 1001, 2, TH_FIN, 0);
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, fn);
+  init_tcp_handshake(&e, &ipp, &mgr);  // starts with seq_no=1000, ackno=2
+  create_tcp_simpleseg(&e, &ipp, 1001, 2, TH_FIN, 0);
+  mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   // Mongoose does a fast reduced ("3-way instead of 4-way" closure)
-  ASSERT((t->flags == (TH_FIN | TH_ACK))); // Mongoose ACKs our FIN, sends FIN
+  ASSERT((t->flags == (TH_FIN | TH_ACK)));  // Mongoose ACKs our FIN, sends FIN
   ASSERT((t->seq == mg_htonl(2)));
   ASSERT((t->ack == mg_htonl(1002)));
   // make sure it is still open
-  ASSERT(mgr.conns->next != NULL);  // more than one connection: the listener + us
-  s_driver_data.len = 0; // avoid Mongoose "receiving itself"
+  ASSERT(mgr.conns->next !=
+         NULL);           // more than one connection: the listener + us
+  s_driver_data.len = 0;  // avoid Mongoose "receiving itself"
   start = mg_millis();
   now = 0;
   do {
     mg_mgr_poll(&mgr, 0);
-    if (received_response(&s_driver_data) && i->proto == 6) break; // check first
+    if (received_response(&s_driver_data) && (ipv6 ? i6->next : i->proto) == 6)
+      break;  // check first
     now = mg_millis() - start;
-  } while (now < (12 * MIP_TCP_FIN_MS)/10);
+  } while (now < (12 * MIP_TCP_FIN_MS) / 10);
   ASSERT(now > MIP_TCP_FIN_MS);
   // make sure it is closed
   ASSERT(mgr.conns->next == NULL);  // only one connection: the listener
@@ -415,8 +517,8 @@ static void test_tcp_basics(void) {
   // Test server-initiated closure, abbreviated 3-way: respond FIN+ACK
   // https://datatracker.ietf.org/doc/html/rfc9293#section-3.6
   // We are case 2, Mongoose is case 1
-  init_tcp_tests(&mgr, &e, &ip, &driver, &mif, tcpclosure_fn);
-  init_tcp_handshake(&e, &ip, &mgr);   // starts with seq_no=1000, ackno=2
+  init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, tcpclosure_fn);
+  init_tcp_handshake(&e, &ipp, &mgr);  // starts with seq_no=1000, ackno=2
   // we should have already received the FIN due to the call above
   start = mg_millis();
   while (!received_response(&s_driver_data)) {
@@ -427,12 +529,13 @@ static void test_tcp_basics(void) {
   }
   ASSERT((t->seq == mg_htonl(2)));
   ASSERT((t->ack == mg_htonl(1001)));
-  ASSERT(t->flags == (TH_FIN | TH_ACK)); // Mongoose ACKs last data, sends FIN
+  ASSERT(t->flags == (TH_FIN | TH_ACK));  // Mongoose ACKs last data, sends FIN
   // send FIN + ACK
-  create_tcp_simpleseg(&e, &ip, 1001, 3, TH_FIN | TH_ACK, 0); // ACK FIN, send FIN
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  create_tcp_simpleseg(&e, &ipp, 1001, 3, TH_FIN | TH_ACK,
+                       0);  // ACK FIN, send FIN
+  mg_mgr_poll(&mgr, 0);     // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
-  ASSERT((t->flags == TH_ACK)); // Mongoose ACKs our FIN
+  ASSERT((t->flags == TH_ACK));  // Mongoose ACKs our FIN
   ASSERT((t->seq == mg_htonl(3)));
   ASSERT((t->ack == mg_htonl(1002)));
   // make sure it is closed
@@ -442,30 +545,31 @@ static void test_tcp_basics(void) {
   mg_mgr_free(&mgr);
 
   // Test server-initiated closure, long 4-way closure: respond ACK
-  init_tcp_tests(&mgr, &e, &ip, &driver, &mif, tcpclosure_fn);
-  init_tcp_handshake(&e, &ip, &mgr);   // starts with seq_no=1000, ackno=2
+  init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, tcpclosure_fn);
+  init_tcp_handshake(&e, &ipp, &mgr);  // starts with seq_no=1000, ackno=2
   // we should have already received the FIN, tested in above tst
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT((t->seq == mg_htonl(2)));
   ASSERT((t->ack == mg_htonl(1001)));
-  ASSERT(t->flags == (TH_FIN | TH_ACK)); // Mongoose ACKs last data, sends FIN
+  ASSERT(t->flags == (TH_FIN | TH_ACK));  // Mongoose ACKs last data, sends FIN
   // ACK Mongoose FIN, do *not* send FIN yet
-  create_tcp_simpleseg(&e, &ip, 1001, 3, TH_ACK, 0); // ACK FIN
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  create_tcp_simpleseg(&e, &ipp, 1001, 3, TH_ACK, 0);  // ACK FIN
+  mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
   start = mg_millis();
   now = 0;
   do {
-    if (received_response(&s_driver_data)) break; // check first
+    if (received_response(&s_driver_data)) break;  // check first
     mg_mgr_poll(&mgr, 0);
     now = mg_millis() - start;
-  } while (now < 2 * MIP_TCP_ACK_MS); // keep timeout below 1s (DHCP discover)
+  } while (now < 2 * MIP_TCP_ACK_MS);  // keep timeout below 1s (DHCP discover)
   ASSERT(now >= 2 * MIP_TCP_ACK_MS);
   // make sure it is still open
-  ASSERT(mgr.conns->next != NULL);  // more than one connection: the listener + us
-  create_tcp_simpleseg(&e, &ip, 1001, 3, TH_FIN, 0); // send FIN
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  ASSERT(mgr.conns->next !=
+         NULL);  // more than one connection: the listener + us
+  create_tcp_simpleseg(&e, &ipp, 1001, 3, TH_FIN, 0);  // send FIN
+  mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
-  ASSERT((t->flags == TH_ACK)); // Mongoose ACKs our FIN
+  ASSERT((t->flags == TH_ACK));  // Mongoose ACKs our FIN
   ASSERT((t->seq == mg_htonl(3)));
   ASSERT((t->ack == mg_htonl(1002)));
   // make sure it is closed
@@ -476,30 +580,31 @@ static void test_tcp_basics(void) {
 
   // Test server-initiated closure, FIN retransmission: do not ACK FIN
   // Actual data retransmission is tested on another unit test
-  init_tcp_tests(&mgr, &e, &ip, &driver, &mif, tcpclosure_fn);
-  init_tcp_handshake(&e, &ip, &mgr);   // starts with seq_no=1000, ackno=2
+  init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, tcpclosure_fn);
+  init_tcp_handshake(&e, &ipp, &mgr);  // starts with seq_no=1000, ackno=2
   // we should have already received the FIN, tested in some tst above
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT((t->seq == mg_htonl(2)));
   ASSERT((t->ack == mg_htonl(1001)));
-  ASSERT(t->flags == (TH_FIN | TH_ACK)); // Mongoose ACKs last data, sends FIN
-  s_driver_data.len = 0; // avoid Mongoose "receiving itself"
+  ASSERT(t->flags == (TH_FIN | TH_ACK));  // Mongoose ACKs last data, sends FIN
+  s_driver_data.len = 0;                  // avoid Mongoose "receiving itself"
   start = mg_millis();
   now = 0;
   do {
-    if (received_response(&s_driver_data)) break; // check first
+    if (received_response(&s_driver_data)) break;  // check first
     mg_mgr_poll(&mgr, 0);
     now = mg_millis() - start;
-  } while (now < 2 * MIP_TCP_ACK_MS); // keep timeout below 1s (DHCP discover)
-//  ASSERT(now < 2 * MIP_TCP_ACK_MS); ******** WE FAIL THIS, Mongoose does not retransmit, FIN is not an additional element in the stream
-//  ASSERT((t->seq == mg_htonl(2)));
-//  ASSERT((t->ack == mg_htonl(1001)));
-//  ASSERT(t->flags == (TH_FIN | TH_ACK)); // Mongoose retransmits FIN
+  } while (now < 2 * MIP_TCP_ACK_MS);  // keep timeout below 1s (DHCP discover)
+  //  ASSERT(now < 2 * MIP_TCP_ACK_MS); ******** WE FAIL THIS, Mongoose does not
+  //  retransmit, FIN is not an additional element in the stream ASSERT((t->seq
+  //  == mg_htonl(2))); ASSERT((t->ack == mg_htonl(1001))); ASSERT(t->flags ==
+  //  (TH_FIN | TH_ACK)); // Mongoose retransmits FIN
   // send FIN + ACK
-  create_tcp_simpleseg(&e, &ip, 1001, 3, TH_FIN | TH_ACK, 0); // ACK FIN, send FIN
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  create_tcp_simpleseg(&e, &ipp, 1001, 3, TH_FIN | TH_ACK,
+                       0);  // ACK FIN, send FIN
+  mg_mgr_poll(&mgr, 0);     // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
-  ASSERT((t->flags == TH_ACK)); // Mongoose ACKs our FIN
+  ASSERT((t->flags == TH_ACK));  // Mongoose ACKs our FIN
   ASSERT((t->seq == mg_htonl(3)));
   ASSERT((t->ack == mg_htonl(1002)));
   // make sure it is closed
@@ -510,8 +615,8 @@ static void test_tcp_basics(void) {
 
   // Test simultaneous closure
   // https://datatracker.ietf.org/doc/html/rfc9293#section-3.6 case 3
-  init_tcp_tests(&mgr, &e, &ip, &driver, &mif, tcpclosure_fn);
-  init_tcp_handshake(&e, &ip, &mgr);   // starts with seq_no=1000, ackno=2
+  init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, tcpclosure_fn);
+  init_tcp_handshake(&e, &ipp, &mgr);  // starts with seq_no=1000, ackno=2
   // we should have already received the FIN due to the call above
   start = mg_millis();
   while (!received_response(&s_driver_data)) {
@@ -522,18 +627,20 @@ static void test_tcp_basics(void) {
   }
   ASSERT((t->seq == mg_htonl(2)));
   ASSERT((t->ack == mg_htonl(1001)));
-  ASSERT(t->flags == (TH_FIN | TH_ACK)); // Mongoose ACKs last data, sends FIN
+  ASSERT(t->flags == (TH_FIN | TH_ACK));  // Mongoose ACKs last data, sends FIN
   // Also initiate closure, send FIN, do *not* ACK Mongoose FIN
-  create_tcp_simpleseg(&e, &ip, 1001, 2, TH_FIN, 0); 
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  create_tcp_simpleseg(&e, &ipp, 1001, 2, TH_FIN, 0);
+  mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
-  ASSERT((t->flags == TH_ACK)); // Mongoose ACKs our FIN
+  ASSERT((t->flags == TH_ACK));  // Mongoose ACKs our FIN
   ASSERT((t->seq == mg_htonl(3)));
   ASSERT((t->ack == mg_htonl(1002)));
-  // make sure it is still open   ******** WE FAIL THIS, Mongoose closes immediately, does not wait to retransmit its ACK nor to get the other end ACK
-//  ASSERT(mgr.conns->next != NULL);  // more than one connection: the listener + us
-//  create_tcp_simpleseg(&e, &ip, 1002, 3, TH_ACK, 0); // ACK FIN
-//  mg_mgr_poll(&mgr, 0);
+  // make sure it is still open   ******** WE FAIL THIS, Mongoose closes
+  // immediately, does not wait to retransmit its ACK nor to get the other end
+  // ACK
+  //  ASSERT(mgr.conns->next != NULL);  // more than one connection: the
+  //  listener + us create_tcp_simpleseg(&e, &ipp, 1002, 3, TH_ACK, 0); // ACK
+  //  FIN mg_mgr_poll(&mgr, 0);
   // make sure it is closed
   ASSERT(mgr.conns->next == NULL);  // only one connection: the listener
 
@@ -541,56 +648,74 @@ static void test_tcp_basics(void) {
   mg_mgr_free(&mgr);
 
   // Test responses to a connecting client
-  // https://datatracker.ietf.org/doc/html/rfc9293#section-3.5 
+  // https://datatracker.ietf.org/doc/html/rfc9293#section-3.5
   // NOTE: Mongoose ignores any data until connection is actually established
-  // NOTE: Mongoose does not support the concept of "simultaneous open", Mongoose is either client or server
+  // NOTE: Mongoose does not support the concept of "simultaneous open",
+  // Mongoose is either client or server
   {
-  struct mg_connection *c;
-  int event = 255;
-  uint32_t ackno;
-  // this creates a listener we won't use
-  init_tcp_tests(&mgr, &e, &ip, &driver, &mif, tcpclosure_fn);
-
-  c = mg_connect(&mgr, "tcp://1.2.3.4:1234/", client_fn, &event);
-  ASSERT(c!=NULL);
-  ASSERT(received_response(&s_driver_data));
-  ASSERT((t->flags == TH_SYN));
-  ASSERT(event == 255);
-  // invalid SYN + ACK to connecting client (after SYN...), send ACK out of seq
-  ackno = mg_ntohl(t->seq) + 1000;
-//  create_tcp_seg(&e, &ip, 4321, ackno, TH_SYN | TH_ACK, 1234, mg_ntohs(c->loc.port), 0, NULL, 0);
-//  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
-//  while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
-//  ASSERT((t->flags == (TH_RST | TH_ACK)));  // ***************** WHAT DOES LINUX DO HERE ????
-// ******** WE FAIL THIS, Mongoose does not validate the ACK number
-//  ASSERT((t->seq == mg_htonl(ackno)));
-//  ASSERT((t->ack == mg_htonl(4322)));
-
-  // connect
-  ackno = mg_ntohl(t->seq) + 1;
-  create_tcp_seg(&e, &ip, 4321, ackno, TH_SYN | TH_ACK, 1234, mg_ntohs(c->loc.port), 0, NULL, 0);
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
-  while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
-  ASSERT(t->flags == TH_ACK);
-  ASSERT(t->seq == mg_htonl(ackno));
-  ASSERT((t->ack == mg_htonl(4322)));
-  ASSERT(event == MG_EV_CONNECT);
-
-  event = 255;
-  s_driver_data.len = 0;
-  mg_mgr_free(&mgr);
+    struct mg_connection *c;
+    int event = 255;
+    uint32_t ackno;
+    // this creates a listener we won't use
+    init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, tcpclosure_fn);
+
+    // must be outside of our network to avoid firing ARP requests  
+    if (ipv6) {
+      c = mg_connect(&mgr, "tcp://[200::]:1234/", client_fn, &event);
+    } else {
+      c = mg_connect(&mgr, "tcp://2.0.0.0:1234/", client_fn, &event);
+    }
+    ASSERT(c != NULL);
+    ASSERT(received_response(&s_driver_data));
+    ASSERT((t->flags == TH_SYN));
+    ASSERT(event == 255);
+    if (ipv6) {
+      ASSERT(i6->src[0] == 1 && i6->dst[0] == 2);
+    } else {
+      ASSERT(i->src == 1 && i->dst == 2);
+    }
 
-  // test connection failure, send RST+ACK
-  // this creates a listener we won't use
-  init_tcp_tests(&mgr, &e, &ip, &driver, &mif, tcpclosure_fn);
-  c = mg_connect(&mgr, "tcp://1.2.3.4:1234/", client_fn, &event);
-  received_response(&s_driver_data); // get the SYN
-  ackno = mg_ntohl(t->seq) + 1;
-  create_tcp_seg(&e, &ip, 4321, ackno, TH_RST + TH_ACK, 1234, mg_ntohs(c->loc.port), 0, NULL, 0);
-  mg_mgr_poll(&mgr, 0);
-  MG_DEBUG(("event: %d", event));
-  ASSERT(event == MG_EV_ERROR);
-  ASSERT(!received_response(&s_driver_data));
+    // invalid SYN + ACK to connecting client (after SYN...), send ACK != seq
+    ackno = mg_ntohl(t->seq) + 1000;
+    //  create_tcp_seg(&e, &ipp, 4321, ackno, TH_SYN | TH_ACK, 1234,
+    //  mg_ntohs(c->loc.port), 0, NULL, 0); mg_mgr_poll(&mgr, 0); 
+    //  while(!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
+    //  ASSERT((t->flags == (TH_RST | TH_ACK)));
+    // ******** WE FAIL THIS, Mongoose does not validate the ACK number
+    //  ASSERT((t->seq == mg_htonl(ackno)));
+    //  ASSERT((t->ack == mg_htonl(4322)));
+
+    // connect
+    ackno = mg_ntohl(t->seq) + 1;
+    create_tcp_seg(&e, &ipp, 4321, ackno, TH_SYN | TH_ACK, 1234,
+                   mg_ntohs(c->loc.port), 0, NULL, 0);
+    mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
+    while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
+    ASSERT(t->flags == TH_ACK);
+    ASSERT(t->seq == mg_htonl(ackno));
+    ASSERT((t->ack == mg_htonl(4322)));
+    ASSERT(event == MG_EV_CONNECT);
+
+    event = 255;
+    s_driver_data.len = 0;
+    mg_mgr_free(&mgr);
+
+    // test connection failure, send RST+ACK
+    // this creates a listener we won't use
+    init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, tcpclosure_fn);
+    if (ipv6) {
+      c = mg_connect(&mgr, "tcp://[200::]:1234/", client_fn, &event);
+    } else {
+      c = mg_connect(&mgr, "tcp://2.0.0.0:1234/", client_fn, &event);
+    }
+    received_response(&s_driver_data);  // get the SYN
+    ackno = mg_ntohl(t->seq) + 1;
+    create_tcp_seg(&e, &ipp, 4321, ackno, TH_RST + TH_ACK, 1234,
+                   mg_ntohs(c->loc.port), 0, NULL, 0);
+    mg_mgr_poll(&mgr, 0);
+    MG_DEBUG(("event: %d", event));
+    ASSERT(event == MG_EV_ERROR);
+    ASSERT(!received_response(&s_driver_data));
   }
 
   s_driver_data.len = 0;
@@ -604,24 +729,28 @@ static void test_tcp_retransmit(void) {
   struct mg_mgr mgr;
   struct eth e;
   struct ip ip;
+  struct ipp ipp;
   struct tcp *t = (struct tcp *) (s_driver_data.buf + sizeof(e) + sizeof(ip));
   uint64_t start, now;
   bool response_recv = true;
   struct mg_tcpip_driver driver;
   struct mg_tcpip_if mif;
 
-  init_tcp_tests(&mgr, &e, &ip, &driver, &mif, fn);
+  ipp.ip4 = &ip;
+  ipp.ip6 = NULL;
 
-  init_tcp_handshake(&e, &ip, &mgr);   // starts with seq_no=1000, ackno=2
+  init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, fn);
+
+  init_tcp_handshake(&e, &ipp, &mgr);  // starts with seq_no=1000, ackno=2
 
   // packet with seq_no = 1001
-  create_tcp_simpleseg(&e, &ip, 1001, 2, TH_PUSH | TH_ACK, 2);
+  create_tcp_simpleseg(&e, &ipp, 1001, 2, TH_PUSH | TH_ACK, 2);
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT((t->flags == TH_ACK));
   ASSERT((t->ack == mg_htonl(1003)));  // OK
 
   // resend packet with seq_no = 1001 (e.g.: MIP ACK lost)
-  create_tcp_simpleseg(&e, &ip, 1001, 2, TH_PUSH | TH_ACK, 2);
+  create_tcp_simpleseg(&e, &ipp, 1001, 2, TH_PUSH | TH_ACK, 2);
   mg_mgr_poll(&mgr, 0);
   start = mg_millis();
   while (!received_response(&s_driver_data)) {
@@ -636,7 +765,7 @@ static void test_tcp_retransmit(void) {
   ASSERT((!response_recv));  // replies should not be sent for duplicate packets
 
   // packet with seq_no = 1003 got lost/delayed, send seq_no = 1005
-  create_tcp_simpleseg(&e, &ip, 1005, 2, TH_PUSH | TH_ACK, 2);
+  create_tcp_simpleseg(&e, &ipp, 1005, 2, TH_PUSH | TH_ACK, 2);
   mg_mgr_poll(&mgr, 0);
   start = mg_millis();
   while (!received_response(&s_driver_data)) {
@@ -649,13 +778,13 @@ static void test_tcp_retransmit(void) {
   ASSERT((t->ack == mg_htonl(1003)));  // dup ACK
 
   // retransmitting packet with seq_no = 1003
-  create_tcp_simpleseg(&e, &ip, 1003, 2, TH_PUSH | TH_ACK, 2);
+  create_tcp_simpleseg(&e, &ipp, 1003, 2, TH_PUSH | TH_ACK, 2);
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT((t->flags == TH_ACK));
   ASSERT((t->ack == mg_htonl(1005)));  // OK
 
   // packet with seq_no = 1005 got delayed, send FIN with seq_no = 1007
-  create_tcp_simpleseg(&e, &ip, 1007, 2, TH_FIN, 0);
+  create_tcp_simpleseg(&e, &ipp, 1007, 2, TH_FIN, 0);
   mg_mgr_poll(&mgr, 0);
   start = mg_millis();
   while (!received_response(&s_driver_data)) {
@@ -668,13 +797,13 @@ static void test_tcp_retransmit(void) {
   ASSERT((t->ack == mg_htonl(1005)));  // dup ACK
 
   // retransmitting packet with seq_no = 1005
-  create_tcp_simpleseg(&e, &ip, 1005, 2, TH_PUSH | TH_ACK, 2);
+  create_tcp_simpleseg(&e, &ipp, 1005, 2, TH_PUSH | TH_ACK, 2);
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT((t->flags == TH_ACK));
   ASSERT((t->ack == mg_htonl(1007)));  // OK
 
   // retransmitting FIN packet with seq_no = 1007
-  create_tcp_simpleseg(&e, &ip, 1007, 2, TH_FIN | TH_ACK, 0);
+  create_tcp_simpleseg(&e, &ipp, 1007, 2, TH_FIN | TH_ACK, 0);
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT((t->flags == (TH_FIN | TH_ACK)));  // check we respond with FIN ACK
   ASSERT((t->ack == mg_htonl(1008)));       // OK
@@ -687,16 +816,20 @@ static void test_frag_recv_path(void) {
   struct mg_mgr mgr;
   struct eth e;
   struct ip ip;
+  struct ipp ipp;
   struct mg_tcpip_driver driver;
   struct mg_tcpip_if mif;
 
-  init_tcp_tests(&mgr, &e, &ip, &driver, &mif, frag_recv_fn);
+  ipp.ip4 = &ip;
+  ipp.ip6 = NULL;
+
+  init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, frag_recv_fn);
 
-  init_tcp_handshake(&e, &ip, &mgr);   // starts with seq_no=1000, ackno=2
+  init_tcp_handshake(&e, &ipp, &mgr);  // starts with seq_no=1000, ackno=2
 
   // send fragmented TCP packet
   ip.frag |= IP_MORE_FRAGS_MSK;  // setting More Fragments bit to 1
-  create_tcp_simpleseg(&e, &ip, 1001, 2, TH_PUSH | TH_ACK, 1000);
+  create_tcp_simpleseg(&e, &ipp, 1001, 2, TH_PUSH | TH_ACK, 1000);
   s_sent_fragment = 1;           // "enable" fn
   mg_mgr_poll(&mgr, 0);          // call it (process fake frag IP)
   ASSERT(s_sent_fragment == 2);  // check it followed the right path
@@ -733,31 +866,34 @@ static void test_fragmentation(void) {
   test_frag_send_path();
 }
 
-
 static void test_tcp_backlog(void) {
   struct mg_mgr mgr;
   struct eth e;
   struct ip ip;
+  struct ipp ipp;
   struct tcp *t = (struct tcp *) (s_driver_data.buf + sizeof(e) + sizeof(ip));
   struct ip *i = (struct ip *) (s_driver_data.buf + sizeof(e));
   uint64_t start, now;
   struct mg_tcpip_driver driver;
   struct mg_tcpip_if mif;
-  uint16_t opts[4 / 2];                            // Send MSS, RFC-9293 3.7.1
+  uint16_t opts[4 / 2];  // Send MSS, RFC-9293 3.7.1
   struct mg_connection *c;
   unsigned int j;
 #define LOGSZ (sizeof(c->data) / sizeof(struct mg_backlog))
   uint32_t seqnos[LOGSZ];
 
-  init_tcp_tests(&mgr, &e, &ip, &driver, &mif, fn);
+  ipp.ip4 = &ip;
+  ipp.ip6 = NULL;
+
+  init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, fn);
 
   // test expired connection attempts cleanup
-  create_tcp_seg(&e, &ip, 1234, 0, TH_SYN, 1, 80, 0, NULL, 0);
-  mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
+  create_tcp_seg(&e, &ipp, 1234, 0, TH_SYN, 1, 80, 0, NULL, 0);
+  mg_mgr_poll(&mgr, 0);  // make sure we clean former stuff in buffer
   while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
   ASSERT(t->flags == (TH_SYN | TH_ACK));
   // delay ACK so conn attempt is removed from the backlog
-  s_driver_data.len = 0; // avoid Mongoose "receiving itself"
+  s_driver_data.len = 0;  // avoid Mongoose "receiving itself"
   start = mg_millis();
   do {
     mg_mgr_poll(&mgr, 0);
@@ -767,18 +903,19 @@ static void test_tcp_backlog(void) {
   c = mgr.conns;
   ASSERT(c->next == NULL);
   for (j = 0; j < LOGSZ; j++) {
-    struct mg_backlog *b = (struct mg_backlog *)(c->data) + j;
+    struct mg_backlog *b = (struct mg_backlog *) (c->data) + j;
     ASSERT(b->port == 0);
   }
   // Mongoose may have retransmitted SYN + ACK, and DHCP sent discover
   received_response(&s_driver_data);  // make sure we clean buffer
 
-  opts[0] = mg_htons(0x0204); // RFC-9293 3.2
+  opts[0] = mg_htons(0x0204);  // RFC-9293 3.2
   // fill the backlog
   for (j = 0; j < LOGSZ; j++) {
     // assign one MSS for each connection
-    opts[1] = mg_htons((uint16_t)(1010 + j));
-    create_tcp_seg(&e, &ip, 100 + j, 0, TH_SYN, (uint16_t)(j + 1), 80, 0, opts, sizeof(opts));
+    opts[1] = mg_htons((uint16_t) (1010 + j));
+    create_tcp_seg(&e, &ipp, 100 + j, 0, TH_SYN, (uint16_t) (j + 1), 80, 0,
+                   opts, sizeof(opts));
     while (!received_response(&s_driver_data) || i->proto != 6)
       mg_mgr_poll(&mgr, 0);
     ASSERT(t->flags == (TH_SYN | TH_ACK));
@@ -788,116 +925,154 @@ static void test_tcp_backlog(void) {
   // check backlog is full and MSS are there
   c = mgr.conns;
   for (j = 0; j < LOGSZ; j++) {
-    struct mg_backlog *b = (struct mg_backlog *)(c->data) + j;
+    struct mg_backlog *b = (struct mg_backlog *) (c->data) + j;
     ASSERT(b->port != 0);
-    MG_DEBUG(("SEQ: %p, MSS: %u", seqnos[j], (unsigned int)b->mss));
+    MG_DEBUG(("SEQ: %p, MSS: %u", seqnos[j], (unsigned int) b->mss));
     ASSERT(b->mss == (1010 + j));
   }
   // one more attempt, it must fail
-  opts[1] = mg_htons((uint16_t)(1010 + j));
-  create_tcp_seg(&e, &ip, 100 + j, 0, TH_SYN, (uint16_t)(j + 1), 80, 0, opts, sizeof(opts));
+  opts[1] = mg_htons((uint16_t) (1010 + j));
+  create_tcp_seg(&e, &ipp, 100 + j, 0, TH_SYN, (uint16_t) (j + 1), 80, 0, opts,
+                 sizeof(opts));
   mg_mgr_poll(&mgr, 0);
   ASSERT(!received_response(&s_driver_data) || i->proto != 6);
   // a late response for this attempt would break what follows
   // establish all connections
   for (j = 0; j < LOGSZ; j++) {
-    create_tcp_seg(&e, &ip, 100 + j + 1, seqnos[j] + 1, TH_ACK, (uint16_t)(j + 1), 80, 0, NULL, 0);
+    create_tcp_seg(&e, &ipp, 100 + j + 1, seqnos[j] + 1, TH_ACK,
+                   (uint16_t) (j + 1), 80, 0, NULL, 0);
     mg_mgr_poll(&mgr, 0);
     ASSERT(!received_response(&s_driver_data) || i->proto != 6);
   }
   // check backlog is now empty
-  c = mgr.conns; // last one is the listener
-  for (; c->next != NULL; c = c->next);
+  c = mgr.conns;  // last one is the listener
+  for (; c->next != NULL; c = c->next)
+    ;
   for (j = 0; j < LOGSZ; j++) {
-    struct mg_backlog *b = (struct mg_backlog *)(c->data) + j;
+    struct mg_backlog *b = (struct mg_backlog *) (c->data) + j;
     ASSERT(b->port == 0);
   }
-  c = mgr.conns; // first one is more recent
+  c = mgr.conns;  // first one is more recent
   // check MSS is what we sent, everything's fine
-  for (j = LOGSZ; j > 0 ; j--, c = c->next) {
-    struct connstate *s = (struct connstate *)(c + 1);
+  for (j = LOGSZ; j > 0; j--, c = c->next) {
+    struct connstate *s = (struct connstate *) (c + 1);
     ASSERT(c != NULL);
     MG_DEBUG(("MSS: %u", (unsigned int) s->dmss));
     ASSERT(s->dmss == (1010 + j - 1));
   }
-  ASSERT(c != NULL); // last one is the listener
+  ASSERT(c != NULL);  // last one is the listener
   ASSERT(c->next == NULL);
 
   s_driver_data.len = 0;
   mg_mgr_free(&mgr);
 }
 
-static void test_tcp(void) {
-  test_tcp_basics();
-  test_tcp_backlog();
-  test_tcp_retransmit();
+static void test_tcp(bool ipv6) {
+  test_tcp_basics(ipv6);
+  if(!ipv6) {
+    test_tcp_backlog();
+    test_tcp_retransmit();
+  }
 }
 
-
 static void udp_fn(struct mg_connection *c, int ev, void *ev_data) {
   if (ev == MG_EV_READ && c->recv.len == 2 && c->recv.buf[0] == 'p')
     mg_send(c, "P90", 3);
   (void) ev_data;
 }
 
-static void create_udp_dat(struct eth *e, struct ip *ip, uint16_t sport, uint16_t dport, size_t payload_len) {
+static void create_udp_dat(struct eth *e, struct ipp *ipp, uint16_t sport,
+                           uint16_t dport, size_t payload_len) {
   struct udp u;
   memset(&u, 0, sizeof(struct udp));
   u.sport = mg_htons(sport);
   u.dport = mg_htons(dport);
   u.len = mg_htons((uint16_t) (sizeof(u) + payload_len));
   memcpy(s_driver_data.buf, e, sizeof(*e));
-  ip->len = mg_htons((uint16_t) (sizeof(*ip) + sizeof(u) + payload_len));
-  memcpy(s_driver_data.buf + sizeof(*e), ip, sizeof(*ip));
-  memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip), &u, sizeof(u));
-  *(s_driver_data.buf + sizeof(*e) + sizeof(*ip) + sizeof(u)) = 'p';
-  s_driver_data.len = sizeof(*e) + sizeof(*ip) + sizeof(u) + payload_len;
-  if (s_driver_data.len < 64) s_driver_data.len = 64; // add padding when needed
+#if MG_ENABLE_IPV6
+  if (ipp->ip6 != NULL) {
+    struct ip6 *ip = ipp->ip6;
+    ip->plen = mg_htons((uint16_t) (sizeof(u) + payload_len));
+    memcpy(s_driver_data.buf + sizeof(*e), ip, sizeof(*ip));
+    memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip), &u, sizeof(u));
+    *(s_driver_data.buf + sizeof(*e) + sizeof(*ip) + sizeof(u)) = 'p';
+    s_driver_data.len = sizeof(*e) + sizeof(*ip) + sizeof(u) + payload_len;
+  } else
+#endif
+  {
+    struct ip *ip = ipp->ip4;
+    ip->len = mg_htons((uint16_t) (sizeof(*ip) + sizeof(u) + payload_len));
+    memcpy(s_driver_data.buf + sizeof(*e), ip, sizeof(*ip));
+    memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip), &u, sizeof(u));
+    *(s_driver_data.buf + sizeof(*e) + sizeof(*ip) + sizeof(u)) = 'p';
+    s_driver_data.len = sizeof(*e) + sizeof(*ip) + sizeof(u) + payload_len;
+  }
+  if (s_driver_data.len < 64) s_driver_data.len = 64;  // add padding if needed
 }
 
-static void init_udp_tests(struct mg_mgr *mgr, struct eth *e, struct ip *ip,
+static void init_udp_tests(struct mg_mgr *mgr, struct eth *e, struct ipp *ipp,
                            struct mg_tcpip_driver *driver,
                            struct mg_tcpip_if *mif, mg_event_handler_t f) {
-
-  init_tests(mgr, e, ip, driver, mif, 17); // 17 -> UDP
-  mif->ip = 1;
-  mif->state = MG_TCPIP_STATE_READY; // so mg_send() works and DHCP stops
-  mg_listen(mgr, "udp://0.0.0.0:888", f, NULL);
+  init_tests(mgr, e, ipp, driver, mif, 17);  // 17 -> UDP
+#if MG_ENABLE_IPV6
+  if (ipp->ip6 != NULL) {
+    mif->state = MG_TCPIP_STATE_READY;  // so DHCP stops
+    mif->state6 = MG_TCPIP_STATE_READY;  // so mg_send() works and RS stops
+    mg_listen(mgr, "udp://[::]:888", f, NULL);
+  } else
+#endif
+  {
+    mif->state = MG_TCPIP_STATE_READY;  // so mg_send() works and DHCP stops
+    mg_listen(mgr, "udp://0.0.0.0:888", f, NULL);
+  }
   mg_mgr_poll(mgr, 0);
 }
 
-static void test_udp(void) {
+static void test_udp(bool ipv6) {
   struct mg_mgr mgr;
   struct eth e;
   struct ip ip;
-  struct udp *u = (struct udp *) (s_driver_data.buf + sizeof(e) + sizeof(ip));
-  // struct ip *i = (struct ip *) (s_driver_data.buf + sizeof(e));
+  struct ip6 ip6;
+  struct ipp ipp;
+  struct udp *u = (struct udp *) (s_driver_data.buf + sizeof(e) + (!ipv6 ? sizeof(ip) : sizeof(ip6)));
+  struct ip *i = (struct ip *) (s_driver_data.buf + sizeof(e));
+  struct ip6 *i6 = (struct ip6 *) (s_driver_data.buf + sizeof(e));
   struct mg_tcpip_driver driver;
   struct mg_tcpip_if mif;
 
-  init_udp_tests(&mgr, &e, &ip, &driver, &mif, udp_fn);
+  ipp.ip4 = !ipv6 ? &ip : NULL;
+  ipp.ip6 = ipv6 ? &ip6 : NULL;
+
+  init_udp_tests(&mgr, &e, &ipp, &driver, &mif, udp_fn);
   received_response(&s_driver_data);
   s_driver_data.len = 0;
 
-  // send data to a non-open port, expect no response (we don't send Destination Unreachable)
-  create_udp_dat(&e, &ip, 1, 800, 2);
+  // send data to a non-open port, expect no response (we don't send Destination
+  // Unreachable)
+  create_udp_dat(&e, &ipp, 1, 800, 2);
   mg_mgr_poll(&mgr, 0);
   ASSERT(!received_response(&s_driver_data));
 
   // send data to an open port, expect response
-  create_udp_dat(&e, &ip, 1, 888, 2);
+  create_udp_dat(&e, &ipp, 1, 888, 2);
   mg_mgr_poll(&mgr, 0);
   ASSERT(received_response(&s_driver_data));
   ASSERT(u->sport == mg_htons(888));
-  ASSERT(u->len == mg_htons(sizeof(u) + 3));
-  ASSERT(*((char *)(u + 1)) == 'P');
+  ASSERT(u->len == mg_htons(sizeof(*u) + 3));
+  ASSERT(*((char *) (u + 1)) == 'P');
+  if (ipv6) {
+    ASSERT(i6->src[0] == 1 && i6->dst[0] == 2);
+  } else {
+    ASSERT(i->src == 1 && i->dst == 2);
+  }
 
   s_driver_data.len = 0;
   mg_mgr_free(&mgr);
 }
 
 
-#define DASHBOARD(x)  printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false":"true");
+#define DASHBOARD(x) \
+  printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true");
 
 int main(void) {
   s_error = false;
@@ -913,17 +1088,32 @@ int main(void) {
   DASHBOARD("poll");
 
   s_error = false;
-  test_tcp();
+  test_tcp(false);
   DASHBOARD("tcp");
 
   s_error = false;
-  test_udp();
+  test_udp(false);
   DASHBOARD("udp");
 
+#if MG_ENABLE_IPV6
+  s_error = false;
+  test_state6change();
+  DASHBOARD("state6change");
+
+  s_error = false;
+  test_tcp(true);
+  DASHBOARD("tcp_ipv6");
+  
+  s_error = false;
+  test_udp(true);
+  DASHBOARD("udp_ipv6");
+
+#endif
+
   s_error = false;
   test_fragmentation();
-  printf("HEALTH_DASHBOARD\t\"ipfrag\": %s\n", s_error ? "false":"true");
- // last entry with no comma
+  printf("HEALTH_DASHBOARD\t\"ipfrag\": %s\n", s_error ? "false" : "true");
 // last entry with no comma
 
 #ifdef NO_ABORT
   if (s_abort != 0) return EXIT_FAILURE;
index 49d38674e9083d420f8405b16fe70df1a460d84d..54e3ef9133a0128c38b94e9ba307aa63f6b86e85 100644 (file)
@@ -2617,6 +2617,8 @@ static void test_util(void) {
   const char *e;
   char buf[100], *s;
   struct mg_addr a;
+  uint64_t ipv3;
+  uint8_t d64[8] = {0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef};
   uint32_t ipv4;
   uint16_t port;
   struct mg_str data;
@@ -2651,6 +2653,20 @@ static void test_util(void) {
          ((uint8_t *) &ipv4)[2] == 0x45);
   ASSERT(MG_LOAD_BE24(&ipv4) == 0xef2345);
 
+  memcpy(&ipv3, d64, sizeof(ipv3));
+#if defined(_MSC_VER) && _MSC_VER < 1700
+  // VC98 doesn't suppport LL suffix
+#else
+  ASSERT(ipv3 == mg_htonll(0x1234567890abcdefLL));
+  ASSERT(mg_ntohll(ipv3) == 0x1234567890abcdefLL);
+#endif
+  MG_STORE_BE64(&ipv3, 0x5678abcd12349ef0);
+  ASSERT(((uint8_t *) &ipv3)[0] == 0x56 && ((uint8_t *) &ipv3)[1] == 0x78 &&
+         ((uint8_t *) &ipv3)[2] == 0xab && ((uint8_t *) &ipv3)[3] == 0xcd &&
+         ((uint8_t *) &ipv3)[4] == 0x12 && ((uint8_t *) &ipv3)[5] == 0x34 &&
+         ((uint8_t *) &ipv3)[6] == 0x9e && ((uint8_t *) &ipv3)[7] == 0xf0);
+  ASSERT(MG_LOAD_BE64(&ipv3) == 0x5678abcd12349ef0);
+
   memset(a.ip, 0xa5, sizeof(a.ip));
   ASSERT(mg_aton(mg_str("1:2:3:4:5:6:7:8"), &a) == true);
   ASSERT(a.is_ip6 == true);