dep-mongoose/src/net_builtin.c
Sergey Lyubka afc84b7c92
Merge pull request #3664 from cesanta/drainudp
make builtin UDP honor c->is_draining, as socket does
2026-08-23 12:54:16 +01:00

2355 lines
90 KiB
C

#include "net_builtin.h"
#include "profile.h"
#if MG_ENABLE_TCPIP
#define MG_EPHEMERAL_PORT_BASE 32768
#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
#ifndef MG_TCPIP_KEEPALIVE_MS
#define MG_TCPIP_KEEPALIVE_MS 45000 // TCP keep-alive period, ms
#endif
#define MG_TCPIP_ACK_MS 150 // Timeout for ACKing
#define MG_TCPIP_ARP_MS 100 // Timeout for ARP response
#define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment
#define MG_TCPIP_FIN_MS 1000 // Timeout for closing connection
#ifndef MG_TCPIP_WIN
#define MG_TCPIP_WIN 6000 // TCP window size
#endif
struct connstate {
uint32_t seq, ack; // TCP seq/ack counters
uint64_t timer; // TCP timer (see 'ttype' below)
uint32_t acked; // Last ACK-ed number
size_t unacked; // Not acked bytes
uint32_t maxseq; // Max send seq (ack + window)
uint16_t win; // destination current window size
uint16_t dmss; // destination MSS (from TCP opts)
uint8_t mac[sizeof(struct mg_l2addr)]; // Peer hw address
uint8_t ttype; // Timer type:
#define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long, send keepalive
#define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it soon
#define MIP_TTYPE_ARP 2 // ARP resolve sent, waiting for response
#define MIP_TTYPE_SYN 3 // SYN sent, waiting for response
#define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating the connection
uint8_t tmiss; // Number of keep-alive misses
bool fin_rcvd; // We have received FIN from the peer
bool twclosure; // 3-way closure done
};
#if defined(__DCC__)
#pragma pack(1)
#else
#pragma pack(push, 1)
#endif
struct ip {
uint8_t ver; // Version
uint8_t tos; // DS field
uint16_t len; // Datagram length
uint16_t id; // Unused
uint16_t frag; // Fragmentation
#define IP_FRAG_OFFSET_MSK 0x1fff
#define IP_MORE_FRAGS_MSK 0x2000
uint8_t ttl; // Time to live
uint8_t proto; // Upper level protocol
uint16_t csum; // Checksum
uint32_t src; // Source IP
uint32_t dst; // Destination IP
};
struct ip6 {
uint8_t ver; // Version
uint8_t label[3]; // Flow label
uint16_t plen; // Payload length
uint8_t next; // Upper level protocol
uint8_t hops; // Hop limit
uint64_t src[2]; // Source IP
uint64_t dst[2]; // Destination IP
};
struct icmp {
uint8_t type;
uint8_t code;
uint16_t csum;
};
struct icmp6 {
uint8_t type;
uint8_t code;
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
uint8_t hlen; // Length of hardware address
uint8_t plen; // Length of protocol address
uint16_t op; // Operation
uint8_t sha[6]; // Sender hardware address
uint32_t spa; // Sender protocol address
uint8_t tha[6]; // Target hardware address
uint32_t tpa; // Target protocol address
};
struct tcp {
uint16_t sport; // Source port
uint16_t dport; // Destination port
uint32_t seq; // Sequence number
uint32_t ack; // Acknowledgement number
uint8_t off; // Data offset
uint8_t flags; // TCP flags
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_STDFLAGS 0x3f
// #define TH_ECE 0x40 // not part of TCP but RFC-3168 (ECN)
// #define TH_CWR 0x80
uint16_t win; // Window
uint16_t csum; // Checksum
uint16_t urp; // Urgent pointer
};
struct udp {
uint16_t sport; // Source port
uint16_t dport; // Destination port
uint16_t len; // UDP length
uint16_t csum; // UDP checksum
};
struct dhcp {
uint8_t op, htype, hlen, hops;
uint32_t xid;
uint16_t secs, flags;
uint32_t ciaddr, yiaddr, siaddr, giaddr;
uint8_t hwaddr[208];
uint32_t magic;
uint8_t options[30 + sizeof(((struct mg_tcpip_if *) 0)->dhcp_name)];
};
struct dhcp6 {
union {
uint8_t type;
uint32_t xid;
};
uint8_t options[30 + sizeof(((struct mg_tcpip_if *) 0)->dhcp_name)];
};
struct pseudoip {
uint32_t src; // Source IP
uint32_t dst; // Destination IP
uint8_t zero;
uint8_t proto; // Upper level protocol
uint16_t len; // Datagram length
};
struct pseudoip6 {
uint64_t src[2]; // Source IP
uint64_t dst[2]; // Destination IP
uint32_t plen; // Payload length
uint8_t zero[3];
uint8_t next; // Upper level protocol
};
#if defined(__DCC__)
#pragma pack(0)
#else
#pragma pack(pop)
#endif
// 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
uint8_t *l2; // Ethernet, PPP [, etc] frame data
struct arp *arp;
struct ip *ip;
struct ip6 *ip6;
struct icmp *icmp;
struct icmp6 *icmp6;
struct tcp *tcp;
struct udp *udp;
struct dhcp *dhcp;
struct dhcp6 *dhcp6;
};
// L2 API
void mg_l2_init(struct mg_tcpip_if *ifp);
uint8_t *mg_l2_header(struct mg_tcpip_if *ifp, enum mg_l2proto proto,
uint8_t *src, uint8_t *dst, uint8_t *frame);
size_t mg_l2_trailer(struct mg_tcpip_if *ifp, size_t len, uint8_t *cur);
bool mg_l2_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
struct mg_str *pay, struct mg_str *raw);
uint8_t *mg_l2_getaddr(struct mg_tcpip_if *ifp, uint8_t *frame);
uint8_t *mg_l2_mapip(enum mg_l2type type, enum mg_l2addrtype addrtype,
struct mg_addr *ip);
#if MG_ENABLE_IPV6
bool mg_l2_genip6(enum mg_l2type type, uint64_t *ip6, uint8_t prefix_len,
uint8_t *addr);
bool mg_l2_ip6get(enum mg_l2type type, uint8_t *addr, uint8_t *opts,
uint8_t len);
uint8_t mg_l2_ip6put(enum mg_l2type type, uint8_t *addr, uint8_t *opts);
#endif
bool mg_l2_poll(struct mg_tcpip_if *ifp, bool expired_1000ms);
static void mg_tcpip_call(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
#if 0 && MG_ENABLE_PROFILE
const char *names[] = {"TCPIP_EV_STATE_CHANGE", "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_STATE6_CHANGE",
"TCPIP_EV_USER"};
if (ev != MG_TCPIP_EV_TIMER_1S && ev < (int) (sizeof(names) / sizeof(names[0]))) {
MG_PROF_ADD(ifp, names[ev]); // TODO(): call MG_PROF_DUMP() MG_PROF_FREE()
}
#endif
// Fire protocol handler first, user handler second. See #2559
if (ifp->pfn != NULL) ifp->pfn(ifp, ev, ev_data);
if (ifp->fn != NULL) ifp->fn(ifp, ev, ev_data);
}
static void send_syn(struct mg_connection *c);
static void mkpay(struct pkt *pkt, void *p) {
pkt->pay =
mg_str_n((char *) p, (size_t) (&pkt->pay.buf[pkt->pay.len] - (char *) p));
}
// NOTE(): DOES NOT handle reentries after odd length, use last
static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
size_t i;
const uint8_t *p = (const uint8_t *) buf;
for (i = 0; i < len; i++) sum += i & 1 ? p[i] : ((uint32_t) p[i]) << 8;
return sum;
}
static uint16_t csumfin(uint32_t sum) {
while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
return mg_htons((uint16_t) ((uint16_t) ~sum & 0xffff));
}
static uint16_t ipcsum(const void *buf, size_t len) {
uint32_t sum = csumup(0, buf, len);
return csumfin(sum);
}
static bool ipcsum_ok(const void *d) {
struct ip *ip = (struct ip *) d;
return (ipcsum(d, (ip->ver & 0x0F) * 4) == 0);
}
static bool icmpcsum_ok(const void *d, size_t len) {
return (ipcsum(d, len) == 0);
}
static uint16_t pcsum(void *d, void *p, size_t plen) {
uint32_t sum;
struct ip *ip = (struct ip *) d;
#if defined(__DCC__)
volatile /* Makes PPC & Diab4.3 happy */
#endif
struct pseudoip pip;
pip.src = ip->src;
pip.dst = ip->dst;
pip.zero = 0;
pip.proto = ip->proto;
pip.len = mg_htons((uint16_t) plen);
sum = csumup(0, &pip, sizeof(pip)); // even length
sum = csumup(sum, p, plen); // possibly odd length: last
return csumfin(sum);
}
static bool udpcsum_ok(void *d, void *u) {
struct udp *udp = (struct udp *) u;
if (udp->csum == 0) return true;
if (udp->csum == 0xFFFF) udp->csum = 0;
return (pcsum(d, u, (size_t) mg_ntohs(udp->len)) == 0);
}
static bool tcpcsum_ok(void *d, void *t) {
struct ip *ip = (struct ip *) d;
return (pcsum(d, t, (size_t) (mg_ntohs(ip->len) - (ip->ver & 0x0F) * 4)) ==
0);
}
#if MG_ENABLE_IPV6
static uint16_t p6csum(void *d, void *p, size_t plen) {
uint32_t sum;
struct ip6 *ip6 = (struct ip6 *) d;
#if defined(__DCC__)
volatile /* Makes PPC & Diab4.3 happy */
#endif
struct pseudoip6 pip6;
pip6.src[0] = ip6->src[0], pip6.src[1] = ip6->src[1];
pip6.dst[0] = ip6->dst[0], pip6.dst[1] = ip6->dst[1];
pip6.zero[0] = 0, pip6.zero[1] = 0, pip6.zero[2] = 0;
pip6.plen = mg_htonl((uint32_t) plen);
pip6.next = ip6->next;
sum = csumup(0, &pip6, sizeof(pip6)); // even length
sum = csumup(sum, p, plen); // possibly odd length: last
return csumfin(sum);
}
static bool udp6csum_ok(void *d, void *u) {
struct udp *udp = (struct udp *) u;
if (udp->csum == 0) return false; // mandatory in IPv6
if (udp->csum == 0xFFFF) udp->csum = 0;
return (p6csum(d, u, (size_t) mg_ntohs(udp->len)) == 0);
}
static bool tcp6csum_ok(void *d, void *t) {
struct ip6 *ip6 = (struct ip6 *) d;
return (p6csum(d, t, (size_t) mg_ntohs(ip6->plen)) == 0);
}
static bool icmp6csum_ok(void *d, void *i) {
struct ip6 *ip6 = (struct ip6 *) d;
return (p6csum(d, i, (size_t) mg_ntohs(ip6->plen)) == 0);
}
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 const struct mg_addr ip6_allrouters = {
{{0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02}}, 0, 0, true};
static const struct mg_addr ip6_allnodes = {
{{0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}}, 0, 0, true};
#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);
unsigned n = type == MIP_TTYPE_ACK ? MG_TCPIP_ACK_MS
: type == MIP_TTYPE_ARP ? MG_TCPIP_ARP_MS
: type == MIP_TTYPE_SYN ? MG_TCPIP_SYN_MS
: type == MIP_TTYPE_FIN ? MG_TCPIP_FIN_MS
: MG_TCPIP_KEEPALIVE_MS;
if (s->ttype == MIP_TTYPE_FIN) return; // skip if 3-way closing
s->timer = ifp->now + n;
s->ttype = type;
MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
}
static size_t driver_output(struct mg_tcpip_if *ifp, size_t len) {
size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp);
if (n == len) ifp->nsent++;
return n;
}
// RFC826, ARP assumes Ethernet MAC addresses
void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac) {
uint8_t *l2p = (uint8_t *) ifp->tx.buf;
struct arp *arp = (struct arp *) mg_l2_header(
ifp, MG_TCPIP_L2PROTO_ARP, ifp->mac,
mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL), l2p);
memset(arp, 0, sizeof(*arp));
arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
arp->plen = 4;
arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
if (mac != NULL) memcpy(arp->tha, mac, sizeof(arp->tha));
driver_output(ifp, mg_l2_trailer(ifp, sizeof(*arp), (uint8_t *) (arp + 1)));
}
static void onstatechange(struct mg_tcpip_if *ifp) {
if (ifp->state == MG_TCPIP_STATE_IP &&
(ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE))
ifp->state = MG_TCPIP_STATE_READY;
if (ifp->state == MG_TCPIP_STATE_READY) {
MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip));
MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw));
if (ifp->l2type == MG_TCPIP_L2_ETH ||
ifp->l2type == MG_TCPIP_L2_PPPoE) // TODO(): print other l2
MG_INFO((" MAC: %M", mg_print_mac, ifp->mac));
if (ifp->l2type == MG_TCPIP_L2_ETH)
mg_tcpip_arp_request(ifp, ifp->ip, ifp->mac); // gratuitous ARP annc
if (ifp->is_ip_changed) {
struct mg_connection *c;
for (c = ifp->mgr->conns; c != NULL; c = c->next) {
if (!c->is_listening && !c->is_udp) c->is_closing = 1;
}
ifp->is_ip_changed = false;
}
} else if (ifp->state == MG_TCPIP_STATE_IP) {
if (ifp->gw != 0 && ifp->l2type == MG_TCPIP_L2_ETH)
mg_tcpip_arp_request(ifp, ifp->gw, NULL); // unsolicited GW ARP request
} else if (ifp->state == MG_TCPIP_STATE_UP) {
srand((unsigned int) mg_millis());
} else if (ifp->state == MG_TCPIP_STATE_DOWN) {
MG_ERROR(("Link down"));
}
mg_tcpip_call(ifp, MG_TCPIP_EV_STATE_CHANGE, &ifp->state);
}
static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint8_t proto,
uint8_t dscp, 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
uint8_t *l2p = (uint8_t *) ifp->tx.buf;
struct ip *ip = (struct ip *) mg_l2_header(ifp, MG_TCPIP_L2PROTO_IPV4,
ifp->mac, l2_dst, l2p);
memset(ip, 0, sizeof(*ip));
ip->ver = 0x45; // Version 4, header length 5 words
ip->tos = (uint8_t) (dscp << 2);
ip->frag = mg_htons(0x4000); // Don't fragment
ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen));
ip->ttl = 64;
ip->proto = proto;
ip->src = ip_src;
ip->dst = ip_dst;
ip->csum = ipcsum(ip, sizeof(*ip));
return ip;
}
#if MG_ENABLE_IPV6
static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
uint8_t next, uint8_t dscp, uint64_t *ip_src,
uint64_t *ip_dst, size_t plen);
#endif
static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
struct mg_addr *ip_src, struct mg_addr *ip_dst, uint8_t dscp,
const void *buf, size_t len) {
uint8_t *l3p;
size_t l2_len;
struct ip *ip = NULL;
struct udp *udp;
#if MG_ENABLE_IPV6
struct ip6 *ip6 = NULL;
if (ip_dst->is_ip6) {
ip6 = tx_ip6(ifp, l2_dst, 17, dscp, ip_src->addr.ip6, ip_dst->addr.ip6,
len + sizeof(struct udp));
udp = (struct udp *) (ip6 + 1);
l2_len = sizeof(*ip6) + sizeof(*udp) + len;
l3p = (uint8_t *) ip6;
} else
#endif
{
ip = tx_ip(ifp, l2_dst, 17, dscp, ip_src->addr.ip4, ip_dst->addr.ip4,
len + sizeof(struct udp));
udp = (struct udp *) (ip + 1);
l2_len = sizeof(*ip) + sizeof(*udp) + len;
l3p = (uint8_t *) ip;
}
udp->sport = ip_src->port;
udp->dport = ip_dst->port;
udp->len = mg_htons((uint16_t) (sizeof(*udp) + len));
udp->csum = 0;
memmove(udp + 1, buf, len);
#if MG_ENABLE_IPV6
if (ip_dst->is_ip6) {
udp->csum = p6csum(ip6, udp, sizeof(*udp) + len);
} else
#endif
{
udp->csum = pcsum(ip, udp, sizeof(*udp) + len);
}
l2_len = mg_l2_trailer(ifp, l2_len, l3p + l2_len);
return (driver_output(ifp, l2_len) == l2_len);
}
static bool tx_udp4(struct mg_tcpip_if *ifp, uint8_t *l2_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.addr.ip4 = ip_src;
ips.port = sport;
memset(&ipd, 0, sizeof(ipd));
ipd.addr.ip4 = ip_dst;
ipd.port = dport;
return tx_udp(ifp, l2_dst, &ips, &ipd, 0, buf, len);
}
static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint32_t ip_src,
uint32_t ip_dst, uint8_t *opts, size_t optslen,
bool ciaddr) {
// https://datatracker.ietf.org/doc/html/rfc2132#section-9.6
// NOTE(): assumes Ethernet: htype=1 hlen=6, copy 6 bytes
struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
dhcp.magic = mg_htonl(0x63825363);
memcpy(&dhcp.hwaddr, ifp->mac, 6);
memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
memcpy(&dhcp.options, opts, optslen);
if (ciaddr) dhcp.ciaddr = ip_src;
tx_udp4(ifp, l2_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp,
sizeof(dhcp));
}
// RFC-2131 #4.3.6, #4.4.1; RFC-2132 #9.8
static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp, uint32_t ip_req,
uint32_t ip_srv) {
uint8_t extra = (uint8_t) ((ifp->enable_req_dns ? 1 : 0) +
(ifp->enable_req_sntp ? 1 : 0));
size_t len = strlen(ifp->dhcp_name);
size_t olen = 21 + len + extra + 2 + 1; // Total length of options
#define OPTS_MAXLEN (21 + sizeof(ifp->dhcp_name) + 2 + 2 + 1)
uint8_t opts[OPTS_MAXLEN]; // Allocate options (max size possible)
uint8_t *p = opts;
assert(olen <= sizeof(opts));
memset(opts, 0, sizeof(opts));
*p++ = 53, *p++ = 1, *p++ = 3; // Type: DHCP request
*p++ = 54, *p++ = 4, memcpy(p, &ip_srv, 4), p += 4; // DHCP server ID
*p++ = 50, *p++ = 4, memcpy(p, &ip_req, 4), p += 4; // Requested IP
*p++ = 12, *p++ = (uint8_t) (len & 255); // DHCP host
memcpy(p, ifp->dhcp_name, len), p += len; // name
*p++ = 55, *p++ = 2 + extra, *p++ = 1, *p++ = 3; // GW, MASK
if (ifp->enable_req_dns) *p++ = 6; // DNS
if (ifp->enable_req_sntp) *p++ = 42; // SNTP
*p++ = 255; // End of options
// assert((size_t) (p - opts) < olen);
tx_dhcp(ifp, mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL), 0,
0xffffffff, opts, olen, 0);
MG_DEBUG(("DHCP req sent"));
}
// RFC-2131 #4.3.6, #4.4.5 (renewing: unicast, rebinding: bcast)
static void tx_dhcp_request_re(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
uint32_t ip_src, uint32_t ip_dst) {
uint8_t opts[] = {
53, 1, 3, // Type: DHCP request
255 // End of options
};
tx_dhcp(ifp, l2_dst, ip_src, ip_dst, opts, sizeof(opts), true);
MG_DEBUG(("DHCP req sent"));
}
static void tx_dhcp_discover(struct mg_tcpip_if *ifp) {
uint8_t opts[] = {
53, 1, 1, // Type: DHCP discover
55, 2, 1, 3, // Parameters: ip, mask
255 // End of options
};
tx_dhcp(ifp, mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL), 0,
0xffffffff, opts, sizeof(opts), false);
MG_DEBUG(("DHCP discover sent. Our MAC: %M", mg_print_mac, ifp->mac));
}
static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt,
bool lsn) {
struct mg_connection *c = NULL;
for (c = mgr->conns; c != NULL; c = c->next) {
if (c->is_arplooking && pkt->arp && pkt->arp->spa == c->rem.addr.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.addr.ip6)) break;
}
#endif
if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport &&
!(c->loc.is_ip6 ^ (pkt->ip6 != NULL))) // IP or IPv6 to same dest
break;
if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
((lsn && c->is_listening && !(c->loc.is_ip6 ^ (pkt->ip6 != NULL))) ||
(!lsn && !c->is_listening && c->rem.port == pkt->tcp->sport &&
((!c->loc.is_ip6 && c->rem.addr.ip4 == pkt->ip->src)
#if MG_ENABLE_IPV6
|| (c->loc.is_ip6 && MG_IP6MATCH(c->rem.addr.ip6, pkt->ip6->src))
#endif
)))) // validate addr for established (not listening) conns
break;
}
return c;
}
static void l2addr_resolved(struct mg_connection *c);
static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem,
bool is_udp, struct pkt *pkt);
// RFC826, ARP assumes Ethernet MAC addresses
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_VERBOSE(("ARP req from %M", mg_print_ip4, &pkt->arp->spa));
uint8_t *l2p = (uint8_t *) ifp->tx.buf;
struct arp *arp = (struct arp *) mg_l2_header(
ifp, MG_TCPIP_L2PROTO_ARP, ifp->mac, mg_l2_getaddr(ifp, pkt->l2), l2p);
*arp = *pkt->arp;
arp->op = mg_htons(2);
memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
arp->tpa = pkt->arp->spa;
arp->spa = ifp->ip;
MG_DEBUG(("ARP: tell %M we're %M", mg_print_ip4, &arp->tpa, mg_print_mac,
ifp->mac));
driver_output(ifp, mg_l2_trailer(ifp, sizeof(*arp), (uint8_t *) (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));
ifp->gw_ready = true;
if (ifp->state == MG_TCPIP_STATE_IP) {
ifp->state = MG_TCPIP_STATE_READY;
onstatechange(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, pkt->arp->sha, sizeof(s->mac));
MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4,
&c->rem.addr.ip4, mg_print_mac, s->mac));
c->is_arplooking = 0;
l2addr_resolved(c);
}
}
}
}
static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
size_t plen = pkt->pay.len;
if (!icmpcsum_ok(pkt->icmp, sizeof(struct icmp) + plen)) return;
if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) {
size_t l2_max_overhead = ifp->framesize - ifp->l2mtu;
size_t hlen = sizeof(struct ip) + sizeof(struct icmp);
size_t room = ifp->tx.len - hlen - l2_max_overhead;
uint8_t *l2addr;
struct ip *ip;
struct icmp *icmp;
struct mg_addr ips;
ips.addr.ip4 = pkt->ip->src;
ips.is_ip6 = false;
if ((l2addr = get_return_l2addr(ifp, &ips, false, pkt)) == NULL)
return; // safety net for lousy networks
if (plen > room) plen = room;
ip = tx_ip(ifp, l2addr, 1, 0, ifp->ip, pkt->ip->src, sizeof(*icmp) + plen);
icmp = (struct icmp *) (ip + 1);
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);
driver_output(
ifp, mg_l2_trailer(ifp, hlen + plen, (uint8_t *) ip + hlen + plen));
}
}
static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip);
static bool dhcp_opt_len_ok(uint8_t len, uint8_t *p, uint8_t *end) {
return (len >= 4 && (len & 3) == 0 && p + 6 < end);
}
static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
uint32_t ip = 0, gw = 0, mask = 0, lease = 0, dns = 0, sntp = 0, owner = 0;
uint8_t msgtype = 0, state = ifp->state;
// perform size check first, then access fields
uint8_t *p = (uint8_t *) pkt->pay.buf,
*end = (uint8_t *) &pkt->pay.buf[pkt->pay.len];
// min header length checked at payload calculation, options are optional
if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return;
while (p < end && p[0] != 255) { // RFC-2132 9
if (p[0] == 0) { p++; continue; } // Pad
if ((size_t) (end - p) < 2 || (size_t) (end - p) < 2U + p[1]) break;
if (p[0] == 1) { // Mask, 3.3
if (p[1] != 4) break;
memcpy(&mask, p + 2, sizeof(mask));
} else if (p[0] == 3) { // GW, 3.5
if (!dhcp_opt_len_ok(p[1], p, end)) break;
memcpy(&gw, p + 2, sizeof(gw));
ip = pkt->dhcp->yiaddr;
} else if (ifp->enable_req_dns && p[0] == 6) { // DNS, 3.8
if (!dhcp_opt_len_ok(p[1], p, end)) break;
memcpy(&dns, p + 2, sizeof(dns));
} else if (ifp->enable_req_sntp && p[0] == 42) { // SNTP, 8.3
if (!dhcp_opt_len_ok(p[1], p, end)) break;
memcpy(&sntp, p + 2, sizeof(sntp));
} else if (p[0] == 51) { // Lease
if (p[1] != 4) break;
memcpy(&lease, p + 2, sizeof(lease));
lease = mg_ntohl(lease);
} else if (p[0] == 53) { // Msg Type
if (p[1] != 1) break;
msgtype = p[2];
} else if (p[0] == 54) { // Server id 9.7
if (p[1] != 4) break;
memcpy(&owner, p + 2, sizeof(sntp)); // This is the lease owner
}
p += p[1] + 2;
}
// Process message type, RFC-1533 (9.4); RFC-2131 (3.1, 4)
if (msgtype == 6 && ifp->ip == ip) { // DHCPNACK, release IP
ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;
} else if (msgtype == 2 && ifp->state == MG_TCPIP_STATE_UP && ip && gw &&
lease) { // DHCPOFFER
// select a server (2131 4.4.1, 2132 9.7): lease owner takes precedence,
// otherwise use siaddr (fallback to IP source addr on foul play).
// This is broadcast, otherwise siaddr would be the destination.
tx_dhcp_request_sel(ifp, ip,
owner ? owner
: pkt->dhcp->siaddr ? pkt->dhcp->siaddr
: pkt->ip->src);
ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state
} else if (msgtype == 5) { // DHCPACK
if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP
uint64_t rand;
ifp->lease_expire = ifp->now + (uint64_t) lease * 1000;
MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000));
// assume DHCP server = router until ARP resolves
memcpy(ifp->gwmac, mg_l2_getaddr(ifp, pkt->l2), sizeof(ifp->gwmac));
ifp->gw_ready = true; // NOTE(): actual gw ARP won't retry now
if (ifp->ip != ip) ifp->is_ip_changed = true;
ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
ifp->state = MG_TCPIP_STATE_IP; // BOUND state
mg_random(&rand, sizeof(rand));
srand((unsigned int) (rand + mg_millis()));
if (ifp->enable_req_dns && dns != 0) {
setdns4(ifp, &dns);
mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_DNS, &dns);
}
if (ifp->enable_req_sntp && sntp != 0)
mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_SNTP, &sntp);
} else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip) { // renew
ifp->lease_expire = ifp->now + (uint64_t) lease * 1000;
MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000));
} // TODO(): accept provided T1/T2 and store server IP for renewal (4.4)
}
if (ifp->state != state) onstatechange(ifp);
}
// Simple DHCP server that assigns a next IP address: ifp->ip + 1
static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
uint8_t *mac;
uint8_t op = 0, *p = (uint8_t *) pkt->pay.buf,
*end = (uint8_t *) &pkt->pay.buf[pkt->pay.len];
// NOTE(): assumes Ethernet: htype=1 hlen=6, copy 6 bytes
struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
// min header length checked at payload calculation, options are optional
res.yiaddr = ifp->ip;
((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1
while (p < end && p[0] != 255) { // Parse options
if (p[0] == 0) { p++; continue; } // Pad
if ((size_t) (end - p) < 2 || (size_t) (end - p) < 2U + p[1]) break;
if (p[0] == 53) { // Message type
if (p[1] != 1) break;
op = p[2];
}
p += p[1] + 2;
}
if (op == 1 || op == 3) { // DHCP Discover or DHCP Request
uint8_t msg = op == 1 ? 2 : 5; // Message type: DHCP OFFER or DHCP ACK
uint8_t opts[] = {
53, 1, 0, // Message type
1, 4, 0, 0, 0, 0, // Subnet mask
54, 4, 0, 0, 0, 0, // Server ID
12, 3, 'm', 'i', 'p', // Host name: "mip"
51, 4, 255, 255, 255, 255, // Lease time
255 // End of options
};
opts[2] = msg;
memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
memcpy(&res.options, opts, sizeof(opts));
res.magic = pkt->dhcp->magic;
res.xid = pkt->dhcp->xid;
mac = mg_l2_getaddr(ifp, pkt->l2);
if (ifp->enable_get_gateway) {
ifp->gw = res.yiaddr; // set gw IP, best-effort gwmac as DHCP server's
memcpy(ifp->gwmac, mac, sizeof(ifp->gwmac));
}
tx_udp4(ifp, mac, 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 *l2_dst,
uint8_t next, uint8_t dscp, 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
uint8_t *l2p = (uint8_t *) ifp->tx.buf;
struct ip6 *ip6 = (struct ip6 *) mg_l2_header(ifp, MG_TCPIP_L2PROTO_IPV6,
ifp->mac, l2_dst, l2p);
memset(ip6, 0, sizeof(*ip6));
ip6->ver = (uint8_t) (0x60 | (dscp >> 2)); // Version 6, traffic class
ip6->label[0] = (uint8_t) (dscp << 6);
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 *l2_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;
ip6 = tx_ip6(ifp, l2_dst, 58, 0, 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
icmp6->csum = p6csum(ip6, icmp6, sizeof(*icmp6) + len);
driver_output(ifp,
mg_l2_trailer(ifp, sizeof(*ip6) + sizeof(*icmp6) + len,
(uint8_t *) (ip6 + 1) + sizeof(*icmp6) + len));
}
// Neighbor Discovery Protocol, RFC-4861
// Neighbor Advertisement, 4.4
static void tx_ndp_na(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
uint64_t *ip_src, uint64_t *ip_dst, bool solicited,
uint8_t *l2) {
uint8_t data[20 + 16]; // NOTE(): optional len upto 2 hw addr
memset(data, 0, sizeof(data));
data[0] = solicited ? 0x60 : 0x20; // O + S
memcpy(data + 4, ip_src, 16); // Target address
data[20] = 2; // 4.6.1, target hwaddr
data[21] = mg_l2_ip6put(ifp->l2type, l2, data + 22); // option length / 8
tx_icmp6(ifp, l2_dst, ip_src, ip_dst, 136, 0, data,
20 + (size_t) (8 * data[21]));
}
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->pay.buf;
uint8_t *opts = (uint8_t *) (na + 1),
*endp = opts + pkt->pay.len - sizeof(*na);
if (pkt->pay.len < (sizeof(*na) + 2)) return; // first 2 bytes in opts
if ((na->res[0] & 0x40) == 0) return; // not "solicited"
if (*opts++ != 2) return; // no target hwaddr, must have
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
uint8_t len = *opts++; // check valid hwaddr and get it
if ((opts + 8 * len - 2) > endp) return; // truncated
if (!mg_l2_ip6get(ifp->l2type, ifp->gw6mac, opts, len)) return;
ifp->gw6_ready = true;
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);
uint8_t len = *opts++; // check valid hwaddr and get it
if ((opts + 8 * len - 2) > endp) return; // truncated
if (!mg_l2_ip6get(ifp->l2type, s->mac, opts, len)) return;
MG_DEBUG(("%lu NDP resolved %M -> %M", c->id, mg_print_ip6,
&c->rem.addr.ip6, mg_print_l2addr, ifp->l2type, s->mac));
c->is_arplooking = 0;
l2addr_resolved(c);
}
}
}
// Neighbor Solicitation, 4.3
static void rx_ndp_ns(struct mg_tcpip_if *ifp, struct pkt *pkt) {
struct ndp_na *ns = (struct ndp_na *) pkt->pay.buf; // struct ndp_ns = ndp_na
uint64_t target[2];
if (pkt->pay.len < (sizeof(*ns) + 2)) return; // first 2 bytes in opts
memcpy(target, ns->addr, sizeof(target));
if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6)) {
uint64_t req[2]; // requester address
uint8_t l2[sizeof(struct mg_l2addr)];
uint8_t len, *opts = (uint8_t *) (ns + 1),
*endp = opts + pkt->pay.len - sizeof(*ns);
if (*opts++ != 1) return; // no requester hwaddr (source)
len = *opts++; // check valid hwaddr and get it
if ((opts + 8 * len - 2) > endp) return; // truncated
if (!mg_l2_ip6get(ifp->l2type, l2, opts, len)) return;
req[0] = pkt->ip6->src[0], req[1] = pkt->ip6->src[1]; // align to 64-bit
tx_ndp_na(ifp, l2, target, req, true, ifp->mac);
}
}
// - use solicited node multicast to resolve a l2 address (l2_addr = NULL)
// - use unicast to verify presence (l2_addr = neighbor l2 address)
static void tx_ndp_ns(struct mg_tcpip_if *ifp, uint64_t *ip_dst,
uint8_t *l2_addr) {
uint8_t payload[4 + 16 + 16]; // NOTE(): 16 --> optional len upto 2 hw addr
uint64_t ip_unspec[2] = {0, 0};
size_t payload_len = 20;
bool mcast = (l2_addr == NULL);
uint64_t ip_mcast[2] = {0, 0};
uint8_t *l2 = l2_addr;
if (ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE) {
MG_DEBUG(("SKIP NS for %M", mg_print_ip6, ip_dst));
return;
}
memset(payload, 0, sizeof(payload));
memcpy(payload + 4, ip_dst, 16);
if (mcast) {
struct mg_addr ipd;
ip6sn(ip_dst, ip_mcast);
ipd.addr.ip6[0] = ip_mcast[0], ipd.addr.ip6[1] = ip_mcast[1],
ipd.is_ip6 = true;
l2 = mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6, &ipd);
}
payload_len = 20;
// 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, ip_unspec)) {
payload[20] = 1; // 4.6.1, source hwaddr; option length in 8-byte units
payload[21] = mg_l2_ip6put(ifp->l2type, ifp->mac, payload + 22);
payload_len += 8 * payload[21];
}
tx_icmp6(ifp, l2, ifp->ip6ll, mcast ? ip_mcast : ip_dst, 135, 0, payload,
payload_len);
}
// Router Solicitation, 4.1
static void tx_ndp_rs(struct mg_tcpip_if *ifp) {
uint8_t payload[4 + 16]; // reserved + optional len upto 2 hw addr NOTE()
size_t payload_len = 4;
uint64_t ip_unspec[2] = {0, 0};
memset(payload, 0, sizeof(payload));
if (!MG_IP6MATCH(ifp->ip6ll, ip_unspec)) {
payload[4] = 1; // 4.6.1, source hwaddr; option length in 8-byte units
payload[5] = mg_l2_ip6put(ifp->l2type, ifp->mac, payload + 6);
payload_len += 8 * payload[5];
}
tx_icmp6(ifp,
mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6,
(struct mg_addr *) &ip6_allrouters),
ifp->ip6ll, (uint64_t *) ip6_allrouters.addr.ip6, 133, 0, payload,
payload_len);
MG_DEBUG(("NDP Router Solicitation sent"));
}
static void fill_prefix(uint8_t *dst, uint8_t *src, uint8_t len) {
uint8_t full = len / 8;
uint8_t rem = len % 8;
if (full > 0) memcpy(dst, src, full);
if (rem > 0) {
uint8_t mask = (uint8_t) (0xFF << (8 - rem));
dst[full] |= src[full] & mask; // mg_l2_genip6() zeroes dst
}
}
static bool match_prefix(uint8_t *newp, uint8_t *curp, uint8_t len) {
uint8_t full = len / 8;
uint8_t rem = len % 8;
if (full > 0 && memcmp(curp, newp, full) != 0) return false;
if (rem > 0) {
uint8_t mask = (uint8_t) (0xFF << (8 - rem));
if (curp[full] != (newp[full] & mask)) return false;
}
return true;
}
static bool fill_global(struct mg_tcpip_if *ifp, uint8_t *prefix,
uint8_t prefix_len) {
if (!mg_l2_genip6(ifp->l2type, ifp->ip6, prefix_len, ifp->mac)) return false;
fill_prefix((uint8_t *) ifp->ip6, prefix, prefix_len);
fill_prefix(ifp->prefix, prefix, prefix_len);
ifp->prefix_len = prefix_len;
return true;
}
// Router Advertisement, 4.2
static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
struct ndp_ra *ra = (struct ndp_ra *) pkt->pay.buf;
uint8_t *opts = (uint8_t *) (ra + 1);
size_t opt_left = pkt->pay.len - sizeof(*ra);
bool gotl2addr = false, gotprefix = false, changed = false;
uint8_t l2[sizeof(struct mg_l2addr)];
uint32_t mtu = 0;
uint8_t *prefix = NULL, prefix_len = 0;
if (pkt->pay.len < sizeof(*ra)) return;
if (ifp->state6 == MG_TCPIP_STATE_UP) {
MG_DEBUG(("Received NDP RA")); // 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 L2 address
if (!mg_l2_ip6get(ifp->l2type, l2, opts + 2, len)) break;
gotl2addr = true;
} else if (type == 5 && length >= 8) {
// process MTU if available, ignore if it smells
mtu = MG_LOAD_BE32(opts + 4);
if (mtu < 1280 || mtu > ifp->l2mtu) mtu = 0; // RFC-8200, minimum MTU
} else if (type == 3 && length >= 32) {
// process prefix, 4.6.2, ignore if it smells
uint8_t pfx_flags = opts[3]; // L=0x80, A=0x40
uint32_t valid = MG_LOAD_BE32(opts + 4);
uint32_t pref_lifetime = MG_LOAD_BE32(opts + 8);
prefix_len = opts[2];
if (prefix_len >= 128) break;
prefix = opts + 16;
// TODO (robertc2000): handle prefix options if necessary
(void) pfx_flags;
(void) valid;
(void) pref_lifetime;
gotprefix = true;
if (prefix_len != ifp->prefix_len ||
!match_prefix(prefix, ifp->prefix, ifp->prefix_len))
changed = true;
}
opts += length;
opt_left -= length;
}
// fill prefix and global
if (gotprefix && !fill_global(ifp, prefix, prefix_len)) return;
if (changed) ifp->is_ip6_changed = true;
ifp->gw6[0] = pkt->ip6->src[0], ifp->gw6[1] = pkt->ip6->src[1];
if (gotl2addr) memcpy(ifp->gw6mac, l2, sizeof(ifp->gw6mac));
if (gotl2addr || ifp->l2type == MG_TCPIP_L2_PPP ||
ifp->l2type == MG_TCPIP_L2_PPPoE) {
ifp->state6 = MG_TCPIP_STATE_READY;
ifp->gw6_ready = true;
}
if (mtu != 0 && ifp->mtu != mtu) {
MG_ERROR(
("got an MTU: %u, that differs from the configured one. "
"All devices in an IPv6 network should have the same MTU, "
"using the router's instead...",
mtu));
ifp->mtu = (uint16_t) mtu;
}
if (ifp->state6 != MG_TCPIP_STATE_READY) {
tx_ndp_ns(ifp, ifp->gw6, NULL); // unsolicited GW hwaddr resolution
ifp->state6 = MG_TCPIP_STATE_IP;
}
onstate6change(ifp);
}
}
static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (!icmp6csum_ok(pkt->ip6, pkt->icmp6)) return;
switch (pkt->icmp6->type) {
case 128: { // Echo Request, RFC-4443 4.1
uint64_t target[2];
target[0] = pkt->ip6->dst[0], target[1] = pkt->ip6->dst[1];
if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6)) {
size_t l2_max_overhead = ifp->framesize - ifp->l2mtu;
size_t hlen = sizeof(struct ip6) + sizeof(struct icmp6);
size_t room = ifp->tx.len - hlen - l2_max_overhead, plen = pkt->pay.len;
struct mg_addr ips;
uint8_t *l2addr;
ips.addr.ip6[0] = pkt->ip6->src[0], ips.addr.ip6[1] = pkt->ip6->src[1];
ips.is_ip6 = true;
if ((l2addr = get_return_l2addr(ifp, &ips, false, pkt)) == NULL)
return; // safety net for lousy networks
if (plen > room) plen = room; // Copy (truncated) RX payload to TX
// Echo Reply, 4.2
tx_icmp6(ifp, l2addr, target, ips.addr.ip6, 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;
default:
if (mg_log_level >= MG_LL_VERBOSE)
mg_hexdump(pkt->icmp6, pkt->pay.len > 16 ? 16 : pkt->pay.len);
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));
if (ifp->l2type == MG_TCPIP_L2_ETH) // TODO(): print other l2
MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac));
if (ifp->l2type == MG_TCPIP_L2_ETH) { // gratuitous NA annc
tx_ndp_na(ifp, // RFC-4861 7.2.6
mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6,
(struct mg_addr *) &ip6_allnodes),
ifp->ip6ll, (uint64_t *) ip6_allnodes.addr.ip6, false,
ifp->mac);
if (ifp->ip6[0] != 0 && ifp->ip6[1] != 0)
tx_ndp_na(ifp, // RFC-9131 4.1
mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6,
(struct mg_addr *) &ip6_allrouters),
ifp->ip6, (uint64_t *) ip6_allrouters.addr.ip6, false,
ifp->mac);
}
if (ifp->is_ip6_changed) {
struct mg_connection *c;
for (c = ifp->mgr->conns; c != NULL; c = c->next) {
if (!c->is_listening && !c->is_udp) c->is_closing = 1;
}
ifp->is_ip6_changed = false;
}
} else if (ifp->state6 == MG_TCPIP_STATE_IP) {
if ((ifp->gw6[0] != 0 || ifp->gw6[1] != 0) &&
(ifp->l2type != MG_TCPIP_L2_PPP && ifp->l2type != MG_TCPIP_L2_PPPoE))
tx_ndp_ns(ifp, ifp->gw6, NULL); // unsolicited GW hwaddr 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)
mg_tcpip_call(ifp, MG_TCPIP_EV_STATE6_CHANGE, &ifp->state6);
}
#endif
static uint8_t *tcpip_mapip(struct mg_tcpip_if *ifp, struct mg_addr *ip) {
#if MG_ENABLE_IPV6
if (ip->is_ip6) {
if (MG_IP6MATCH(ip->addr.ip6, ip6_allnodes.addr.ip6)) // local broadcast
return mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6,
(struct mg_addr *) &ip6_allnodes);
if (*ip->addr.ip == 0xFF) // multicast
return mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST6, ip);
} else
#endif
{ // global/local broadcast
if (ip->addr.ip4 == 0xffffffff || ip->addr.ip4 == (ifp->ip | ~ifp->mask))
return mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL);
if ((*ip->addr.ip & 0xE0) == 0xE0) // 224 ~ 239 = E0 ~ EF, multicast
return mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_MCAST, ip);
}
return NULL;
}
static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem,
bool is_udp, struct pkt *pkt) {
uint8_t *l2addr;
if (is_udp && (l2addr = tcpip_mapip(ifp, rem)) != NULL)
return l2addr; // broadcast or multicast
#if MG_ENABLE_IPV6
if (rem->is_ip6) {
if (rem->addr.ip6[0] == ifp->ip6ll[0] ||
match_prefix((uint8_t *) rem->addr.ip6, ifp->prefix, ifp->prefix_len))
return mg_l2_getaddr(ifp, pkt->l2); // same LAN, get from frame
if (ifp->gw6_ready) // use the router
return ifp->gw6mac; // ignore source address in frame
} else
#endif
{
if (ifp->ip != 0 && ((rem->addr.ip4 & ifp->mask) == (ifp->ip & ifp->mask)))
return mg_l2_getaddr(ifp, pkt->l2); // same LAN, get from frame
if (ifp->gw_ready) // use the router
return ifp->gwmac; // ignore source address in frame
}
MG_ERROR(("%M %s: No way back, can't respond", mg_print_ip_port, rem,
is_udp ? "UDP" : "TCP"));
return NULL;
}
static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
struct connstate *s;
uint8_t *l2addr;
if (c == NULL) return false; // No UDP listener on this port
s = (struct connstate *) (c + 1);
c->rem.port = pkt->udp->sport;
#if MG_ENABLE_IPV6
if (c->loc.is_ip6) { // matching of v4/v6 to dest is done bt getpeer()
if (!udp6csum_ok(pkt->ip6, pkt->udp)) return false;
c->rem.addr.ip6[0] = pkt->ip6->src[0],
c->rem.addr.ip6[1] = pkt->ip6->src[1], c->rem.is_ip6 = true;
} else
#endif
{
if (!udpcsum_ok(pkt->ip, pkt->udp)) return false;
c->rem.addr.ip4 = pkt->ip->src;
}
if ((l2addr = get_return_l2addr(ifp, &c->rem, true, pkt)) == NULL)
return false; // safety net for lousy networks
memcpy(s->mac, l2addr, sizeof(s->mac));
if (c->recv.len >= MG_MAX_RECV_SIZE) {
mg_error(c, "max_recv_buf_size reached");
} else if (c->recv.size - c->recv.len < pkt->pay.len &&
!mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
mg_error(c, "oom");
} else {
memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
c->recv.len += pkt->pay.len;
mg_call(c, MG_EV_READ, &pkt->pay.len);
}
return true;
}
static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
struct mg_addr *ip_src, struct mg_addr *ip_dst,
uint8_t dscp, uint8_t flags, uint32_t seq, uint32_t ack,
const void *buf, size_t len) {
uint8_t *l3p;
struct ip *ip = NULL;
struct tcp *tcp;
uint16_t opts[4 / 2];
size_t hlen = sizeof(*tcp);
#if MG_ENABLE_IPV6
struct ip6 *ip6 = NULL;
#endif
// Handle any options first, here, to determine header size
if (flags & TH_SYN) { // Send MSS
uint16_t mss;
#if MG_ENABLE_IPV6 // RFC-9293 3.7.1; RFC-6691 2
mss = (uint16_t) (ifp->mtu - 60);
#else
mss = (uint16_t) (ifp->mtu - 40);
#endif
opts[0] = mg_htons(0x0204); // RFC-9293 3.2
opts[1] = mg_htons(mss);
hlen += sizeof(opts); // always whole number of 32-bit words
}
#if MG_ENABLE_IPV6
if (ip_dst->is_ip6) {
ip6 = tx_ip6(ifp, l2_dst, 6, dscp, ip_src->addr.ip6, ip_dst->addr.ip6,
hlen + len);
tcp = (struct tcp *) (ip6 + 1);
l3p = (uint8_t *) ip6;
} else
#endif
{
ip = tx_ip(ifp, l2_dst, 6, dscp, ip_src->addr.ip4, ip_dst->addr.ip4,
hlen + len);
tcp = (struct tcp *) (ip + 1);
l3p = (uint8_t *) ip;
}
memset(tcp, 0, sizeof(*tcp));
memmove(tcp + 1, opts, hlen - sizeof(*tcp)); // copy opts if any
if (buf != NULL && len) memmove((uint8_t *) tcp + hlen, buf, len);
tcp->sport = ip_src->port;
tcp->dport = ip_dst->port;
tcp->seq = seq;
tcp->ack = ack;
tcp->flags = flags;
tcp->win = mg_htons(MG_TCPIP_WIN);
tcp->off = (uint8_t) (hlen / 4 << 4);
#if MG_ENABLE_IPV6
if (ip_dst->is_ip6) {
tcp->csum = p6csum(ip6, tcp, hlen + len);
} else
#endif
{
tcp->csum = pcsum(ip, tcp, hlen + len);
}
MG_VERBOSE(("TCP %M -> %M fl %x len %u", mg_print_ip_port, ip_src,
mg_print_ip_port, ip_dst, tcp->flags, len));
return driver_output(
ifp, mg_l2_trailer(ifp, PDIFF(l3p, (uint8_t *) tcp + hlen + len),
(uint8_t *) tcp + hlen + len));
}
static size_t tx_tcp_ctrlresp(struct mg_tcpip_if *ifp, struct pkt *pkt,
uint8_t flags, uint32_t seqno) {
uint32_t ackno = mg_htonl(mg_ntohl(pkt->tcp->seq) + (uint32_t) pkt->pay.len +
((pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0));
struct mg_addr ips, ipd;
uint8_t *l2addr;
memset(&ips, 0, sizeof(ips));
memset(&ipd, 0, sizeof(ipd));
if (pkt->ip != NULL) {
ips.addr.ip4 = pkt->ip->dst;
ipd.addr.ip4 = pkt->ip->src;
} else {
ips.addr.ip6[0] = pkt->ip6->dst[0], ips.addr.ip6[1] = pkt->ip6->dst[1];
ipd.addr.ip6[0] = pkt->ip6->src[0], ipd.addr.ip6[1] = pkt->ip6->src[1];
ips.is_ip6 = true;
ipd.is_ip6 = true;
}
ips.port = pkt->tcp->dport;
ipd.port = pkt->tcp->sport;
if ((l2addr = get_return_l2addr(ifp, &ipd, false, pkt)) == NULL)
return 0; // safety net for lousy networks
return tx_tcp(ifp, l2addr, &ips, &ipd, 0, flags, seqno, ackno, NULL, 0);
}
static size_t tx_tcp_rst(struct mg_tcpip_if *ifp, struct pkt *pkt, bool toack) {
return tx_tcp_ctrlresp(ifp, pkt,
(uint8_t) (toack ? TH_RST : (TH_RST | TH_ACK)),
toack ? pkt->tcp->ack : 0);
}
static struct mg_connection *accept_conn(struct mg_connection *lsn,
struct pkt *pkt, uint16_t mss) {
struct connstate *s;
uint8_t *l2addr;
struct mg_connection *c = mg_alloc_conn(lsn->mgr);
if (c == NULL) {
MG_ERROR(("OOM"));
return NULL;
}
s = (struct connstate *) (c + 1);
s->dmss = mss; // from options in client SYN
s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t) (s->seq + s->win);
#if MG_ENABLE_IPV6
if (lsn->loc.is_ip6) {
c->rem.addr.ip6[0] = pkt->ip6->src[0],
c->rem.addr.ip6[1] = pkt->ip6->src[1], c->rem.is_ip6 = true;
c->loc.addr.ip6[0] = c->mgr->ifp->ip6[0],
c->loc.addr.ip6[1] = c->mgr->ifp->ip6[1], c->loc.is_ip6 = true;
// TODO(): compare lsn to link-local, or rem as link-local: use ll instead
} else
#endif
{
c->rem.addr.ip4 = pkt->ip->src;
c->loc.addr.ip4 = c->mgr->ifp->ip;
}
c->rem.port = pkt->tcp->sport;
c->loc.port = lsn->loc.port;
if ((l2addr = get_return_l2addr(lsn->mgr->ifp, &c->rem, false, pkt)) ==
NULL) {
free(c); // safety net for lousy networks, not actually needed
return NULL; // as path has already been checked at SYN (sending SYN+ACK)
}
memcpy(s->mac, l2addr, sizeof(s->mac));
settmout(c, MIP_TTYPE_KEEPALIVE);
MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem));
LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
c->is_accepted = 1;
c->is_hexdumping = lsn->is_hexdumping;
c->pfn = lsn->pfn;
c->pfn_data = lsn->pfn_data;
c->fn = lsn->fn;
c->fn_data = lsn->fn_data;
c->is_tls = lsn->is_tls;
mg_call(c, MG_EV_OPEN, NULL);
mg_call(c, MG_EV_ACCEPT, NULL);
if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init()
return c;
}
static size_t trim_len(struct mg_connection *c, size_t len) {
struct mg_tcpip_if *ifp = c->mgr->ifp;
size_t l2_max_overhead = ifp->framesize - ifp->l2mtu;
size_t ip_max_h_len = c->rem.is_ip6 ? 40 : 24; // we don't send options
size_t tcp_max_h_len = 60 /* RFC-9293 3.7.1; RFC-6691 2 */, udp_h_len = 8;
size_t max_headers_len =
ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len);
size_t min_mtu = c->rem.is_ip6 ? 1280 /* RFC-8200, IPv6 minimum */
: c->is_udp ? 68 /* RFC-791, IP minimum */
: max_headers_len /* fit full TCP header */;
// NOTE(): We are effectively reducing transmitted TCP segment length by 20,
// accounting for possible options; though we currently don't send options
// except for SYN.
// If the frame exceeds the available buffer, trim the length.
if (len + max_headers_len + l2_max_overhead > ifp->tx.len)
len = ifp->tx.len - max_headers_len - l2_max_overhead;
// Ensure the MTU isn't lower than the minimum allowed value
if (ifp->mtu < min_mtu) {
MG_ERROR(("MTU is lower than minimum, raising to %lu", min_mtu));
ifp->mtu = (uint16_t) min_mtu;
}
// If the total packet size exceeds the MTU, trim the length
if (len + max_headers_len > ifp->mtu) {
len = ifp->mtu - max_headers_len;
if (c->is_udp) MG_ERROR(("UDP datagram exceeds MTU. Truncating it."));
}
return len;
}
static bool udp_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);
struct mg_addr ips;
memset(&ips, 0, sizeof(ips));
#if MG_ENABLE_IPV6
if (c->loc.is_ip6) {
ips.addr.ip6[0] = ifp->ip6[0], ips.addr.ip6[1] = ifp->ip6[1],
ips.is_ip6 = true;
// TODO(): detect link-local (c->rem) and use it
} else
#endif
{
ips.addr.ip4 = ifp->ip;
}
ips.port = c->loc.port;
return tx_udp(ifp, s->mac, &ips, &c->rem, c->dscp, buf, len);
}
long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
struct connstate *s = (struct connstate *) (c + 1);
len = trim_len(c, len);
if (c->is_udp) {
if (!udp_send(c, buf, len)) return MG_IO_WAIT;
} else { // TCP, cap to peer's MSS and check window
struct mg_tcpip_if *ifp = c->mgr->ifp;
size_t sent;
uint32_t room = s->maxseq - s->seq;
if (room == 0) return MG_IO_WAIT;
if (len > s->dmss) len = s->dmss; // RFC-6691: reduce if sending opts
if ((uint32_t) len > room) len = room;
sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, 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) {
return MG_IO_ERR;
} else {
s->seq += (uint32_t) len;
if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
}
}
return (long) len;
}
static void handle_tls_recv(struct mg_connection *c) {
size_t avail = mg_tls_pending(c); // will change after mg_tls_recv()
size_t min = avail > MG_MAX_RECV_SIZE ? MG_MAX_RECV_SIZE : avail;
struct mg_iobuf *io = &c->recv; // allocated on first avail > 0
if (io->size - io->len < min && !mg_iobuf_resize(io, io->len + min)) {
mg_error(c, "oom");
} else {
// Decrypt data directly into c->recv. If io->buf = NULL or
// io->len = io->size (no room), there can be outstanding data that can't be
// moved (mg_tls_pending() > 0 after mg_tls_recv() returns). So
// mg_tls_recv() takes care of returning 0 in this case (commented as "MIP")
long n = mg_tls_recv(c, io->buf != NULL ? &io->buf[io->len] : io->buf,
io->size - io->len);
if (n == MG_IO_ERR) {
mg_error(c, "TLS recv error");
} else if (n > 0) {
// Decrypted successfully - trigger MG_EV_READ
io->len += (size_t) n;
mg_call(c, MG_EV_READ, &n);
} // else n < 0: outstanding data to be moved to c->recv
}
}
static void handle_ack(struct connstate *s, uint32_t ackno, uint16_t win) {
if (ackno < (s->seq - s->win) || ackno > s->seq) return;
s->maxseq = (uint32_t) (ackno + win);
s->win = win;
}
static void read_conn(struct mg_connection *c, struct pkt *pkt) {
struct connstate *s = (struct connstate *) (c + 1);
struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv;
uint32_t seq = mg_ntohl(pkt->tcp->seq);
// Evaluation order is important: 1) process FIN, 2) process keep-alive,
// 3) discard invalid SEG.SEQ, 4) process ACK and payload.
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, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
return;
}
// If we initiated the closure, we reply with ACK upon receiving FIN
// If we didn't initiate it, we reply with FIN as part of the normal TCP
// closure process
s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1);
s->fin_rcvd = true;
if (c->is_draining && s->ttype == MIP_TTYPE_FIN) {
if (s->seq == mg_htonl(pkt->tcp->ack)) { // Simultaneous closure ?
s->seq++; // Yes. Increment our SEQ
} else { // Otherwise,
s->seq = mg_htonl(pkt->tcp->ack); // Set to peer's ACK
}
s->twclosure = true;
} else {
// Peer closed first: send ACK only, enter CLOSE_WAIT.
// The connection loop will call init_closure after pending send data
// is flushed, then send our FIN.
c->is_draining = 1;
}
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, flags,
mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
} 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, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
return; // RFC-9293 3.10.7.4 discard (incorrect) payload, ACK, window
} else if (seq != s->ack) {
uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
if (s->ack == ack) {
MG_VERBOSE(("ignoring duplicate pkt"));
} else {
MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack));
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
}
return; // drop it, RFC-9293 3.10.7.4: ignore ACKno
}
// Now process the segment for ACK and payload
if (pkt->tcp->flags & TH_ACK) {
handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win));
if (pkt->pay.len == 0 && s->fin_rcvd && s->ttype == MIP_TTYPE_FIN)
s->twclosure = true;
}
if (pkt->pay.len == 0) return;
if (io->size - io->len < pkt->pay.len &&
!mg_iobuf_resize(io, io->len + pkt->pay.len)) {
mg_error(c, "oom");
return; // drop it
}
// Copy TCP payload into the IO buffer. If the connection is plain text,
// we copy to c->recv. If the connection is TLS, this data is encrypted,
// therefore we copy that encrypted data to the c->rtls iobuffer instead,
// and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will
// call back mg_io_recv() which grabs raw data from c->rtls
memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len);
io->len += pkt->pay.len;
MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack));
if (!(pkt->tcp->flags & TH_FIN)) { // FIN already advanced s->ack
// Advance ACK counter
s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
s->unacked += pkt->pay.len;
}
// size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack;
if (s->unacked > MG_TCPIP_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, &c->loc, &c->rem, c->dscp, 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);
} else {
// if not already running, setup a timer to send an ACK later
if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
}
if (c->is_tls) {
c->is_tls_hs ? mg_tls_handshake(c) : handle_tls_recv(c);
} else {
// Plain text connection, data is already in c->recv, trigger MG_EV_READ
mg_call(c, MG_EV_READ, &pkt->pay.len);
}
}
// TCP backlog
struct mg_backlog {
uint16_t port, mss; // use port=0 for available entries
uint8_t age;
};
static int backlog_insert(struct mg_connection *c, uint16_t port,
uint16_t mss) {
struct mg_backlog *p = (struct mg_backlog *) c->data;
size_t i;
for (i = 0; i < sizeof(c->data) / sizeof(*p); i++) {
if (p[i].port != 0) continue;
p[i].age = 2; // remove after two calls, average 1.5 call rate
p[i].port = port, p[i].mss = mss;
return (int) i;
}
return -1;
}
static struct mg_backlog *backlog_retrieve(struct mg_connection *c,
uint16_t key, uint16_t port) {
struct mg_backlog *p = (struct mg_backlog *) c->data;
if (key >= sizeof(c->data) / sizeof(*p)) return NULL;
if (p[key].port != port) return NULL;
p += key;
return p;
}
static void backlog_remove(struct mg_connection *c, uint16_t key) {
struct mg_backlog *p = (struct mg_backlog *) c->data;
p[key].port = 0;
}
static void backlog_maintain(struct mg_connection *c) {
struct mg_backlog *p = (struct mg_backlog *) c->data;
size_t i; // dec age and remove those where it reaches 0
for (i = 0; i < sizeof(c->data) / sizeof(*p); i++) {
if (p[i].port == 0) continue;
if (p[i].age != 0) --p[i].age;
if (p[i].age == 0) p[i].port = 0;
}
}
static void backlog_poll(struct mg_mgr *mgr) {
struct mg_connection *c = NULL;
for (c = mgr->conns; c != NULL; c = c->next) {
if (!c->is_udp && c->is_listening) backlog_maintain(c);
}
}
// process options (MSS)
static bool 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 = 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
if (len < 2 || opts[1] == 0 || opts[1] > len) return false; // Malformed
optlen = opts[1];
if (kind == 2 && optlen == 4) // set received MSS
s->dmss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]);
}
MG_VERBOSE(("kind: %u, optlen: %u, len: %d\n", kind, optlen, len));
opts += optlen;
len -= optlen;
}
return true;
}
static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1);
#if MG_ENABLE_IPV6 // matching of v4/v6 to dest is done by getpeer()
if (pkt->ip6 != NULL && !tcp6csum_ok(pkt->ip6, pkt->tcp)) return;
#endif
if (pkt->ip != NULL && !tcpcsum_ok(pkt->ip, pkt->tcp)) return;
pkt->tcp->flags &= TH_STDFLAGS; // tolerate creative usage (ECN, ?)
// Order is VERY important; RFC-9293 3.5.2
// - 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
if (!handle_opt(s, pkt->tcp, pkt->ip6 != NULL))
return; // process options (MSS)
s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1;
s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t) (s->seq + s->win);
tx_tcp_ctrlresp(ifp, pkt, TH_ACK, pkt->tcp->ack);
c->is_connecting = 0; // Client connected
settmout(c, MIP_TTYPE_KEEPALIVE);
mg_call(c, MG_EV_CONNECT, NULL); // Let user know
if (c->is_tls_hs) mg_tls_handshake(c);
if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init()
} else if (c != NULL && c->is_connecting && pkt->tcp->flags != TH_ACK) {
mg_error(c, "connection refused");
} else if (c != NULL && pkt->tcp->flags & TH_RST) {
uint32_t seqno = mg_ntohl(pkt->tcp->seq);
if (seqno >= s->ack && seqno < (s->ack + MG_TCPIP_WIN)) // RFC-9293 3.5.3
mg_error(c, "peer RST"); // RFC-1122 4.2.2.13
} else if (c != NULL) {
// process segment
s->tmiss = 0; // Reset missed keep-alive counter
if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer
settmout(c,
MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending
read_conn(c, pkt); // Override timer with ACK timeout if needed
} else
// - we don't listen on that port; RFC-9293 3.5.2 Group 1
// - check listening connections; RFC-9293 3.5.2 Group 2
if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
// not listening on that port
if (!(pkt->tcp->flags & TH_RST)) {
tx_tcp_rst(ifp, pkt, pkt->tcp->flags & TH_ACK);
} // else silently discard
} else if (pkt->tcp->flags == TH_SYN) {
// listener receives a connection request
struct connstate cs; // At this point, s = NULL, there is no connection
int key;
uint32_t isn;
if (pkt->tcp->sport != 0) {
if (!handle_opt(&cs, pkt->tcp, pkt->ip6 != NULL))
return; // 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
// Use peer's src port and bl key as ISN, to later identify the
// handshake
isn = (mg_htonl(((uint32_t) key << 16) | mg_ntohs(pkt->tcp->sport)));
if (tx_tcp_ctrlresp(ifp, pkt, TH_SYN | TH_ACK, isn) == 0)
backlog_remove(c, (uint16_t) key); // safety net for lousy networks
} // what should we do when port=0 ? Linux takes port 0 as any other
// port
} else if (pkt->tcp->flags == TH_ACK) {
// listener receives an ACK
struct mg_backlog *b = NULL;
if ((uint16_t) (mg_htonl(pkt->tcp->ack) - 1) ==
mg_htons(pkt->tcp->sport)) {
uint16_t key = (uint16_t) ((mg_htonl(pkt->tcp->ack) - 1) >> 16);
b = backlog_retrieve(c, key, pkt->tcp->sport);
if (b != NULL) { // ACK is a response to a SYN+ACK
accept_conn(c, pkt, b->mss); // pass options
backlog_remove(c, key);
} // else not an actual match, reset
}
if (b == NULL) tx_tcp_rst(ifp, pkt, true);
} else if (pkt->tcp->flags & TH_RST) {
// silently discard
} else if (pkt->tcp->flags & TH_ACK) { // ACK + something else != RST
tx_tcp_rst(ifp, pkt, true);
} else if (pkt->tcp->flags & TH_SYN) { // SYN + something else != ACK
tx_tcp_rst(ifp, pkt, false);
} // else silently discard
}
static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
uint8_t ihl;
uint16_t frag, len;
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
// There can be link padding, take length from IP header
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
if (!ipcsum_ok(pkt->ip)) return;
frag = mg_ntohs(pkt->ip->frag);
if (frag & IP_MORE_FRAGS_MSK || frag & IP_FRAG_OFFSET_MSK) {
struct mg_connection *c;
if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->pay.buf);
if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->pay.buf);
c = getpeer(ifp->mgr, pkt, false);
if (c) mg_error(c, "Received fragmented packet");
} else if (pkt->ip->proto == 1) {
pkt->icmp = (struct icmp *) (pkt->pay.buf);
if (pkt->pay.len < sizeof(*pkt->icmp)) return;
mkpay(pkt, pkt->icmp + 1);
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
// 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_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,
mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68) &&
len >= offsetof(struct dhcp, options)) {
pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
mkpay(pkt, &pkt->dhcp->options);
rx_dhcp_client(ifp, pkt);
} else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67) &&
len >= offsetof(struct dhcp, options)) {
pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
mkpay(pkt, &pkt->dhcp->options);
rx_dhcp_server(ifp, pkt);
} else if (!rx_udp(ifp, pkt)) {
// 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;
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,
mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len));
rx_tcp(ifp, pkt);
} else {
MG_DEBUG(("Unknown IP proto %x", (int) pkt->ip->proto));
if (mg_log_level >= MG_LL_VERBOSE)
mg_hexdump(pkt->ip, pkt->pay.len >= 32 ? 32 : pkt->pay.len);
}
}
#if MG_ENABLE_IPV6
static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
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) {
uint16_t hlen;
switch (next) {
case 0: // Hop-by-Hop 4.3
case 43: // Routing 4.4
case 60: // Destination Options 4.6
case 51: // Authentication RFC-4302
MG_INFO(("IPv6 extension header %d", (int) next));
if (((uint32_t) len + 2) > plen) return; // nhdr[0, 1]; malformed
next = nhdr[0];
hlen = (uint16_t) (8 * (nhdr[1] + 1));
if (((uint32_t) len + hlen) > plen) return; // malformed
len += hlen;
nhdr += hlen;
break;
case 44: // Fragment 4.5
{
struct mg_connection *c;
if (((uint32_t) len + 2) > plen) return; // nhdr[0, 1]; malformed
if (nhdr[0] == 17) pkt->udp = (struct udp *) (pkt->pay.buf);
if (nhdr[0] == 6) pkt->tcp = (struct tcp *) (pkt->pay.buf);
c = getpeer(ifp->mgr, pkt, false);
if (c) mg_error(c, "Received fragmented packet");
}
return;
case 59: // No Next Header 4.7
return;
case 50: // IPsec ESP RFC-4303, unsupported
default:
loop = false;
break;
}
}
// There can be link padding, take payload length from IPv6 header - options
pkt->pay.buf = (char *) nhdr;
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);
} 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_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);
#endif
} else if (!rx_udp(ifp, pkt)) {
// Should send ICMPv6 Destination Unreachable for unicasts, keep silent
}
} else if (next == 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;
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_ntohs(pkt->tcp->dport), (int) pkt->pay.len));
rx_tcp(ifp, pkt);
} else {
MG_DEBUG(("Unknown IPv6 next hdr %x", (int) next));
if (mg_log_level >= MG_LL_VERBOSE)
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;
enum mg_l2proto proto;
memset(&pkt, 0, sizeof(pkt));
pkt.raw.buf = (char *) buf;
pkt.raw.len = len;
pkt.l2 = (uint8_t *) pkt.raw.buf;
if (!mg_l2_rx(ifp, &proto, &pkt.pay, &pkt.raw)) return;
if (ifp->state < MG_TCPIP_STATE_UP) return; // discard while L2 is not up
if (proto == MG_TCPIP_L2PROTO_ARP) {
pkt.arp = (struct arp *) (pkt.pay.buf);
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 (proto == MG_TCPIP_L2PROTO_IPV6) {
pkt.ip6 = (struct ip6 *) (pkt.pay.buf);
rx_ip6(ifp, &pkt);
} else if (proto == MG_TCPIP_L2PROTO_IPV4) {
pkt.ip = (struct ip *) (pkt.pay.buf);
rx_ip(ifp, &pkt);
}
}
static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) {
if (ifp->state < MG_TCPIP_STATE_UP) 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, mg_l2_mapip(ifp->l2type, MG_TCPIP_L2ADDR_BCAST, NULL), ifp->ip,
0xffffffff);
} // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
}
}
}
static void mg_ip_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) {
bool cur_drv = (ifp->state != MG_TCPIP_STATE_DOWN);
bool cur_l2 = (ifp->state >= MG_TCPIP_STATE_UP);
if (!l2_up && ifp->enable_dhcp_client) ifp->ip = 0;
if (drv_up != cur_drv || l2_up != cur_l2) { // link/L2 state has changed
ifp->state = !drv_up ? MG_TCPIP_STATE_DOWN
: !l2_up ? MG_TCPIP_STATE_LINK_UP
: 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_UP) return;
if (ifp->enable_slaac && s1 && ifp->state6 == MG_TCPIP_STATE_UP)
tx_ndp_rs(ifp);
}
static void mg_ip6_link(struct mg_tcpip_if *ifp, bool drv_up, bool l2_up) {
bool cur_drv = (ifp->state6 != MG_TCPIP_STATE_DOWN);
bool cur_l2 = (ifp->state6 >= MG_TCPIP_STATE_UP);
const uint8_t px[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0}; // RFC-4291 2.5.6
if (drv_up != cur_drv || l2_up != cur_l2) { // link/L2 state has changed
if (ifp->l2type == MG_TCPIP_L2_ETH && l2_up && ifp->ip6ll[0] == 0 &&
ifp->ip6ll[1] == 0) { // gen ll address
mg_l2_genip6(ifp->l2type, ifp->ip6ll, 64, ifp->mac);
memcpy(ifp->ip6ll, px, 8); // RFC-2464 5
} // just got our link local address if we didn't have one.
// If static configuration is used, 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;
if (!l2_up && ifp->enable_slaac) ifp->ip6[0] = ifp->ip6[1] = 0;
ifp->state6 = !drv_up ? MG_TCPIP_STATE_DOWN
: !l2_up ? MG_TCPIP_STATE_LINK_UP
: ifp->ip6ll[0] == 0 || 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->ip6ll[0] != 0 && ifp->ip6[0] != 0) {
ifp->state6 = MG_TCPIP_STATE_IP; // ifp->fn has set an IP
onstate6change(ifp);
}
if ((ifp->l2type == MG_TCPIP_L2_PPP || ifp->l2type == MG_TCPIP_L2_PPPoE) &&
ifp->state6 == MG_TCPIP_STATE_UP && ifp->ip6ll[0] == 0 &&
ifp->ip6ll[1] != 0) { // IPV6CP has got an IFCID, gen ll address
memcpy(ifp->ip6ll, px, 8); // RFC-5072 5
onstate6change(ifp);
} // just got our link local address if we didn't have one.
}
#else
#define mg_ip6_poll(x, y)
#define mg_ip6_link(x, y, z)
#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);
ifp->now = now;
if (expired_1000ms) {
#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS
const char *names[] = {"down", "lnk up", "lnk rdy", "req", "ip", "ready"};
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);
}
// Handle gw ARP request timeout, order is important
if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) {
ifp->state = MG_TCPIP_STATE_READY; // keep best-effort MAC or poison mark
onstatechange(ifp);
}
if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && !ifp->gw_ready &&
ifp->gw != 0)
mg_tcpip_arp_request(ifp, ifp->gw, NULL); // retry GW ARP request
#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 or poison mark
onstate6change(ifp);
}
if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && !ifp->gw6_ready &&
(ifp->gw6[0] != 0 || ifp->gw6[1] != 0))
tx_ndp_ns(ifp, ifp->gw6, NULL); // retry GW hwaddr resolution
#endif
{ // poll driver and let L2 do its work, if any
// Handle physical interface up/down status
bool drv_up, l2_up;
drv_up = ifp->driver->poll ? ifp->driver->poll(ifp, expired_1000ms) : true;
l2_up = mg_l2_poll(ifp, expired_1000ms); // Handle L2 up/down link status;
if (expired_1000ms) { // ifp->state rules over state6
mg_ip_link(ifp, drv_up, l2_up); // Handle IPv4
mg_ip6_link(ifp, drv_up, l2_up); // Handle IPv6
if (ifp->state < MG_TCPIP_STATE_UP) MG_ERROR(("Network is down"));
mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL);
}
}
mg_ip_poll(ifp, expired_1000ms); // Handle IPv4
mg_ip6_poll(ifp, expired_1000ms); // Handle IPv6
// Read data from the network (mg_tcpip_rx() will discard for us)
if (ifp->driver->rx != NULL) { // Simple polling driver, returns one frame
size_t len =
ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp);
if (len > 0) {
ifp->nrecv++;
mg_tcpip_rx(ifp, ifp->recv_queue.buf, len);
}
} else { // Complex poll / Interrupt-based driver. Queues recvd frames
char *buf;
size_t len, cnt = 7; // Max 7 packets to fetch
while (cnt-- > 0 && (len = mg_queue_next(&ifp->recv_queue, &buf)) > 0) {
mg_tcpip_rx(ifp, buf, len);
mg_queue_del(&ifp->recv_queue, len);
}
}
if (ifp->state < MG_TCPIP_STATE_UP) return; // need to let L2 do its job
// Process timeouts
for (c = ifp->mgr->conns; c != NULL; c = c->next) {
struct connstate *s = (struct connstate *) (c + 1);
if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving)
continue;
if (ifp->now > s->timer) {
if (s->ttype == MIP_TTYPE_ARP) {
mg_error(c, "ARP timeout");
} else if (c->is_udp) {
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, &c->loc, &c->rem, c->dscp, 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");
} else if (s->ttype == MIP_TTYPE_FIN) {
c->is_closing = 1;
continue;
} else {
if (s->tmiss++ > 2) {
mg_error(c, "keepalive");
} else {
MG_VERBOSE(("%lu keepalive", c->id));
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0);
}
}
settmout(c, MIP_TTYPE_KEEPALIVE);
}
}
}
// This function executes in interrupt context, thus it should copy data
// somewhere fast. Note that newlib's malloc is not thread safe, thus use
// our lock-free queue with preallocated buffer to copy data and return asap
void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp) {
char *p;
if (mg_queue_book(&ifp->recv_queue, &p, len) >= len) {
memcpy(p, buf, len);
mg_queue_add(&ifp->recv_queue, len);
ifp->nrecv++;
} else {
ifp->ndrop++;
}
}
void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
// If L2 address is not set, make a random one; fill MTU
mg_l2_init(ifp);
ifp->mtu = ifp->l2mtu;
if (ifp->dhcp_name[0] == '\0') // If DHCP name is not set, use "mip"
memcpy(ifp->dhcp_name, "mip", 4);
ifp->dhcp_name[sizeof(ifp->dhcp_name) - 1] = '\0'; // Just in case
if (ifp->driver->init && !ifp->driver->init(ifp)) {
MG_ERROR(("driver init failed"));
} else {
ifp->tx.buf = (char *) mg_calloc(1, ifp->framesize),
ifp->tx.len = ifp->framesize;
if (ifp->recv_queue.size == 0)
ifp->recv_queue.size = ifp->driver->rx ? ifp->framesize : 8192;
ifp->recv_queue.buf = (char *) mg_calloc(1, ifp->recv_queue.size);
ifp->timer_1000ms = mg_millis();
mgr->ifp = ifp;
ifp->mgr = mgr;
mgr->extraconnsize = sizeof(struct connstate);
if (ifp->ip == 0 && ifp->l2type != MG_TCPIP_L2_PPP &&
ifp->l2type != MG_TCPIP_L2_PPPoE)
ifp->enable_dhcp_client = true;
mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535
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"));
}
}
void mg_tcpip_free(struct mg_tcpip_if *ifp) {
mg_free(ifp->recv_queue.buf);
mg_free(ifp->tx.buf);
mg_free(ifp->dns4_url);
}
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));
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_SYN, isn, 0, NULL,
0);
}
static void l2addr_resolved(struct mg_connection *c) {
if (c->is_udp) {
c->is_connecting = 0;
mg_call(c, MG_EV_CONNECT, NULL);
} else {
send_syn(c);
settmout(c, MIP_TTYPE_SYN);
}
}
void mg_connect_resolved(struct mg_connection *c) {
struct mg_tcpip_if *ifp = c->mgr->ifp;
uint8_t *l2addr;
c->is_resolving = 0;
if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE;
c->loc.port = mg_htons(ifp->eport++);
#if MG_ENABLE_IPV6
if (c->rem.is_ip6) {
if (c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll
c->loc.addr.ip6[0] = ifp->ip6ll[0], c->loc.addr.ip6[1] = ifp->ip6ll[1];
} else { // use global address
c->loc.addr.ip6[0] = ifp->ip6[0], c->loc.addr.ip6[1] = ifp->ip6[1];
}
c->loc.is_ip6 = true;
} else
#endif
{
c->loc.addr.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 && (l2addr = tcpip_mapip(ifp, &c->rem)) != NULL) {
struct connstate *s = (struct connstate *) (c + 1);
memcpy(s->mac, l2addr, sizeof(s->mac));
l2addr_resolved(c); // broadcast or multicast
#if MG_ENABLE_IPV6
} else if (c->rem.is_ip6) {
if (match_prefix((uint8_t *) c->rem.addr.ip6, ifp->prefix,
ifp->prefix_len) // same global LAN
|| (c->rem.addr.ip6[0] == ifp->ip6ll[0] // same local LAN
&& !MG_IP6MATCH(c->rem.addr.ip6, ifp->gw6))) { // and not gw
// If we're in the same LAN, fire a Neighbor Solicitation
MG_DEBUG(("%lu NS lookup...", c->id));
tx_ndp_ns(ifp, c->rem.addr.ip6, NULL); // RFC-4861 4.3, requesting
settmout(c, MIP_TTYPE_ARP);
c->is_arplooking = 1;
} else if (ifp->gw6_ready) {
struct connstate *s = (struct connstate *) (c + 1);
memcpy(s->mac, ifp->gw6mac, sizeof(s->mac));
l2addr_resolved(c);
} else {
MG_ERROR(("No IPv6 gateway, can't connect"));
}
#endif
} else {
uint32_t rem_ip = c->rem.addr.ip4;
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 (ifp->gw_ready) {
struct connstate *s = (struct connstate *) (c + 1);
memcpy(s->mac, ifp->gwmac, sizeof(s->mac));
l2addr_resolved(c);
} else {
MG_ERROR(("No gateway, can't connect"));
}
}
}
bool mg_open_listener(struct mg_connection *c, const char *url) {
c->loc.port = mg_htons(mg_url_port(url));
if (!mg_aton(mg_url_host(url), &c->loc)) {
MG_ERROR(("invalid listening URL: %s", url));
return false;
}
return true;
}
static void write_conn(struct mg_connection *c) {
long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
: mg_io_send(c, c->send.buf, c->send.len);
// TODO(): mg_tls_send() may return 0 forever on steady OOM
if (len == MG_IO_ERR) {
mg_error(c, "tx err");
} else if (len > 0) {
mg_iobuf_del(&c->send, 0, (size_t) len);
mg_call(c, MG_EV_WRITE, &len);
}
}
static void init_closure(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1);
if (c->is_listening == false && c->is_connecting == false) {
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_FIN | TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
settmout(c, MIP_TTYPE_FIN);
}
}
static void close_conn(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1);
mg_close_conn(c);
(void) s;
}
static bool can_write(struct mg_connection *c) {
return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 &&
c->is_tls_hs == 0 && c->is_arplooking == 0;
}
void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
struct mg_connection *c, *tmp;
uint64_t now = mg_millis();
mg_timer_poll(&mgr->timers, now);
mg_ota_poll(mgr);
if (mgr->ifp == NULL || mgr->ifp->driver == NULL) return;
mg_tcpip_poll(mgr->ifp, now);
for (c = mgr->conns; c != NULL; c = tmp) {
struct connstate *s = (struct connstate *) (c + 1);
bool is_tls = c->is_tls && !c->is_resolving && !c->is_arplooking &&
!c->is_listening && !c->is_connecting;
tmp = c->next;
mg_call(c, MG_EV_POLL, &now);
MG_VERBOSE(("%lu .. %c%c%c%c%c %lu %lu", c->id, c->is_tls ? 'T' : 't',
c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c',
mg_tls_pending(c), c->rtls.len));
// order is important, TLS conn close with > 1 record in buffer (below)
if (is_tls && (c->rtls.len > 0 || mg_tls_pending(c) > 0))
c->is_tls_hs ? mg_tls_handshake(c) : handle_tls_recv(c);
if (can_write(c)) write_conn(c);
if (is_tls && c->send.len == 0) mg_tls_flush(c);
if (c->is_draining && c->send.len == 0) {
if (c->is_udp) c->is_closing = 1;
if (!c->is_udp && s->ttype != MIP_TTYPE_FIN) init_closure(c);
}
// For non-TLS, close immediately upon completing the 3-way closure
// For TLS, handle any pending data (above) until MIP_TTYPE_FIN expires
if (s->twclosure &&
(!c->is_tls || (c->rtls.len == 0 && mg_tls_pending(c) == 0)))
c->is_closing = 1;
if (c->is_closing) close_conn(c);
}
(void) ms;
}
bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
struct mg_tcpip_if *ifp = c->mgr->ifp;
bool res = false;
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) {
len = trim_len(c, len); // Trimming length if necessary
res = udp_send(c, buf, len);
} else {
res = len == 0 || mg_iobuf_add(&c->send, c->send.len, buf, len) > 0;
// returning 0 means an OOM condition (iobuf couldn't resize), yet this is
// so far recoverable, let the caller decide
}
return res;
}
uint8_t mcast_addr[6] = {0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb};
void mg_multicast_add(struct mg_connection *c, char *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
}
bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc);
static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip) {
struct mg_dns *dnsc;
mg_free(ifp->dns4_url);
ifp->dns4_url = mg_mprintf("udp://%M:53", mg_print_ip4, ip);
dnsc = &ifp->mgr->dns4;
dnsc->url = (const char *) ifp->dns4_url;
MG_DEBUG(("Set DNS URL to %s", dnsc->url));
if (ifp->mgr->use_dns6) return;
if (dnsc->c != NULL) mg_close_conn(dnsc->c);
if (!mg_dnsc_init(ifp->mgr, dnsc)) // create DNS connection
MG_ERROR(("DNS connection creation failed"));
}
void mg_tcpip_mapip(struct mg_connection *c, struct mg_addr *ip) {
struct connstate *s = (struct connstate *) (c + 1);
uint8_t *l2addr = tcpip_mapip(c->mgr->ifp, ip);
if (l2addr == NULL) return;
memcpy(s->mac, l2addr, sizeof(s->mac));
}
#endif // MG_ENABLE_TCPIP