mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-17 10:49:47 +00:00
Fix IP opts
Refactor payload handling Refactor UDP port unreachable Add UDP unit tests Introduce basic IPv6 handling
This commit is contained in:
1 parent
26260a1d88
commit
0e63510708
3 files changed
+392
-159
No files matched your search
+154
-73
@@ -48,7 +48,7 @@ struct eth {
|
||||
struct ip {
|
||||
uint8_t ver; // Version
|
||||
uint8_t tos; // Unused
|
||||
uint16_t len; // Length
|
||||
uint16_t len; // Datagram length
|
||||
uint16_t id; // Unused
|
||||
uint16_t frag; // Fragmentation
|
||||
#define IP_FRAG_OFFSET_MSK 0x1fff
|
||||
@@ -61,13 +61,13 @@ struct ip {
|
||||
};
|
||||
|
||||
struct ip6 {
|
||||
uint8_t ver; // Version
|
||||
uint8_t opts[3]; // Options
|
||||
uint16_t len; // Length
|
||||
uint8_t proto; // Upper level protocol
|
||||
uint8_t ttl; // Time to live
|
||||
uint8_t src[16]; // Source IP
|
||||
uint8_t dst[16]; // Destination IP
|
||||
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
|
||||
uint8_t src[16]; // Source IP
|
||||
uint8_t dst[16]; // Destination IP
|
||||
};
|
||||
|
||||
struct icmp {
|
||||
@@ -76,6 +76,12 @@ struct icmp {
|
||||
uint16_t csum;
|
||||
};
|
||||
|
||||
struct icmp6 {
|
||||
uint8_t type;
|
||||
uint8_t code;
|
||||
uint16_t csum;
|
||||
};
|
||||
|
||||
struct arp {
|
||||
uint16_t fmt; // Format of hardware address
|
||||
uint16_t pro; // Format of protocol address
|
||||
@@ -125,6 +131,14 @@ struct dhcp {
|
||||
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)];
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
struct pkt {
|
||||
@@ -136,9 +150,11 @@ struct pkt {
|
||||
struct ip *ip;
|
||||
struct ip6 *ip6;
|
||||
struct icmp *icmp;
|
||||
struct icmp6 *icmp6;
|
||||
struct tcp *tcp;
|
||||
struct udp *udp;
|
||||
struct dhcp *dhcp;
|
||||
struct dhcp6 *dhcp6;
|
||||
};
|
||||
|
||||
static void mg_tcpip_call(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
|
||||
@@ -149,7 +165,7 @@ 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->raw.buf[pkt->raw.len] - (char *) p));
|
||||
mg_str_n((char *) p, (size_t) (&pkt->pay.buf[pkt->pay.len] - (char *) p));
|
||||
}
|
||||
|
||||
static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
|
||||
@@ -412,7 +428,7 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
uint8_t msgtype = 0, state = ifp->state;
|
||||
// perform size check first, then access fields
|
||||
uint8_t *p = pkt->dhcp->options,
|
||||
*end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
|
||||
*end = (uint8_t *) &pkt->pay.buf[pkt->pay.len];
|
||||
if (end < (uint8_t *) (pkt->dhcp + 1)) return;
|
||||
if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return;
|
||||
while (p + 1 < end && p[0] != 255) { // Parse options RFC-1533 #9
|
||||
@@ -472,7 +488,7 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
// 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 op = 0, *p = pkt->dhcp->options,
|
||||
*end = (uint8_t *) &pkt->raw.buf[pkt->raw.len];
|
||||
*end = (uint8_t *) &pkt->pay.buf[pkt->pay.len];
|
||||
// struct dhcp *req = pkt->dhcp;
|
||||
struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
|
||||
if (end < (uint8_t *) (pkt->dhcp + 1)) return;
|
||||
@@ -509,26 +525,25 @@ static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
}
|
||||
}
|
||||
|
||||
static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
|
||||
if (c == NULL) {
|
||||
// No UDP listener on this port. Should send ICMP, but keep silent.
|
||||
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));
|
||||
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");
|
||||
} 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 {
|
||||
struct connstate *s = (struct connstate *) (c + 1);
|
||||
c->rem.port = pkt->udp->sport;
|
||||
memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
|
||||
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");
|
||||
} 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);
|
||||
}
|
||||
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 *dst_mac, uint32_t dst_ip,
|
||||
@@ -568,7 +583,6 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip,
|
||||
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));
|
||||
// mg_hexdump(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len);
|
||||
return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len);
|
||||
}
|
||||
|
||||
@@ -924,23 +938,38 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
}
|
||||
|
||||
static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
uint16_t frag = mg_ntohs(pkt->ip->frag);
|
||||
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 < (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
|
||||
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->ip + 1);
|
||||
if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->ip + 1);
|
||||
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->ip + 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->ip + 1);
|
||||
if (pkt->pay.len < sizeof(*pkt->udp)) return;
|
||||
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);
|
||||
if (pkt->udp->len < pkt->pay.len) pkt->pay.len = pkt->udp->len;
|
||||
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));
|
||||
@@ -952,47 +981,108 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
|
||||
mkpay(pkt, pkt->dhcp + 1);
|
||||
rx_dhcp_server(ifp, pkt);
|
||||
} else {
|
||||
rx_udp(ifp, pkt);
|
||||
} else if (!rx_udp(ifp, pkt)) {
|
||||
// Should send ICMP Destination Unreachable for unicasts, but keep silent
|
||||
}
|
||||
} else if (pkt->ip->proto == 6) {
|
||||
uint16_t iplen, off;
|
||||
pkt->tcp = (struct tcp *) (pkt->ip + 1);
|
||||
uint8_t off;
|
||||
pkt->tcp = (struct tcp *) (pkt->pay.buf);
|
||||
if (pkt->pay.len < sizeof(*pkt->tcp)) return;
|
||||
mkpay(pkt, (uint32_t *) pkt->tcp + (pkt->tcp->off >> 4)); // may have opts
|
||||
iplen = mg_ntohs(pkt->ip->len);
|
||||
off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 4U));
|
||||
if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
|
||||
off = pkt->tcp->off >> 4; // account for opts
|
||||
if (pkt->pay.len < (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", mg_htons(pkt->ip->proto)));
|
||||
if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(pkt->ip, pkt->pay.len >= 32 ? 32 : pkt->pay.len);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ip6_handle_opt(struct ip6 *h) {
|
||||
switch(h->next) {
|
||||
case 0:
|
||||
case 43:
|
||||
case 44:
|
||||
case 50:
|
||||
case 51:
|
||||
case 60:
|
||||
case 135:
|
||||
case 139:
|
||||
case 140:
|
||||
case 253:
|
||||
case 254:
|
||||
MG_INFO(("IPv6 extension header %d", (int) h->next));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
|
||||
// MG_DEBUG(("IP %d", (int) len));
|
||||
if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
|
||||
pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
|
||||
if (pkt->pay.len < sizeof(*pkt->icmp)) return;
|
||||
mkpay(pkt, pkt->icmp + 1);
|
||||
rx_icmp(ifp, pkt);
|
||||
} else if (pkt->ip6->proto == 17) {
|
||||
pkt->udp = (struct udp *) (pkt->ip6 + 1);
|
||||
struct ip6 *hdr;
|
||||
uint16_t len;
|
||||
if (pkt->pay.len < sizeof(*pkt->ip6)) return; // Truncated
|
||||
if ((pkt->ip6->ver >> 4) != 0x6) return; // Not IPv6
|
||||
hdr = pkt->ip6;
|
||||
while (ip6_handle_opt(hdr)) ++hdr;
|
||||
// There can be link padding, take payload length from IPv6 last header
|
||||
len = mg_ntohs(hdr->plen); // IPv6 datagram reported payload length
|
||||
if ((len + (size_t) hdr - (size_t) pkt->ip6) > pkt->pay.len) return;
|
||||
pkt->pay.buf = (char *)(hdr + 1);
|
||||
pkt->pay.len = len; // strip padding
|
||||
if (hdr->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 (hdr->next == 17) {
|
||||
pkt->udp = (struct udp *) (pkt->pay.buf);
|
||||
if (pkt->pay.len < sizeof(*pkt->udp)) return;
|
||||
// MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport),
|
||||
// mg_htons(udp->dport)));
|
||||
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)) {
|
||||
pkt->dhcp6 = (struct dhcp6 *) (pkt->udp + 1);
|
||||
mkpay(pkt, pkt->dhcp6 + 1);
|
||||
// rx_dhcp6_client(ifp, pkt);
|
||||
} 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);
|
||||
} else if (!rx_udp(ifp, pkt)) {
|
||||
// Should send ICMPv6 Destination Unreachable for unicasts, keep silent
|
||||
}
|
||||
} else if (hdr->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 < sizeof(*pkt->tcp) + 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", mg_htons(hdr->next)));
|
||||
if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(pkt->ip6, pkt->pay.len >= 32 ? 32 : pkt->pay.len);
|
||||
}
|
||||
}
|
||||
|
||||
static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
|
||||
struct pkt pkt;
|
||||
memset(&pkt, 0, sizeof(pkt));
|
||||
pkt.raw.buf = (char *) buf;
|
||||
pkt.raw.len = len;
|
||||
pkt.eth = (struct eth *) buf;
|
||||
// mg_hexdump(buf, len > 16 ? 16: len);
|
||||
pkt.pay.buf = pkt.raw.buf = (char *) buf;
|
||||
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 &&
|
||||
@@ -1003,28 +1093,19 @@ static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
|
||||
len -= 4; // TODO(scaprile): check on bigendian
|
||||
crc = mg_crc32(0, (const char *) buf, len);
|
||||
if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc))) return;
|
||||
pkt.pay.len = len;
|
||||
}
|
||||
mkpay(&pkt, pkt.eth + 1);
|
||||
if (pkt.eth->type == mg_htons(0x806)) {
|
||||
pkt.arp = (struct arp *) (pkt.eth + 1);
|
||||
if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return; // Truncated
|
||||
pkt.arp = (struct arp *) (pkt.pay.buf);
|
||||
if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.pay.len) return; // Truncated
|
||||
mg_tcpip_call(ifp, MG_TCPIP_EV_ARP, &pkt.raw);
|
||||
rx_arp(ifp, &pkt);
|
||||
} else if (pkt.eth->type == mg_htons(0x86dd)) {
|
||||
pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
|
||||
if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return; // Truncated
|
||||
if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP
|
||||
mkpay(&pkt, pkt.ip6 + 1);
|
||||
pkt.ip6 = (struct ip6 *) (pkt.pay.buf);
|
||||
rx_ip6(ifp, &pkt);
|
||||
} else if (pkt.eth->type == mg_htons(0x800)) {
|
||||
pkt.ip = (struct ip *) (pkt.eth + 1);
|
||||
if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated
|
||||
// Truncate frame to what IP header tells us
|
||||
if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) {
|
||||
pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
|
||||
}
|
||||
if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated
|
||||
if ((pkt.ip->ver >> 4) != 4) return; // Not IP
|
||||
mkpay(&pkt, pkt.ip + 1);
|
||||
pkt.ip = (struct ip *) (pkt.pay.buf);
|
||||
rx_ip(ifp, &pkt);
|
||||
} else {
|
||||
MG_DEBUG(("Unknown eth type %x", mg_htons(pkt.eth->type)));
|
||||
|
||||
Reference in new issue
Block a user