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 {
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
#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
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]);
}
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);
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));
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) {
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};
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;
}
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));
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));
}
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;
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);
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");
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;
{
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);
}
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) {
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);
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) {
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) {
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
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;
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 &&
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);
}
// 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
// - 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
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
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;
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,
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,
}
}
+#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) {
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
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
}
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,
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 &&
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)) {
}
}
+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);
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);
}
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 =
// 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");
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");
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);
}
}
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
}
}
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) {
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);
+ }
}
}
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;
}
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);
}
}
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);
}
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
}
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;
#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
#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) \
((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; \
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))
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
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
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);
#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_; \
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)); \
#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
#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_; \
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)); \
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 {
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
#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
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]);
}
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);
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));
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) {
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};
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;
}
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));
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));
}
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;
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);
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");
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;
{
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);
}
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) {
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);
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) {
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) {
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
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;
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 &&
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);
}
// 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
// - 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
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
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;
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,
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,
}
}
+#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) {
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
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
}
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,
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 &&
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)) {
}
}
+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);
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);
}
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 =
// 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");
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");
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);
}
}
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
}
}
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) {
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);
+ }
}
}
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;
}
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);
}
}
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);
}
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
}
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
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
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);
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;
#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) \
((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; \
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))
} \
} 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,
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);
}
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;
}
(void) c, (void) ev_data;
}
-
static void test_poll(void) {
int count = 0, i;
struct mg_tcpip_if mif;
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;
}
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;
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));
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);
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 !
}
// 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));
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
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
// 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)) {
}
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
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
// 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
// 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)) {
}
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
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;
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)) {
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)) {
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)) {
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
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
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);
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));
// 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;
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;
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;
((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);