Compare commits

..
3 Commits
Author SHA1 Message Date
cpq fc71bc0588 Add skeleton for the radio mesh simulator 2026-11-01 11:05:59 +00:00
Sergey Lyubka 08305b9000 Merge pull request #3751 from cesanta/dhcp
Fix: DHCP client stuck in REQUESTING state forever if the REQUEST or ACK is lost
2026-10-30 12:43:26 +00:00
cpq 69e8c0a92f Fix: DHCP client stuck in REQUESTING state forever if the REQUEST or ACK is lost 2026-10-30 09:42:12 +00:00
29 changed files with 910 additions and 1403 deletions

No files matched your search

+58 -216
View File
@@ -167,7 +167,6 @@ fail:
#endif #endif
#include <stdarg.h> #include <stdarg.h>
#if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS #if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS
@@ -278,57 +277,6 @@ static struct mg_connection *bsd_new_conn(struct mg_bsd_sock *s) {
return c; return c;
} }
// Mirror net_builtin.c::trim_len() logic
static bool bsd_udp_too_big(struct mg_connection *c, size_t len) {
struct mg_tcpip_if *ifp = c->mgr->ifp;
size_t hlen = c->rem.is_ip6 ? 48 : 32; // IP plus UDP headers
size_t cap = ifp->mtu - hlen;
size_t room = ifp->tx.len - (ifp->framesize - ifp->l2mtu) - hlen;
return len > cap || len > room;
}
static bool bsd_udp_send(struct mg_connection *c, uint32_t ip, uint16_t port,
const void *buf, size_t len) {
struct mg_bsd_udp_frame *h;
size_t off = c->send.len, total = sizeof(*h) + len;
if (mg_iobuf_add(&c->send, off, NULL, total) != total) return false;
h = (struct mg_bsd_udp_frame *) (c->send.buf + off);
h->ip = ip;
h->port = port;
h->len = (uint16_t) len;
memcpy(h + 1, buf, len);
return true;
}
static size_t bsd_recv(struct mg_bsd_sock *s, void *buf, size_t len,
int flags, struct sockaddr *src, socklen_t *addrlen) {
struct mg_connection *c = s->c;
size_t n;
if (!s->is_udp) {
n = c->recv.len < len ? c->recv.len : len;
memcpy(buf, c->recv.buf, n);
if (!(flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
} else {
struct mg_bsd_udp_frame *h = (struct mg_bsd_udp_frame *) c->recv.buf;
assert(c->recv.len >= sizeof(*h));
assert(h->len <= c->recv.len - sizeof(*h));
n = h->len < len ? h->len : len;
memcpy(buf, h + 1, n);
if (src != NULL && addrlen != NULL) {
struct sockaddr_in *sa = (struct sockaddr_in *) src;
sa->sin_family = AF_INET;
sa->sin_port = h->port;
sa->sin_addr.s_addr = h->ip;
*addrlen = sizeof(*sa);
}
if (!(flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, sizeof(*h) + h->len);
}
return n;
}
#if MG_ENABLE_FREERTOS #if MG_ENABLE_FREERTOS
// Complete operations blocked on c before Mongoose destroys it. // Complete operations blocked on c before Mongoose destroys it.
static void bsd_finish_waiters(struct mg_bsd_sock *s, bool error) { static void bsd_finish_waiters(struct mg_bsd_sock *s, bool error) {
@@ -509,9 +457,9 @@ static int bsd_ready(struct mg_bsd_sock *s) {
return 0; return 0;
} }
if (s->c->recv.len > 0 || s->c->is_draining) ready |= BSD_READ; if (s->c->recv.len > 0 || s->c->is_draining) ready |= BSD_READ;
if ((s->is_udp || !s->c->is_connecting) && !s->c->is_closing && if (!s->is_udp && !s->c->is_connecting && !s->c->is_closing &&
!s->c->is_draining) !s->c->is_draining)
ready |= BSD_WRITE; // UDP socket or established TCP socket ready |= BSD_WRITE; // Established TCP socket
return ready; return ready;
} }
@@ -525,7 +473,7 @@ static void bsd_close(struct mg_bsd_sock *s) {
if (t->orphan && t->listener == s) { if (t->orphan && t->listener == s) {
if (t->c != NULL) { if (t->c != NULL) {
t->c->fn_data = NULL; t->c->fn_data = NULL;
t->c->is_draining = 1; mg_close_conn(t->c);
t->c = NULL; t->c = NULL;
} }
memset(t, 0, sizeof(*t)); // Release orphan memset(t, 0, sizeof(*t)); // Release orphan
@@ -533,7 +481,7 @@ static void bsd_close(struct mg_bsd_sock *s) {
} }
if (s->c != NULL) { if (s->c != NULL) {
s->c->fn_data = NULL; s->c->fn_data = NULL;
s->c->is_draining = 1; mg_close_conn(s->c);
s->c = NULL; s->c = NULL;
} }
memset(s, 0, sizeof(*s)); // Release socket memset(s, 0, sizeof(*s)); // Release socket
@@ -712,8 +660,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
cmd->result = -1, cmd->err = ECONNREFUSED; cmd->result = -1, cmd->err = ECONNREFUSED;
return true; return true;
} }
if (s->is_udp || !c->is_connecting) { if (!c->is_connecting) {
cmd->result = 0; // UDP may still resolve ARP; TCP waits for SYN/SYN-ACK cmd->result = 0;
return true; return true;
} }
if (s->nonblock) { if (s->nonblock) {
@@ -737,8 +685,11 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
return true; return true;
} }
if (c->recv.len > 0) { if (c->recv.len > 0) {
cmd->result = (int) bsd_recv(s, cmd->op_recv.buf, cmd->op_recv.len, size_t n = cmd->op_recv.len;
cmd->op_recv.flags, NULL, NULL); if (c->recv.len < n) n = c->recv.len;
memcpy(cmd->op_recv.buf, c->recv.buf, n);
if (!(cmd->op_recv.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
cmd->result = (int) n;
return true; return true;
} }
if (s->err != 0) { if (s->err != 0) {
@@ -759,17 +710,10 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
case BSD_OP_SEND: case BSD_OP_SEND:
if ((s = bsd_get(cmd->op_send.fd)) == NULL) { if ((s = bsd_get(cmd->op_send.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF; cmd->result = -1, cmd->err = EBADF;
} else if (s->err != 0 || s->closed || } else if ((c = s->c) == NULL || s->err != 0 || c->is_closing ||
((c = s->c) != NULL && (c->is_closing || c->is_draining))) { c->is_draining) {
cmd->result = -1, cmd->err = EPIPE; cmd->result = -1, cmd->err = EPIPE;
} else if (c == NULL || (s->is_udp && !c->is_client)) { } else if (mg_send(c, cmd->op_send.buf, cmd->op_send.len)) {
cmd->result = -1, cmd->err = s->is_udp ? EDESTADDRREQ : EPIPE;
} else if (s->is_udp && bsd_udp_too_big(c, cmd->op_send.len)) {
cmd->result = -1, cmd->err = EMSGSIZE;
} else if ((s->is_udp ? bsd_udp_send(c, c->rem.addr.ip4, c->rem.port,
cmd->op_send.buf, cmd->op_send.len)
: mg_send(c, cmd->op_send.buf,
cmd->op_send.len))) {
cmd->result = (int) cmd->op_send.len; cmd->result = (int) cmd->op_send.len;
} else if (s->nonblock || (cmd->op_send.flags & MSG_DONTWAIT)) { } else if (s->nonblock || (cmd->op_send.flags & MSG_DONTWAIT)) {
cmd->result = -1, cmd->err = EAGAIN; cmd->result = -1, cmd->err = EAGAIN;
@@ -784,20 +728,16 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
case BSD_OP_SENDTO: case BSD_OP_SENDTO:
if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) { if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF; cmd->result = -1, cmd->err = EBADF;
} else if (!s->is_udp) {
cmd->result = -1, cmd->err = EOPNOTSUPP;
} else if ((c = s->c) == NULL || s->err != 0 || c->is_closing || } else if ((c = s->c) == NULL || s->err != 0 || c->is_closing ||
c->is_draining) { c->is_draining) {
cmd->result = -1, cmd->err = EPIPE; cmd->result = -1, cmd->err = EPIPE;
} else if (c->is_client) {
cmd->result = -1, cmd->err = EISCONN;
} else { } else {
const struct sockaddr_in *sa = const struct sockaddr_in *sa =
(const struct sockaddr_in *) cmd->op_sendto.addr; (const struct sockaddr_in *) cmd->op_sendto.addr;
if (bsd_udp_too_big(c, cmd->op_sendto.len)) { c->rem.port = sa->sin_port;
cmd->result = -1, cmd->err = EMSGSIZE; c->rem.addr.ip4 = sa->sin_addr.s_addr;
} else if (bsd_udp_send(c, sa->sin_addr.s_addr, sa->sin_port, c->rem.is_ip6 = false;
cmd->op_sendto.buf, cmd->op_sendto.len)) { if (mg_send(c, cmd->op_sendto.buf, cmd->op_sendto.len)) {
cmd->result = (int) cmd->op_sendto.len; cmd->result = (int) cmd->op_sendto.len;
} else if (s->nonblock || (cmd->op_sendto.flags & MSG_DONTWAIT)) { } else if (s->nonblock || (cmd->op_sendto.flags & MSG_DONTWAIT)) {
cmd->result = -1, cmd->err = EAGAIN; cmd->result = -1, cmd->err = EAGAIN;
@@ -814,9 +754,6 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) { if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF; cmd->result = -1, cmd->err = EBADF;
return true; return true;
} else if (!s->is_udp) {
cmd->result = -1, cmd->err = EOPNOTSUPP;
return true;
} }
if ((c = s->c) == NULL) { if ((c = s->c) == NULL) {
cmd->result = s->closed && s->err == 0 ? 0 : -1; cmd->result = s->closed && s->err == 0 ? 0 : -1;
@@ -824,11 +761,18 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
return true; return true;
} }
if (c->recv.len > 0) { if (c->recv.len > 0) {
cmd->result = (int) bsd_recv(s, cmd->op_recvfrom.buf, size_t n = cmd->op_recvfrom.len;
cmd->op_recvfrom.len, if (c->recv.len < n) n = c->recv.len;
cmd->op_recvfrom.flags, memcpy(cmd->op_recvfrom.buf, c->recv.buf, n);
cmd->op_recvfrom.src, if (!(cmd->op_recvfrom.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
cmd->op_recvfrom.addrlen); if (cmd->op_recvfrom.src != NULL && cmd->op_recvfrom.addrlen != NULL) {
struct sockaddr_in *sa = (struct sockaddr_in *) cmd->op_recvfrom.src;
sa->sin_family = AF_INET;
sa->sin_port = c->rem.port;
sa->sin_addr.s_addr = c->rem.addr.ip4;
*cmd->op_recvfrom.addrlen = sizeof(*sa);
}
cmd->result = (int) n;
return true; return true;
} }
if (s->err != 0) { if (s->err != 0) {
@@ -9330,36 +9274,6 @@ static void l2addr_resolved(struct mg_connection *c);
static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem, static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem,
bool is_udp, struct pkt *pkt); bool is_udp, struct pkt *pkt);
#if MG_ENABLE_BSD_SOCKETS
static uint8_t *sendto_cache_find(struct mg_tcpip_if *ifp, uint32_t ip) {
struct mg_tcpip_sendto_entry *a;
for (a = ifp->sendto; a < ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE; a++) {
if (a->ip == ip) return (uint8_t *) &a->addr;
}
return NULL;
}
static void sendto_cache_add(struct mg_tcpip_if *ifp, uint32_t ip,
const uint8_t *addr) {
struct mg_tcpip_sendto_entry *a;
for (a = ifp->sendto; a < ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE; a++) {
if (a->ip == ip) return; // already cached, skip
if (a->ip == 0) break; // not present, first empty slot
}
if (a == ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE) { // no room
memmove(ifp->sendto, ifp->sendto + 1,
sizeof(ifp->sendto[0]) * (MG_TCPIP_SENDTO_CACHE_SIZE - 1));
a--; // make room and point to last entry (just made)
}
a->ip = ip; // populate this entry
memcpy(&a->addr, addr, sizeof(a->addr));
}
static void sendto_cache_clear(struct mg_tcpip_if *ifp) {
memset(ifp->sendto, 0, sizeof(ifp->sendto));
}
#endif
// RFC826, ARP assumes Ethernet MAC addresses // RFC826, ARP assumes Ethernet MAC addresses
static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) { if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
@@ -9485,6 +9399,7 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
: pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->dhcp->siaddr ? pkt->dhcp->siaddr
: pkt->ip->src); : pkt->ip->src);
ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state
ifp->lease_expire = ifp->now + 5000; // No ACK by then: start over
} else if (msgtype == 5) { // DHCPACK } else if (msgtype == 5) { // DHCPACK
if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP
uint64_t rand; uint64_t rand;
@@ -9965,57 +9880,30 @@ static void proto_error(struct mg_connection *c, bool r, const char *f, ...) {
static bool 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); struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
struct connstate *s; struct connstate *s;
struct mg_addr rem;
uint8_t *l2addr; uint8_t *l2addr;
size_t len = pkt->pay.len;
if (c == NULL) return false; // No UDP listener on this port if (c == NULL) return false; // No UDP listener on this port
s = (struct connstate *) (c + 1); s = (struct connstate *) (c + 1);
c->rem.port = pkt->udp->sport;
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
if (c->loc.is_ip6) { // matching of v4/v6 to dest is done bt getpeer() if (c->loc.is_ip6) { // matching of v4/v6 to dest is done bt getpeer()
if (!udp6csum_ok(pkt->ip6, pkt->udp)) return false; if (!udp6csum_ok(pkt->ip6, pkt->udp)) return false;
if (c->is_bsd && c->is_client && c->rem.addr.ip6[0] = pkt->ip6->src[0],
(c->rem.port != pkt->udp->sport || c->rem.addr.ip6[1] = pkt->ip6->src[1], c->rem.is_ip6 = true;
!MG_IP6MATCH(c->rem.addr.ip6, pkt->ip6->src)))
return true;
rem.addr.ip6[0] = pkt->ip6->src[0],
rem.addr.ip6[1] = pkt->ip6->src[1], rem.is_ip6 = true;
} else } else
#endif #endif
{ {
if (!udpcsum_ok(pkt->ip, pkt->udp)) return false; if (!udpcsum_ok(pkt->ip, pkt->udp)) return false;
if (c->is_bsd && c->is_client && c->rem.addr.ip4 = pkt->ip->src;
(c->rem.port != pkt->udp->sport || c->rem.addr.ip4 != pkt->ip->src))
return true;
rem.addr.ip4 = pkt->ip->src, rem.is_ip6 = false;
} }
rem.port = pkt->udp->sport; if ((l2addr = get_return_l2addr(ifp, &c->rem, true, pkt)) == NULL)
if (!c->is_bsd || c->is_client) c->rem = rem;
if ((l2addr = get_return_l2addr(ifp, &rem, true, pkt)) == NULL)
return false; // safety net for lousy networks return false; // safety net for lousy networks
if (!c->is_bsd || c->is_client) memcpy(s->mac, l2addr, sizeof(s->mac)); memcpy(s->mac, l2addr, sizeof(s->mac));
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && !rem.is_ip6 && MG_IPISLOCAL(ifp, rem.addr.ip4))
sendto_cache_add(ifp, rem.addr.ip4, l2addr);
#endif
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd) len += sizeof(struct mg_bsd_udp_frame);
#endif
if (c->recv.len >= MG_MAX_RECV_SIZE) { if (c->recv.len >= MG_MAX_RECV_SIZE) {
proto_error(c, false, "max_recv_buf_size reached"); proto_error(c, false, "max_recv_buf_size reached");
} else if (c->recv.size - c->recv.len < len && } else if (c->recv.size - c->recv.len < pkt->pay.len &&
!mg_iobuf_resize(&c->recv, c->recv.len + len)) { !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
proto_error(c, false, "oom"); proto_error(c, false, "oom");
} else { } else {
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd) {
struct mg_bsd_udp_frame *h =
(struct mg_bsd_udp_frame *) &c->recv.buf[c->recv.len];
h->ip = rem.addr.ip4;
h->port = rem.port;
h->len = (uint16_t) pkt->pay.len;
c->recv.len += sizeof(*h);
}
#endif
memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len); memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
c->recv.len += pkt->pay.len; c->recv.len += pkt->pay.len;
mg_call(c, MG_EV_READ, &pkt->pay.len); mg_call(c, MG_EV_READ, &pkt->pay.len);
@@ -10819,6 +10707,9 @@ static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) {
if (ifp->enable_dhcp_client && s1) { if (ifp->enable_dhcp_client && s1) {
if (ifp->state == MG_TCPIP_STATE_UP) { if (ifp->state == MG_TCPIP_STATE_UP) {
tx_dhcp_discover(ifp); // INIT (4.4.1) tx_dhcp_discover(ifp); // INIT (4.4.1)
} else if (ifp->state == MG_TCPIP_STATE_REQ &&
ifp->now >= ifp->lease_expire) {
ifp->state = MG_TCPIP_STATE_UP; // REQUEST or ACK lost: DISCOVER again
} else if (ifp->state == MG_TCPIP_STATE_READY && } else if (ifp->state == MG_TCPIP_STATE_READY &&
ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
if (ifp->now >= ifp->lease_expire) { if (ifp->now >= ifp->lease_expire) {
@@ -11068,9 +10959,6 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
static void close_conn(struct mg_connection *c) { static void close_conn(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1); struct connstate *s = (struct connstate *) (c + 1);
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && c->is_udp) sendto_cache_clear(c->mgr->ifp);
#endif
mg_iobuf_free(&s->txq); mg_iobuf_free(&s->txq);
mg_close_conn(c); mg_close_conn(c);
} }
@@ -11092,11 +10980,6 @@ static void send_syn(struct mg_connection *c) {
static void l2addr_resolved(struct mg_connection *c) { static void l2addr_resolved(struct mg_connection *c) {
if (c->is_udp) { if (c->is_udp) {
c->is_connecting = 0; c->is_connecting = 0;
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd)
sendto_cache_add(c->mgr->ifp, c->rem.addr.ip4,
((struct connstate *) (c + 1))->mac);
#endif
mg_call(c, MG_EV_CONNECT, NULL); mg_call(c, MG_EV_CONNECT, NULL);
} else { } else {
send_syn(c); send_syn(c);
@@ -11111,25 +10994,20 @@ void mg_connect_resolved(struct mg_connection *c) {
s->retransmit = ifp->enable_tcp_retransmit; s->retransmit = ifp->enable_tcp_retransmit;
s->txq.align = MG_TCPIP_TXQ_ALIGN; s->txq.align = MG_TCPIP_TXQ_ALIGN;
c->is_resolving = 0; c->is_resolving = 0;
if (c->loc.port == 0) { if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE;
if (ifp->eport < MG_EPHEMERAL_PORT_BASE) c->loc.port = mg_htons(ifp->eport++);
ifp->eport = MG_EPHEMERAL_PORT_BASE;
c->loc.port = mg_htons(ifp->eport++);
} // otherwise retain an existing bound port
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
if (c->rem.is_ip6) { if (c->rem.is_ip6) {
if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0 && if (c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll
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]; c->loc.addr.ip6[0] = ifp->ip6ll[0], c->loc.addr.ip6[1] = ifp->ip6ll[1];
} else if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0) { } else { // use global address
// Use global address
c->loc.addr.ip6[0] = ifp->ip6[0], c->loc.addr.ip6[1] = ifp->ip6[1]; c->loc.addr.ip6[0] = ifp->ip6[0], c->loc.addr.ip6[1] = ifp->ip6[1];
} // otherwise retain an existing bound address }
c->loc.is_ip6 = true; c->loc.is_ip6 = true;
} else } else
#endif #endif
{ {
if (c->loc.addr.ip4 == 0) c->loc.addr.ip4 = ifp->ip; c->loc.addr.ip4 = ifp->ip;
} }
MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port, MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port,
&c->rem)); &c->rem));
@@ -11160,20 +11038,11 @@ void mg_connect_resolved(struct mg_connection *c) {
uint32_t rem_ip = c->rem.addr.ip4; uint32_t rem_ip = c->rem.addr.ip4;
if (ifp->ip && MG_IPISLOCAL(ifp, rem_ip) && if (ifp->ip && MG_IPISLOCAL(ifp, rem_ip) &&
rem_ip != ifp->gw) { // skip if gw (onstatechange -> ARP) rem_ip != ifp->gw) { // skip if gw (onstatechange -> ARP)
// We're in the same LAN // If we're in the same LAN, fire an ARP lookup.
#if MG_ENABLE_BSD_SOCKETS MG_DEBUG(("%lu ARP lookup...", c->id));
if (c->is_bsd && c->is_udp && // see if we already have L2 data mg_tcpip_arp_request(ifp, rem_ip, NULL);
(l2addr = sendto_cache_find(ifp, rem_ip)) != NULL) { settmout(c, MIP_TTYPE_ARP);
memcpy(s->mac, l2addr, sizeof(s->mac)); c->is_arplooking = 1;
l2addr_resolved(c);
} else
#endif
{ // 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) { } else if (ifp->gw_ready) {
memcpy(s->mac, ifp->gwmac, sizeof(s->mac)); memcpy(s->mac, ifp->gwmac, sizeof(s->mac));
l2addr_resolved(c); l2addr_resolved(c);
@@ -11193,36 +11062,13 @@ bool mg_open_listener(struct mg_connection *c, const char *url) {
} }
static void write_conn(struct mg_connection *c) { static void write_conn(struct mg_connection *c) {
long len; long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
size_t off = 0; : mg_io_send(c, c->send.buf, c->send.len);
bool empty = false;
#if MG_ENABLE_BSD_SOCKETS
struct mg_bsd_udp_frame *h;
if (c->is_bsd && c->is_udp) {
if (c->is_connecting) return;
h = (struct mg_bsd_udp_frame *) c->send.buf;
assert(c->send.len >= sizeof(*h));
assert(h->len <= c->send.len - sizeof(*h));
c->rem.port = h->port;
c->rem.is_ip6 = false;
if (!c->is_client && c->rem.addr.ip4 != h->ip) {
c->rem.addr.ip4 = h->ip;
mg_connect_resolved(c);
if (c->is_connecting) return;
}
len = mg_io_send(c, h + 1, h->len);
off = sizeof(*h), empty = h->len == 0;
} else
#endif
{
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 // TODO(): mg_tls_send() may return 0 forever on steady OOM
if (len == MG_IO_ERR) { if (len == MG_IO_ERR) {
proto_error(c, false, "tx err"); proto_error(c, false, "tx err");
} else if (len > 0 || empty) { } else if (len > 0) {
mg_iobuf_del(&c->send, 0, off + (size_t) len); mg_iobuf_del(&c->send, 0, (size_t) len);
mg_call(c, MG_EV_WRITE, &len); mg_call(c, MG_EV_WRITE, &len);
} }
} }
@@ -15531,11 +15377,7 @@ static long recv_raw(struct mg_connection *c, void *buf, size_t len) {
union usa usa; union usa usa;
socklen_t slen = tousa(&c->rem, &usa); socklen_t slen = tousa(&c->rem, &usa);
n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen); n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen);
if (n > 0) { if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
struct mg_addr rem;
tomgaddr(&usa, &rem, slen != sizeof(usa.sin));
c->rem = rem;
}
} else { } else {
n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING); n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
} }
-41
View File
@@ -4620,17 +4620,6 @@ struct mg_tcpip_driver {
typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev, typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev,
void *ev_data); void *ev_data);
#if MG_ENABLE_BSD_SOCKETS
#ifndef MG_TCPIP_SENDTO_CACHE_SIZE
#define MG_TCPIP_SENDTO_CACHE_SIZE 4
#endif
struct mg_tcpip_sendto_entry {
uint32_t ip;
struct mg_l2addr addr;
};
#endif
// clang-format off // clang-format off
// Events delivered to mg_tcpip_if.fn (user) and mg_tcpip_if.pfn (driver). // Events delivered to mg_tcpip_if.fn (user) and mg_tcpip_if.pfn (driver).
// Each entry shows the ev_data type to cast inside the handler. // Each entry shows the ev_data type to cast inside the handler.
@@ -4693,9 +4682,6 @@ struct mg_tcpip_if {
enum mg_l2type l2type; // Layer-2 type: Ethernet, PPP, etc. (see l2.h) enum mg_l2type l2type; // Layer-2 type: Ethernet, PPP, etc. (see l2.h)
union mg_l2data l2data; // Layer-2 config and state (see l2.h) union mg_l2data l2data; // Layer-2 config and state (see l2.h)
char *dns4_url; // Active DNS server URL (set from DHCP or config) char *dns4_url; // Active DNS server URL (set from DHCP or config)
#if MG_ENABLE_BSD_SOCKETS
struct mg_tcpip_sendto_entry sendto[MG_TCPIP_SENDTO_CACHE_SIZE]; // Local IPv4 neighbors
#endif
uint64_t now; // Current time in ms (updated each mg_mgr_poll) uint64_t now; // Current time in ms (updated each mg_mgr_poll)
uint64_t timer_1000ms; // Timestamp of last 1-second tick uint64_t timer_1000ms; // Timestamp of last 1-second tick
uint64_t lease_expire; // DHCP lease expiry time in ms uint64_t lease_expire; // DHCP lease expiry time in ms
@@ -4907,37 +4893,10 @@ void mg_bsd_poll(struct mg_mgr *);
#ifndef EPIPE #ifndef EPIPE
#define EPIPE 32 #define EPIPE 32
#endif #endif
#ifndef EOPNOTSUPP
#define EOPNOTSUPP 95
#endif
#ifndef EISCONN
#define EISCONN 106
#endif
#ifndef EDESTADDRREQ
#define EDESTADDRREQ 121
#endif
#ifndef EMSGSIZE
#define EMSGSIZE 90
#endif
#ifndef EPROTONOSUPPORT #ifndef EPROTONOSUPPORT
#define EPROTONOSUPPORT 93 #define EPROTONOSUPPORT 93
#endif #endif
#if defined(__DCC__)
#pragma pack(1)
#else
#pragma pack(push, 1)
#endif
struct mg_bsd_udp_frame {
uint32_t ip;
uint16_t port, len;
};
#if defined(__DCC__)
#pragma pack(0)
#else
#pragma pack(pop)
#endif
#if MG_ENABLE_BSD_PROTOTYPES #if MG_ENABLE_BSD_PROTOTYPES
typedef unsigned int socklen_t; typedef unsigned int socklen_t;
typedef unsigned long nfds_t; typedef unsigned long nfds_t;
+30 -86
View File
@@ -1,6 +1,5 @@
#include "bsd.h" #include "bsd.h"
#include "dns.h" #include "dns.h"
#include "util.h"
#include <stdarg.h> #include <stdarg.h>
#if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS #if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS
@@ -111,57 +110,6 @@ static struct mg_connection *bsd_new_conn(struct mg_bsd_sock *s) {
return c; return c;
} }
// Mirror net_builtin.c::trim_len() logic
static bool bsd_udp_too_big(struct mg_connection *c, size_t len) {
struct mg_tcpip_if *ifp = c->mgr->ifp;
size_t hlen = c->rem.is_ip6 ? 48 : 32; // IP plus UDP headers
size_t cap = ifp->mtu - hlen;
size_t room = ifp->tx.len - (ifp->framesize - ifp->l2mtu) - hlen;
return len > cap || len > room;
}
static bool bsd_udp_send(struct mg_connection *c, uint32_t ip, uint16_t port,
const void *buf, size_t len) {
struct mg_bsd_udp_frame *h;
size_t off = c->send.len, total = sizeof(*h) + len;
if (mg_iobuf_add(&c->send, off, NULL, total) != total) return false;
h = (struct mg_bsd_udp_frame *) (c->send.buf + off);
h->ip = ip;
h->port = port;
h->len = (uint16_t) len;
memcpy(h + 1, buf, len);
return true;
}
static size_t bsd_recv(struct mg_bsd_sock *s, void *buf, size_t len,
int flags, struct sockaddr *src, socklen_t *addrlen) {
struct mg_connection *c = s->c;
size_t n;
if (!s->is_udp) {
n = c->recv.len < len ? c->recv.len : len;
memcpy(buf, c->recv.buf, n);
if (!(flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
} else {
struct mg_bsd_udp_frame *h = (struct mg_bsd_udp_frame *) c->recv.buf;
assert(c->recv.len >= sizeof(*h));
assert(h->len <= c->recv.len - sizeof(*h));
n = h->len < len ? h->len : len;
memcpy(buf, h + 1, n);
if (src != NULL && addrlen != NULL) {
struct sockaddr_in *sa = (struct sockaddr_in *) src;
sa->sin_family = AF_INET;
sa->sin_port = h->port;
sa->sin_addr.s_addr = h->ip;
*addrlen = sizeof(*sa);
}
if (!(flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, sizeof(*h) + h->len);
}
return n;
}
#if MG_ENABLE_FREERTOS #if MG_ENABLE_FREERTOS
// Complete operations blocked on c before Mongoose destroys it. // Complete operations blocked on c before Mongoose destroys it.
static void bsd_finish_waiters(struct mg_bsd_sock *s, bool error) { static void bsd_finish_waiters(struct mg_bsd_sock *s, bool error) {
@@ -342,9 +290,9 @@ static int bsd_ready(struct mg_bsd_sock *s) {
return 0; return 0;
} }
if (s->c->recv.len > 0 || s->c->is_draining) ready |= BSD_READ; if (s->c->recv.len > 0 || s->c->is_draining) ready |= BSD_READ;
if ((s->is_udp || !s->c->is_connecting) && !s->c->is_closing && if (!s->is_udp && !s->c->is_connecting && !s->c->is_closing &&
!s->c->is_draining) !s->c->is_draining)
ready |= BSD_WRITE; // UDP socket or established TCP socket ready |= BSD_WRITE; // Established TCP socket
return ready; return ready;
} }
@@ -358,7 +306,7 @@ static void bsd_close(struct mg_bsd_sock *s) {
if (t->orphan && t->listener == s) { if (t->orphan && t->listener == s) {
if (t->c != NULL) { if (t->c != NULL) {
t->c->fn_data = NULL; t->c->fn_data = NULL;
t->c->is_draining = 1; mg_close_conn(t->c);
t->c = NULL; t->c = NULL;
} }
memset(t, 0, sizeof(*t)); // Release orphan memset(t, 0, sizeof(*t)); // Release orphan
@@ -366,7 +314,7 @@ static void bsd_close(struct mg_bsd_sock *s) {
} }
if (s->c != NULL) { if (s->c != NULL) {
s->c->fn_data = NULL; s->c->fn_data = NULL;
s->c->is_draining = 1; mg_close_conn(s->c);
s->c = NULL; s->c = NULL;
} }
memset(s, 0, sizeof(*s)); // Release socket memset(s, 0, sizeof(*s)); // Release socket
@@ -545,8 +493,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
cmd->result = -1, cmd->err = ECONNREFUSED; cmd->result = -1, cmd->err = ECONNREFUSED;
return true; return true;
} }
if (s->is_udp || !c->is_connecting) { if (!c->is_connecting) {
cmd->result = 0; // UDP may still resolve ARP; TCP waits for SYN/SYN-ACK cmd->result = 0;
return true; return true;
} }
if (s->nonblock) { if (s->nonblock) {
@@ -570,8 +518,11 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
return true; return true;
} }
if (c->recv.len > 0) { if (c->recv.len > 0) {
cmd->result = (int) bsd_recv(s, cmd->op_recv.buf, cmd->op_recv.len, size_t n = cmd->op_recv.len;
cmd->op_recv.flags, NULL, NULL); if (c->recv.len < n) n = c->recv.len;
memcpy(cmd->op_recv.buf, c->recv.buf, n);
if (!(cmd->op_recv.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
cmd->result = (int) n;
return true; return true;
} }
if (s->err != 0) { if (s->err != 0) {
@@ -592,17 +543,10 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
case BSD_OP_SEND: case BSD_OP_SEND:
if ((s = bsd_get(cmd->op_send.fd)) == NULL) { if ((s = bsd_get(cmd->op_send.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF; cmd->result = -1, cmd->err = EBADF;
} else if (s->err != 0 || s->closed || } else if ((c = s->c) == NULL || s->err != 0 || c->is_closing ||
((c = s->c) != NULL && (c->is_closing || c->is_draining))) { c->is_draining) {
cmd->result = -1, cmd->err = EPIPE; cmd->result = -1, cmd->err = EPIPE;
} else if (c == NULL || (s->is_udp && !c->is_client)) { } else if (mg_send(c, cmd->op_send.buf, cmd->op_send.len)) {
cmd->result = -1, cmd->err = s->is_udp ? EDESTADDRREQ : EPIPE;
} else if (s->is_udp && bsd_udp_too_big(c, cmd->op_send.len)) {
cmd->result = -1, cmd->err = EMSGSIZE;
} else if ((s->is_udp ? bsd_udp_send(c, c->rem.addr.ip4, c->rem.port,
cmd->op_send.buf, cmd->op_send.len)
: mg_send(c, cmd->op_send.buf,
cmd->op_send.len))) {
cmd->result = (int) cmd->op_send.len; cmd->result = (int) cmd->op_send.len;
} else if (s->nonblock || (cmd->op_send.flags & MSG_DONTWAIT)) { } else if (s->nonblock || (cmd->op_send.flags & MSG_DONTWAIT)) {
cmd->result = -1, cmd->err = EAGAIN; cmd->result = -1, cmd->err = EAGAIN;
@@ -617,20 +561,16 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
case BSD_OP_SENDTO: case BSD_OP_SENDTO:
if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) { if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF; cmd->result = -1, cmd->err = EBADF;
} else if (!s->is_udp) {
cmd->result = -1, cmd->err = EOPNOTSUPP;
} else if ((c = s->c) == NULL || s->err != 0 || c->is_closing || } else if ((c = s->c) == NULL || s->err != 0 || c->is_closing ||
c->is_draining) { c->is_draining) {
cmd->result = -1, cmd->err = EPIPE; cmd->result = -1, cmd->err = EPIPE;
} else if (c->is_client) {
cmd->result = -1, cmd->err = EISCONN;
} else { } else {
const struct sockaddr_in *sa = const struct sockaddr_in *sa =
(const struct sockaddr_in *) cmd->op_sendto.addr; (const struct sockaddr_in *) cmd->op_sendto.addr;
if (bsd_udp_too_big(c, cmd->op_sendto.len)) { c->rem.port = sa->sin_port;
cmd->result = -1, cmd->err = EMSGSIZE; c->rem.addr.ip4 = sa->sin_addr.s_addr;
} else if (bsd_udp_send(c, sa->sin_addr.s_addr, sa->sin_port, c->rem.is_ip6 = false;
cmd->op_sendto.buf, cmd->op_sendto.len)) { if (mg_send(c, cmd->op_sendto.buf, cmd->op_sendto.len)) {
cmd->result = (int) cmd->op_sendto.len; cmd->result = (int) cmd->op_sendto.len;
} else if (s->nonblock || (cmd->op_sendto.flags & MSG_DONTWAIT)) { } else if (s->nonblock || (cmd->op_sendto.flags & MSG_DONTWAIT)) {
cmd->result = -1, cmd->err = EAGAIN; cmd->result = -1, cmd->err = EAGAIN;
@@ -647,9 +587,6 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) { if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) {
cmd->result = -1, cmd->err = EBADF; cmd->result = -1, cmd->err = EBADF;
return true; return true;
} else if (!s->is_udp) {
cmd->result = -1, cmd->err = EOPNOTSUPP;
return true;
} }
if ((c = s->c) == NULL) { if ((c = s->c) == NULL) {
cmd->result = s->closed && s->err == 0 ? 0 : -1; cmd->result = s->closed && s->err == 0 ? 0 : -1;
@@ -657,11 +594,18 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) {
return true; return true;
} }
if (c->recv.len > 0) { if (c->recv.len > 0) {
cmd->result = (int) bsd_recv(s, cmd->op_recvfrom.buf, size_t n = cmd->op_recvfrom.len;
cmd->op_recvfrom.len, if (c->recv.len < n) n = c->recv.len;
cmd->op_recvfrom.flags, memcpy(cmd->op_recvfrom.buf, c->recv.buf, n);
cmd->op_recvfrom.src, if (!(cmd->op_recvfrom.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n);
cmd->op_recvfrom.addrlen); if (cmd->op_recvfrom.src != NULL && cmd->op_recvfrom.addrlen != NULL) {
struct sockaddr_in *sa = (struct sockaddr_in *) cmd->op_recvfrom.src;
sa->sin_family = AF_INET;
sa->sin_port = c->rem.port;
sa->sin_addr.s_addr = c->rem.addr.ip4;
*cmd->op_recvfrom.addrlen = sizeof(*sa);
}
cmd->result = (int) n;
return true; return true;
} }
if (s->err != 0) { if (s->err != 0) {
-27
View File
@@ -55,37 +55,10 @@ void mg_bsd_poll(struct mg_mgr *);
#ifndef EPIPE #ifndef EPIPE
#define EPIPE 32 #define EPIPE 32
#endif #endif
#ifndef EOPNOTSUPP
#define EOPNOTSUPP 95
#endif
#ifndef EISCONN
#define EISCONN 106
#endif
#ifndef EDESTADDRREQ
#define EDESTADDRREQ 121
#endif
#ifndef EMSGSIZE
#define EMSGSIZE 90
#endif
#ifndef EPROTONOSUPPORT #ifndef EPROTONOSUPPORT
#define EPROTONOSUPPORT 93 #define EPROTONOSUPPORT 93
#endif #endif
#if defined(__DCC__)
#pragma pack(1)
#else
#pragma pack(push, 1)
#endif
struct mg_bsd_udp_frame {
uint32_t ip;
uint16_t port, len;
};
#if defined(__DCC__)
#pragma pack(0)
#else
#pragma pack(pop)
#endif
#if MG_ENABLE_BSD_PROTOTYPES #if MG_ENABLE_BSD_PROTOTYPES
typedef unsigned int socklen_t; typedef unsigned int socklen_t;
typedef unsigned long nfds_t; typedef unsigned long nfds_t;
+27 -125
View File
@@ -609,36 +609,6 @@ static void l2addr_resolved(struct mg_connection *c);
static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem, static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem,
bool is_udp, struct pkt *pkt); bool is_udp, struct pkt *pkt);
#if MG_ENABLE_BSD_SOCKETS
static uint8_t *sendto_cache_find(struct mg_tcpip_if *ifp, uint32_t ip) {
struct mg_tcpip_sendto_entry *a;
for (a = ifp->sendto; a < ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE; a++) {
if (a->ip == ip) return (uint8_t *) &a->addr;
}
return NULL;
}
static void sendto_cache_add(struct mg_tcpip_if *ifp, uint32_t ip,
const uint8_t *addr) {
struct mg_tcpip_sendto_entry *a;
for (a = ifp->sendto; a < ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE; a++) {
if (a->ip == ip) return; // already cached, skip
if (a->ip == 0) break; // not present, first empty slot
}
if (a == ifp->sendto + MG_TCPIP_SENDTO_CACHE_SIZE) { // no room
memmove(ifp->sendto, ifp->sendto + 1,
sizeof(ifp->sendto[0]) * (MG_TCPIP_SENDTO_CACHE_SIZE - 1));
a--; // make room and point to last entry (just made)
}
a->ip = ip; // populate this entry
memcpy(&a->addr, addr, sizeof(a->addr));
}
static void sendto_cache_clear(struct mg_tcpip_if *ifp) {
memset(ifp->sendto, 0, sizeof(ifp->sendto));
}
#endif
// RFC826, ARP assumes Ethernet MAC addresses // RFC826, ARP assumes Ethernet MAC addresses
static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) { if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
@@ -764,6 +734,7 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
: pkt->dhcp->siaddr ? pkt->dhcp->siaddr : pkt->dhcp->siaddr ? pkt->dhcp->siaddr
: pkt->ip->src); : pkt->ip->src);
ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state
ifp->lease_expire = ifp->now + 5000; // No ACK by then: start over
} else if (msgtype == 5) { // DHCPACK } else if (msgtype == 5) { // DHCPACK
if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP
uint64_t rand; uint64_t rand;
@@ -1244,57 +1215,30 @@ static void proto_error(struct mg_connection *c, bool r, const char *f, ...) {
static bool 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); struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
struct connstate *s; struct connstate *s;
struct mg_addr rem;
uint8_t *l2addr; uint8_t *l2addr;
size_t len = pkt->pay.len;
if (c == NULL) return false; // No UDP listener on this port if (c == NULL) return false; // No UDP listener on this port
s = (struct connstate *) (c + 1); s = (struct connstate *) (c + 1);
c->rem.port = pkt->udp->sport;
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
if (c->loc.is_ip6) { // matching of v4/v6 to dest is done bt getpeer() if (c->loc.is_ip6) { // matching of v4/v6 to dest is done bt getpeer()
if (!udp6csum_ok(pkt->ip6, pkt->udp)) return false; if (!udp6csum_ok(pkt->ip6, pkt->udp)) return false;
if (c->is_bsd && c->is_client && c->rem.addr.ip6[0] = pkt->ip6->src[0],
(c->rem.port != pkt->udp->sport || c->rem.addr.ip6[1] = pkt->ip6->src[1], c->rem.is_ip6 = true;
!MG_IP6MATCH(c->rem.addr.ip6, pkt->ip6->src)))
return true;
rem.addr.ip6[0] = pkt->ip6->src[0],
rem.addr.ip6[1] = pkt->ip6->src[1], rem.is_ip6 = true;
} else } else
#endif #endif
{ {
if (!udpcsum_ok(pkt->ip, pkt->udp)) return false; if (!udpcsum_ok(pkt->ip, pkt->udp)) return false;
if (c->is_bsd && c->is_client && c->rem.addr.ip4 = pkt->ip->src;
(c->rem.port != pkt->udp->sport || c->rem.addr.ip4 != pkt->ip->src))
return true;
rem.addr.ip4 = pkt->ip->src, rem.is_ip6 = false;
} }
rem.port = pkt->udp->sport; if ((l2addr = get_return_l2addr(ifp, &c->rem, true, pkt)) == NULL)
if (!c->is_bsd || c->is_client) c->rem = rem;
if ((l2addr = get_return_l2addr(ifp, &rem, true, pkt)) == NULL)
return false; // safety net for lousy networks return false; // safety net for lousy networks
if (!c->is_bsd || c->is_client) memcpy(s->mac, l2addr, sizeof(s->mac)); memcpy(s->mac, l2addr, sizeof(s->mac));
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && !rem.is_ip6 && MG_IPISLOCAL(ifp, rem.addr.ip4))
sendto_cache_add(ifp, rem.addr.ip4, l2addr);
#endif
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd) len += sizeof(struct mg_bsd_udp_frame);
#endif
if (c->recv.len >= MG_MAX_RECV_SIZE) { if (c->recv.len >= MG_MAX_RECV_SIZE) {
proto_error(c, false, "max_recv_buf_size reached"); proto_error(c, false, "max_recv_buf_size reached");
} else if (c->recv.size - c->recv.len < len && } else if (c->recv.size - c->recv.len < pkt->pay.len &&
!mg_iobuf_resize(&c->recv, c->recv.len + len)) { !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
proto_error(c, false, "oom"); proto_error(c, false, "oom");
} else { } else {
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd) {
struct mg_bsd_udp_frame *h =
(struct mg_bsd_udp_frame *) &c->recv.buf[c->recv.len];
h->ip = rem.addr.ip4;
h->port = rem.port;
h->len = (uint16_t) pkt->pay.len;
c->recv.len += sizeof(*h);
}
#endif
memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len); memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
c->recv.len += pkt->pay.len; c->recv.len += pkt->pay.len;
mg_call(c, MG_EV_READ, &pkt->pay.len); mg_call(c, MG_EV_READ, &pkt->pay.len);
@@ -2098,6 +2042,9 @@ static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) {
if (ifp->enable_dhcp_client && s1) { if (ifp->enable_dhcp_client && s1) {
if (ifp->state == MG_TCPIP_STATE_UP) { if (ifp->state == MG_TCPIP_STATE_UP) {
tx_dhcp_discover(ifp); // INIT (4.4.1) tx_dhcp_discover(ifp); // INIT (4.4.1)
} else if (ifp->state == MG_TCPIP_STATE_REQ &&
ifp->now >= ifp->lease_expire) {
ifp->state = MG_TCPIP_STATE_UP; // REQUEST or ACK lost: DISCOVER again
} else if (ifp->state == MG_TCPIP_STATE_READY && } else if (ifp->state == MG_TCPIP_STATE_READY &&
ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING
if (ifp->now >= ifp->lease_expire) { if (ifp->now >= ifp->lease_expire) {
@@ -2347,9 +2294,6 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
static void close_conn(struct mg_connection *c) { static void close_conn(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1); struct connstate *s = (struct connstate *) (c + 1);
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd && c->is_udp) sendto_cache_clear(c->mgr->ifp);
#endif
mg_iobuf_free(&s->txq); mg_iobuf_free(&s->txq);
mg_close_conn(c); mg_close_conn(c);
} }
@@ -2371,11 +2315,6 @@ static void send_syn(struct mg_connection *c) {
static void l2addr_resolved(struct mg_connection *c) { static void l2addr_resolved(struct mg_connection *c) {
if (c->is_udp) { if (c->is_udp) {
c->is_connecting = 0; c->is_connecting = 0;
#if MG_ENABLE_BSD_SOCKETS
if (c->is_bsd)
sendto_cache_add(c->mgr->ifp, c->rem.addr.ip4,
((struct connstate *) (c + 1))->mac);
#endif
mg_call(c, MG_EV_CONNECT, NULL); mg_call(c, MG_EV_CONNECT, NULL);
} else { } else {
send_syn(c); send_syn(c);
@@ -2390,25 +2329,20 @@ void mg_connect_resolved(struct mg_connection *c) {
s->retransmit = ifp->enable_tcp_retransmit; s->retransmit = ifp->enable_tcp_retransmit;
s->txq.align = MG_TCPIP_TXQ_ALIGN; s->txq.align = MG_TCPIP_TXQ_ALIGN;
c->is_resolving = 0; c->is_resolving = 0;
if (c->loc.port == 0) { if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE;
if (ifp->eport < MG_EPHEMERAL_PORT_BASE) c->loc.port = mg_htons(ifp->eport++);
ifp->eport = MG_EPHEMERAL_PORT_BASE;
c->loc.port = mg_htons(ifp->eport++);
} // otherwise retain an existing bound port
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
if (c->rem.is_ip6) { if (c->rem.is_ip6) {
if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0 && if (c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll
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]; c->loc.addr.ip6[0] = ifp->ip6ll[0], c->loc.addr.ip6[1] = ifp->ip6ll[1];
} else if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0) { } else { // use global address
// Use global address
c->loc.addr.ip6[0] = ifp->ip6[0], c->loc.addr.ip6[1] = ifp->ip6[1]; c->loc.addr.ip6[0] = ifp->ip6[0], c->loc.addr.ip6[1] = ifp->ip6[1];
} // otherwise retain an existing bound address }
c->loc.is_ip6 = true; c->loc.is_ip6 = true;
} else } else
#endif #endif
{ {
if (c->loc.addr.ip4 == 0) c->loc.addr.ip4 = ifp->ip; c->loc.addr.ip4 = ifp->ip;
} }
MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port, MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port,
&c->rem)); &c->rem));
@@ -2439,20 +2373,11 @@ void mg_connect_resolved(struct mg_connection *c) {
uint32_t rem_ip = c->rem.addr.ip4; uint32_t rem_ip = c->rem.addr.ip4;
if (ifp->ip && MG_IPISLOCAL(ifp, rem_ip) && if (ifp->ip && MG_IPISLOCAL(ifp, rem_ip) &&
rem_ip != ifp->gw) { // skip if gw (onstatechange -> ARP) rem_ip != ifp->gw) { // skip if gw (onstatechange -> ARP)
// We're in the same LAN // If we're in the same LAN, fire an ARP lookup.
#if MG_ENABLE_BSD_SOCKETS MG_DEBUG(("%lu ARP lookup...", c->id));
if (c->is_bsd && c->is_udp && // see if we already have L2 data mg_tcpip_arp_request(ifp, rem_ip, NULL);
(l2addr = sendto_cache_find(ifp, rem_ip)) != NULL) { settmout(c, MIP_TTYPE_ARP);
memcpy(s->mac, l2addr, sizeof(s->mac)); c->is_arplooking = 1;
l2addr_resolved(c);
} else
#endif
{ // 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) { } else if (ifp->gw_ready) {
memcpy(s->mac, ifp->gwmac, sizeof(s->mac)); memcpy(s->mac, ifp->gwmac, sizeof(s->mac));
l2addr_resolved(c); l2addr_resolved(c);
@@ -2472,36 +2397,13 @@ bool mg_open_listener(struct mg_connection *c, const char *url) {
} }
static void write_conn(struct mg_connection *c) { static void write_conn(struct mg_connection *c) {
long len; long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
size_t off = 0; : mg_io_send(c, c->send.buf, c->send.len);
bool empty = false;
#if MG_ENABLE_BSD_SOCKETS
struct mg_bsd_udp_frame *h;
if (c->is_bsd && c->is_udp) {
if (c->is_connecting) return;
h = (struct mg_bsd_udp_frame *) c->send.buf;
assert(c->send.len >= sizeof(*h));
assert(h->len <= c->send.len - sizeof(*h));
c->rem.port = h->port;
c->rem.is_ip6 = false;
if (!c->is_client && c->rem.addr.ip4 != h->ip) {
c->rem.addr.ip4 = h->ip;
mg_connect_resolved(c);
if (c->is_connecting) return;
}
len = mg_io_send(c, h + 1, h->len);
off = sizeof(*h), empty = h->len == 0;
} else
#endif
{
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 // TODO(): mg_tls_send() may return 0 forever on steady OOM
if (len == MG_IO_ERR) { if (len == MG_IO_ERR) {
proto_error(c, false, "tx err"); proto_error(c, false, "tx err");
} else if (len > 0 || empty) { } else if (len > 0) {
mg_iobuf_del(&c->send, 0, off + (size_t) len); mg_iobuf_del(&c->send, 0, (size_t) len);
mg_call(c, MG_EV_WRITE, &len); mg_call(c, MG_EV_WRITE, &len);
} }
} }
-14
View File
@@ -26,17 +26,6 @@ struct mg_tcpip_driver {
typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev, typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev,
void *ev_data); void *ev_data);
#if MG_ENABLE_BSD_SOCKETS
#ifndef MG_TCPIP_SENDTO_CACHE_SIZE
#define MG_TCPIP_SENDTO_CACHE_SIZE 4
#endif
struct mg_tcpip_sendto_entry {
uint32_t ip;
struct mg_l2addr addr;
};
#endif
// clang-format off // clang-format off
// Events delivered to mg_tcpip_if.fn (user) and mg_tcpip_if.pfn (driver). // Events delivered to mg_tcpip_if.fn (user) and mg_tcpip_if.pfn (driver).
// Each entry shows the ev_data type to cast inside the handler. // Each entry shows the ev_data type to cast inside the handler.
@@ -99,9 +88,6 @@ struct mg_tcpip_if {
enum mg_l2type l2type; // Layer-2 type: Ethernet, PPP, etc. (see l2.h) enum mg_l2type l2type; // Layer-2 type: Ethernet, PPP, etc. (see l2.h)
union mg_l2data l2data; // Layer-2 config and state (see l2.h) union mg_l2data l2data; // Layer-2 config and state (see l2.h)
char *dns4_url; // Active DNS server URL (set from DHCP or config) char *dns4_url; // Active DNS server URL (set from DHCP or config)
#if MG_ENABLE_BSD_SOCKETS
struct mg_tcpip_sendto_entry sendto[MG_TCPIP_SENDTO_CACHE_SIZE]; // Local IPv4 neighbors
#endif
uint64_t now; // Current time in ms (updated each mg_mgr_poll) uint64_t now; // Current time in ms (updated each mg_mgr_poll)
uint64_t timer_1000ms; // Timestamp of last 1-second tick uint64_t timer_1000ms; // Timestamp of last 1-second tick
uint64_t lease_expire; // DHCP lease expiry time in ms uint64_t lease_expire; // DHCP lease expiry time in ms
+1 -5
View File
@@ -299,11 +299,7 @@ static long recv_raw(struct mg_connection *c, void *buf, size_t len) {
union usa usa; union usa usa;
socklen_t slen = tousa(&c->rem, &usa); socklen_t slen = tousa(&c->rem, &usa);
n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen); n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen);
if (n > 0) { if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
struct mg_addr rem;
tomgaddr(&usa, &rem, slen != sizeof(usa.sin));
c->rem = rem;
}
} else { } else {
n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING); n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
} }
+6 -9
View File
@@ -13,7 +13,7 @@ else
C_WARN ?= -Wno-deprecated C_WARN ?= -Wno-deprecated
endif endif
.PHONY: all bsd bsd_test bsd_client bsd_udp bsd_nortos bsd_nortos_test shm_tap_bridge \ .PHONY: all bsd bsd_test bsd_client bsd_nortos bsd_nortos_test shm_tap_bridge \
shm_port_bridge bsd_s390 bsd_nortos_s390 clean shm_port_bridge bsd_s390 bsd_nortos_s390 clean
TEST_SOURCES = bsd_shm_test.c TEST_SOURCES = bsd_shm_test.c
@@ -47,11 +47,11 @@ DOCKER ?= $(DOCKER_BIN) run --platform linux/amd64 --net=host --rm $(ENV) -v $(R
CFLAGS_MONGOOSE += -DMG_ARCH=MG_ARCH_CUSTOM CFLAGS_MONGOOSE += -DMG_ARCH=MG_ARCH_CUSTOM
all bsd: shm_tap_bridge bsd_client bsd_udp FreeRTOS-Kernel bsd_test all bsd: shm_tap_bridge bsd_client FreeRTOS-Kernel bsd_test
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_test $(ARGS) ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_test $(ARGS)
# also # also
bsd_nortos: shm_tap_bridge bsd_client bsd_udp bsd_nortos_test bsd_nortos: shm_tap_bridge bsd_client bsd_nortos_test
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_nortos_test $(ARGS) ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_nortos_test $(ARGS)
# requires shm_tap_bridge to be running # requires shm_tap_bridge to be running
@@ -70,9 +70,6 @@ bsd_nortos_test:
bsd_client: bsd_client.c bsd_client: bsd_client.c
$(CC) bsd_client.c -pthread -o $@ $(CC) bsd_client.c -pthread -o $@
bsd_udp: bsd_udp.c
$(CC) bsd_udp.c -o $@
# bridge shm queue <--> TAP interface # bridge shm queue <--> TAP interface
shm_tap_bridge: shm_tap_bridge.c shm_queue.h shm_tap_bridge: shm_tap_bridge.c shm_queue.h
$(CC) shm_tap_bridge.c -pthread -o $@ $(CC) shm_tap_bridge.c -pthread -o $@
@@ -82,7 +79,7 @@ bsd_s390: CFLAGS_MONGOOSE += -DMG_ENABLE_BSD_PROTOTYPES=0
bsd_s390: ASAN= bsd_s390: ASAN=
bsd_s390: CC = $(DOCKER) mdashnet/s390 cc bsd_s390: CC = $(DOCKER) mdashnet/s390 cc
bsd_s390: RUN = $(DOCKER) --init mdashnet/s390 bsd_s390: RUN = $(DOCKER) --init mdashnet/s390
bsd_s390: shm_port_bridge bsd_client bsd_udp FreeRTOS-Kernel bsd_test bsd_s390: shm_port_bridge bsd_client FreeRTOS-Kernel bsd_test
$(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc' $(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc'
#also #also
@@ -90,14 +87,14 @@ bsd_nortos_s390: CFLAGS_MONGOOSE += -DMG_ENABLE_BSD_PROTOTYPES=0
bsd_nortos_s390: ASAN= bsd_nortos_s390: ASAN=
bsd_nortos_s390: CC = $(DOCKER) mdashnet/s390 cc bsd_nortos_s390: CC = $(DOCKER) mdashnet/s390 cc
bsd_nortos_s390: RUN = $(DOCKER) --init mdashnet/s390 bsd_nortos_s390: RUN = $(DOCKER) --init mdashnet/s390
bsd_nortos_s390: shm_port_bridge bsd_client bsd_udp bsd_nortos_test bsd_nortos_s390: shm_port_bridge bsd_client bsd_nortos_test
$(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_nortos_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc' $(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_nortos_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc'
shm_port_bridge: shm_port_bridge.c shm_queue.h shm_port_bridge: shm_port_bridge.c shm_queue.h
$(CC) shm_port_bridge.c $(CFLAGS) -pthread -o $@ $(CC) shm_port_bridge.c $(CFLAGS) -pthread -o $@
clean: # Cleanup. Delete built program and all build artifacts clean: # Cleanup. Delete built program and all build artifacts
rm -rf bsd_test bsd_client bsd_udp shm_port_bridge *.o *.obj FreeRTOS-Kernel rm -rf bsd_test bsd_client shm_port_bridge *.o *.obj FreeRTOS-Kernel
FreeRTOS-Kernel: # FreeRTOS sources FreeRTOS-Kernel: # FreeRTOS sources
git clone -q -c advice.detachedHead=false --depth 1 -b V11.2.0 https://github.com/FreeRTOS/FreeRTOS-Kernel $@ git clone -q -c advice.detachedHead=false --depth 1 -b V11.2.0 https://github.com/FreeRTOS/FreeRTOS-Kernel $@
-55
View File
@@ -1,55 +0,0 @@
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#define PORT 1235
int main(int argc, char *argv[]) {
struct sockaddr_in sa;
struct timeval timeout;
char buf[8];
ssize_t n;
int fd, rogue;
if (argc != 2) return 1;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(PORT);
if (inet_pton(AF_INET, argv[1], &sa.sin_addr) != 1) return 1;
timeout.tv_sec = 5, timeout.tv_usec = 0;
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0 ||
connect(fd, (struct sockaddr *) &sa, sizeof(sa)) != 0 ||
send(fd, "hello", 5, 0) != 5 || recv(fd, buf, sizeof(buf), 0) != 5 ||
memcmp(buf, "ready", 5) != 0)
goto fail;
if ((rogue = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ||
setsockopt(rogue, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) !=
0 ||
send(fd, "one", 3, 0) != 3 ||
sendto(rogue, "two2", 4, 0, (struct sockaddr *) &sa, sizeof(sa)) != 4 ||
recv(fd, buf, sizeof(buf), 0) != 4 || memcmp(buf, "peer", 4) != 0 ||
recv(rogue, buf, sizeof(buf), 0) != 5 || memcmp(buf, "rogue", 5) != 0 ||
send(fd, "truncate", 8, 0) != 8 || send(fd, "peek", 4, 0) != 4 ||
recv(fd, buf, sizeof(buf), 0) != 4 || memcmp(buf, "more", 4) != 0 ||
recv(fd, buf, sizeof(buf), 0) != 3 || memcmp(buf, "one", 3) != 0 ||
recv(fd, buf, sizeof(buf), 0) != 4 || memcmp(buf, "two2", 4) != 0 ||
recv(fd, buf, sizeof(buf), 0) != 0 ||
sendto(rogue, "rogue", 5, 0, (struct sockaddr *) &sa, sizeof(sa)) != 5 ||
recv(fd, buf, sizeof(buf), 0) != 7 || memcmp(buf, "checked", 7) != 0 ||
send(fd, "echo", 4, 0) != 4 || recv(fd, buf, sizeof(buf), 0) != 4 ||
memcmp(buf, "echo", 4) != 0)
goto fail_close_rogue;
close(rogue);
close(fd);
return 0;
fail_close_rogue:
close(rogue);
fail:
close(fd);
return 1;
}
+2 -230
View File
@@ -33,175 +33,6 @@ static int s_abort = 0;
#define DASHBOARD(x) \ #define DASHBOARD(x) \
printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true") printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true")
#define UDP_PORT 1235
#if MG_ENABLE_FREERTOS
static volatile bool s_tx_held, s_tx_waits;
#endif
static int open_udp(void) {
struct sockaddr_in sa;
char buf;
int fd;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(UDP_PORT);
sa.sin_addr.s_addr = INADDR_ANY;
ASSERT((fd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0);
ASSERT(bind(fd, (struct sockaddr *) &sa, sizeof(sa)) == 0);
ASSERT(send(fd, &buf, sizeof(buf), 0) < 0 && errno == EDESTADDRREQ);
ASSERT(fcntl(fd, F_SETFL, O_NONBLOCK) == 0);
return fd;
}
static void check_udp_oversize(struct mg_mgr *mgr, int fd,
const struct sockaddr *dst, socklen_t dstlen) {
static char buf[1600];
size_t len = mgr->ifp->mtu + 1;
ASSERT(len <= sizeof(buf));
if (dst == NULL) {
ASSERT(send(fd, buf, len, 0) < 0 && errno == EMSGSIZE);
} else {
ASSERT(sendto(fd, buf, len, 0, dst, dstlen) < 0 && errno == EMSGSIZE);
}
}
static void udp_exchange(struct mg_mgr *mgr, int fd,
void (*progress)(struct mg_mgr *)) {
struct sockaddr_in peer, other, rogue = {0};
socklen_t peerlen, otherlen;
uint64_t deadline;
struct timeval zero = {0, 0};
struct pollfd p;
fd_set r, w, e;
bool one;
char buf[8];
ssize_t n;
for (;;) {
peerlen = sizeof(peer);
n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &peer, &peerlen);
if (n >= 0) break;
ASSERT(errno == EAGAIN);
progress(mgr);
}
ASSERT(n == 5 && memcmp(buf, "hello", 5) == 0);
ASSERT(sendto(fd, "ready", 5, 0, (struct sockaddr *) &peer, peerlen) ==
5);
deadline = mg_millis() + 100;
while (mg_millis() < deadline) progress(mgr); // Queue both UDP datagrams
do {
otherlen = sizeof(other);
n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &other,
&otherlen);
if (n < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
} while (n < 0);
one = n == 3;
if (one) {
ASSERT(memcmp(buf, "one", 3) == 0 && other.sin_port == peer.sin_port);
} else {
ASSERT(n == 4 && memcmp(buf, "two2", 4) == 0 &&
other.sin_port != peer.sin_port);
}
if (other.sin_port != peer.sin_port) rogue = other;
do {
otherlen = sizeof(other);
n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &other,
&otherlen);
if (n < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
} while (n < 0);
if (one) {
ASSERT(n == 4 && memcmp(buf, "two2", 4) == 0 &&
other.sin_port != peer.sin_port);
} else {
ASSERT(n == 3 && memcmp(buf, "one", 3) == 0 &&
other.sin_port == peer.sin_port);
}
if (other.sin_port != peer.sin_port) rogue = other;
check_udp_oversize(mgr, fd, (struct sockaddr *) &peer, peerlen);
ASSERT(sendto(fd, "peer", 4, 0, (struct sockaddr *) &peer, peerlen) == 4);
ASSERT(sendto(fd, "rogue", 5, 0, (struct sockaddr *) &rogue,
sizeof(rogue)) == 5);
deadline = mg_millis() + 100;
while (mg_millis() < deadline) progress(mgr);
ASSERT(connect(fd, (struct sockaddr *) &peer, peerlen) == 0);
ASSERT(sendto(fd, "no", 2, 0, (struct sockaddr *) &peer, peerlen) < 0 &&
errno == EISCONN);
check_udp_oversize(mgr, fd, NULL, 0);
ASSERT(send(fd, "more", 4, 0) == 4);
deadline = mg_millis() + 100;
while (mg_millis() < deadline) progress(mgr); // Queue both UDP datagrams
do {
p.fd = fd, p.events = POLLIN | POLLOUT, p.revents = 0;
ASSERT(poll(&p, 1, 0) == 1);
if (!(p.revents & POLLIN)) progress(mgr);
} while (!(p.revents & POLLIN));
ASSERT(p.revents == (POLLIN | POLLOUT));
FD_ZERO(&r);
FD_ZERO(&w);
FD_ZERO(&e);
FD_SET(fd, &r);
FD_SET(fd, &w);
FD_SET(fd, &e);
ASSERT(select(fd + 1, &r, &w, &e, &zero) == 1);
ASSERT(FD_ISSET(fd, &r) && FD_ISSET(fd, &w) && !FD_ISSET(fd, &e));
do {
n = recv(fd, buf, 3, 0);
if (n < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
} while (n < 0);
ASSERT(n == 3 && memcmp(buf, "tru", 3) == 0);
do {
n = recv(fd, buf, sizeof(buf), MSG_PEEK);
if (n < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
} while (n < 0);
ASSERT(n == 4 && memcmp(buf, "peek", 4) == 0);
ASSERT(recv(fd, buf, sizeof(buf), 0) == 4 && memcmp(buf, "peek", 4) == 0);
p.events = POLLIN | POLLOUT, p.revents = 0;
ASSERT(poll(&p, 1, 0) == 1 && p.revents == POLLOUT);
#if MG_ENABLE_FREERTOS
s_tx_waits = false, s_tx_held = true;
#endif
ASSERT(send(fd, "one", 3, 0) == 3);
ASSERT(send(fd, "two2", 4, 0) == 4);
ASSERT(send(fd, "", 0, 0) == 0);
#if MG_ENABLE_FREERTOS
while (!s_tx_waits) progress(mgr); // MG_IO_WAIT keeps all records queued
s_tx_held = false;
#endif
deadline = mg_millis() + 100;
while (mg_millis() < deadline) progress(mgr); // Let rogue arrive
ASSERT(recv(fd, buf, sizeof(buf), MSG_DONTWAIT) < 0 && errno == EAGAIN);
while ((n = send(fd, "checked", 7, 0)) < 0) {
ASSERT(errno == EAGAIN);
progress(mgr);
}
ASSERT(n == 7);
for (;;) {
n = recv(fd, buf, sizeof(buf), 0);
if (n >= 0) break;
ASSERT(errno == EAGAIN);
progress(mgr);
}
ASSERT(n == 4 && memcmp(buf, "echo", 4) == 0);
ASSERT(send(fd, "echo", 4, 0) == 4);
progress(mgr); // Drain the last queued datagram before close
ASSERT(close(fd) == 0);
}
static pid_t start_client(const char *ip, const char *connections) { static pid_t start_client(const char *ip, const char *connections) {
char *cmd[4]; char *cmd[4];
pid_t pid; pid_t pid;
@@ -215,18 +46,6 @@ static pid_t start_client(const char *ip, const char *connections) {
return pid; return pid;
} }
static pid_t start_udp(const char *ip) {
char *cmd[3];
pid_t pid;
extern char **environ;
cmd[0] = (char *) "./bsd_udp";
cmd[1] = (char *) ip;
cmd[2] = NULL;
ASSERT(posix_spawn(&pid, cmd[0], NULL, NULL, cmd, environ) == 0);
return pid;
}
static bool client_done(pid_t pid, int *status) { static bool client_done(pid_t pid, int *status) {
pid_t r = waitpid(pid, status, WNOHANG); pid_t r = waitpid(pid, status, WNOHANG);
ASSERT(r >= 0); ASSERT(r >= 0);
@@ -241,17 +60,12 @@ static void check_client(int status) {
static int open_listener(int backlog, bool nonblock) { static int open_listener(int backlog, bool nonblock) {
int lfd = socket(AF_INET, SOCK_STREAM, 0); int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in sa; struct sockaddr_in sa;
char buf;
memset(&sa, 0, sizeof(sa)); memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET; sa.sin_family = AF_INET;
sa.sin_port = htons(1234); sa.sin_port = htons(1234);
sa.sin_addr.s_addr = INADDR_ANY; sa.sin_addr.s_addr = INADDR_ANY;
ASSERT(lfd >= 0); ASSERT(lfd >= 0);
ASSERT(sendto(lfd, &buf, sizeof(buf), 0, (struct sockaddr *) &sa,
sizeof(sa)) < 0 && errno == EOPNOTSUPP);
ASSERT(recvfrom(lfd, &buf, sizeof(buf), 0, NULL, NULL) < 0 &&
errno == EOPNOTSUPP);
ASSERT(bind(lfd, (struct sockaddr *) &sa, sizeof(sa)) == 0); ASSERT(bind(lfd, (struct sockaddr *) &sa, sizeof(sa)) == 0);
ASSERT(listen(lfd, backlog) == 0); ASSERT(listen(lfd, backlog) == 0);
if (nonblock) ASSERT(fcntl(lfd, F_SETFL, O_NONBLOCK) == 0); if (nonblock) ASSERT(fcntl(lfd, F_SETFL, O_NONBLOCK) == 0);
@@ -316,12 +130,11 @@ static volatile bool s_select_waiting, s_select_done;
static volatile bool s_poll_waiting, s_poll_done; static volatile bool s_poll_waiting, s_poll_done;
static volatile bool s_ready_waiting, s_ready_done; static volatile bool s_ready_waiting, s_ready_done;
static volatile bool s_backlog_ready, s_backlog_full, s_backlog_accept; static volatile bool s_backlog_ready, s_backlog_full, s_backlog_accept;
static volatile bool s_tx_fail, s_tx_error; static volatile bool s_tx_held, s_tx_waits, s_tx_fail, s_tx_error;
static volatile bool s_send_waiting; static volatile bool s_send_waiting;
static volatile bool s_send_done, s_send_expect_error; static volatile bool s_send_done, s_send_expect_error;
static volatile bool s_recv_start, s_recv_waiting, s_recv_done; static volatile bool s_recv_start, s_recv_waiting, s_recv_done;
static volatile bool s_recv_expect_error; static volatile bool s_recv_expect_error;
static volatile bool s_udp_ready, s_udp_done;
static size_t (*s_tx)(const void *, size_t, struct mg_tcpip_if *); static size_t (*s_tx)(const void *, size_t, struct mg_tcpip_if *);
struct client_data { struct client_data {
@@ -339,7 +152,6 @@ static void select_task(void *args);
static void readiness_task(void *args); static void readiness_task(void *args);
static void backlog_task(void *args); static void backlog_task(void *args);
static void tx_gate_task(void *args); static void tx_gate_task(void *args);
static void udp_task(void *args);
struct atask_args { struct atask_args {
bool listener_nonblock; bool listener_nonblock;
@@ -494,21 +306,6 @@ static void tx_gate_task(void *args) {
(void) args; (void) args;
} }
static void udp_delay(struct mg_mgr *mgr) {
(void) mgr;
vTaskDelay(1);
}
static void udp_task(void *args) {
struct mg_mgr *mgr = (struct mg_mgr *) args;
int fd = open_udp();
s_udp_ready = true;
udp_exchange(mgr, fd, udp_delay);
s_udp_done = true;
vTaskDelete(NULL);
}
static void recv_wait_task(void *args) { static void recv_wait_task(void *args) {
char buf; char buf;
int fd = (int) (uintptr_t) args; int fd = (int) (uintptr_t) args;
@@ -722,22 +519,12 @@ static void task2(void *args) {
struct mg_mgr *mgr = (struct mg_mgr *)args; // Event manager struct mg_mgr *mgr = (struct mg_mgr *)args; // Event manager
char *ip; char *ip;
int status; int status;
pid_t first, second, udp; pid_t first, second;
vTaskDelay(pdMS_TO_TICKS(500)); // 500 ms vTaskDelay(pdMS_TO_TICKS(500)); // 500 ms
s_error = false; s_error = false;
s_wait = false; s_wait = false;
ip = mg_mprintf("%M", mg_print_ip4, &mgr->ifp->ip); ip = mg_mprintf("%M", mg_print_ip4, &mgr->ifp->ip);
s_udp_ready = s_udp_done = false;
ASSERT(xTaskCreate(udp_task, "udp", 512, mgr, configMAX_PRIORITIES - 1,
NULL) == pdPASS);
while (!s_udp_ready) vTaskDelay(1);
udp = start_udp(ip);
while (!client_done(udp, &status)) vTaskDelay(1);
check_client(status);
while (!s_udp_done) vTaskDelay(1);
DASHBOARD("udp");
s_poll_waiting = s_poll_done = false; s_poll_waiting = s_poll_done = false;
ASSERT(xTaskCreate(poll_task, "poll", 512, NULL, ASSERT(xTaskCreate(poll_task, "poll", 512, NULL,
configMAX_PRIORITIES - 1, NULL) == pdPASS); configMAX_PRIORITIES - 1, NULL) == pdPASS);
@@ -1065,20 +852,6 @@ static void nortos_echo(struct mg_mgr *mgr, struct nortos_echo_client *clients,
nortos_wait_client(mgr, pid); nortos_wait_client(mgr, pid);
} }
static void udp_poll(struct mg_mgr *mgr) {
mg_mgr_poll(mgr, 0);
}
static void nortos_udp(struct mg_mgr *mgr, const char *ip) {
pid_t pid;
int fd = open_udp();
pid = start_udp(ip);
udp_exchange(mgr, fd, udp_poll);
nortos_wait_client(mgr, pid);
DASHBOARD("nortos_udp");
}
static void nortos_test(struct mg_mgr *mgr) { static void nortos_test(struct mg_mgr *mgr) {
static const char bootstrap = '\0'; static const char bootstrap = '\0';
static const char grow[512] = {0}; static const char grow[512] = {0};
@@ -1180,7 +953,6 @@ static void nortos_test(struct mg_mgr *mgr) {
ASSERT(close(fd) == 0); ASSERT(close(fd) == 0);
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
DASHBOARD("nortos_nonblocking_connect"); DASHBOARD("nortos_nonblocking_connect");
nortos_udp(mgr, ip);
mg_free(ip); mg_free(ip);
} }
+32
View File
@@ -0,0 +1,32 @@
# Mesh simulator. `make` builds and runs an example
ROOT = ../..
SRCS = simulator.c l2_mesh.c l2_802154.c star.c $(ROOT)/mongoose.c
HDRS = simulator.h l2_mesh.h l2_802154.h star.h $(ROOT)/mongoose.h
WARN = -pedantic -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common \
-Wconversion -Wundef -Wmissing-prototypes -Wstrict-prototypes
DEFS = -DMG_ENABLE_TCPIP=1 -DMG_ENABLE_TCPIP_DRIVER_INIT=0 -DMG_ENABLE_IPV6=0 \
-DMG_ENABLE_CUSTOM_MILLIS=1
CFLAGS ?= -O2 -g0 $(WARN)
X ?= 100
Y ?= 100
ROWS ?= 10
COLS ?= 10
# Nodes at every corner of a ROWS x COLS grid over X,Y, edges included
GRID = $(shell for r in $$(seq 0 $(ROWS)); do \
for c in $$(seq 0 $(COLS)); do \
echo -node $$((c * $(X) / $(COLS))),$$((r * $(Y) / $(ROWS))); \
done; done)
all: mesh_sim
./mesh_sim -area $(X),$(Y) \
-bridge $$(($(X) / 2)),$$(($(Y) / 2)) \
$(GRID)
mesh_sim: $(SRCS) $(HDRS) Makefile
$(CC) $(SRCS) $(CFLAGS) $(DEFS) -I. -I$(ROOT) -o $@
clean:
rm -rf mesh_sim mesh_sim.dSYM
.PHONY: all clean
+189
View File
@@ -0,0 +1,189 @@
# Ethernet over a radio mesh
## The idea
Small radio devices, e.g. IEEE 802.15.4, sub-GHz or LoRa boards, should run
Mongoose as if they were on an ordinary Ethernet LAN: get an IP address over
DHCP, answer HTTP, talk MQTT. No 6LoWPAN, no Thread, no special stack.
To do that, the radio interface looks like Ethernet to Mongoose
(`MG_TCPIP_L2_ETH`). Its driver splits each Ethernet frame into radio
frames, sends them over the air, and the receiver puts them back together.
One or more bridges connect the radio network to the wired LAN: a bridge has
an Ethernet port and a radio. A node in range of a bridge talks to it
directly; a node out of range reaches a bridge through other nodes, which
forward its frames. That makes the radio network a mesh. With several
bridges, each node uses one of them, e.g. the nearest; the mesh covers a
bigger area, and keeps working if a bridge goes down. A PC on the LAN sees
the radio nodes as regular hosts on the same LAN.
```
+---- bridge 1 ~~~ node ~~~ node
| ~
PC ---- LAN node
| ~
+---- bridge 2 ~~~ node ~~~ node ~~~ node
```
`~~~` and `~` are radio hops. Nodes far from a bridge send their frames
through nearer nodes. A node in the middle can reach either bridge.
## Two parts plug in
- `struct mg_radio`, a radio standard: how big a radio frame is, how
addresses are written into it. `mg_radio_802154` is the first one.
- `struct mg_mesh`, a mesh strategy: how nodes find a bridge, and where each
frame goes next. `mg_mesh_star` is the first and simplest one: every node
talks to a bridge directly, one hop.
Everything else, splitting and reassembling frames, sending again when a
frame is lost, is common code that works the same for every radio and every
mesh. The goal is to swap radios and mesh strategies without touching
anything else.
## Why a simulator
Real boards are slow to test with, and you can't put 500 of them on a desk.
The simulator runs a whole network in one process: every node is a real
Mongoose instance, only the radio and the air between nodes are emulated.
That lets us try mesh strategies with many nodes, and measure how long it
takes nodes to join, get an address, and answer a request.
## Virtual time
The simulator has its own clock, `simulator::now`, in microseconds, and
Mongoose reads it through `mg_millis()`. Nothing waits for real time: the
simulator moves the clock forward itself, 250 us per step, and polls every
station once per step. A minute of network traffic takes about a second to
simulate. `-t SEC` sets how long to run, 10 minutes by default.
The step is a trade-off. Each step polls every station's Mongoose, and even
an idle poll costs something, so a smaller step means a slower run. A
bigger step makes frames wait longer in queues, see below. Measured with
121 nodes and 10 simulated minutes:
| Step | Request time, mean | Run time |
|------|--------------------|----------|
| 1 ms | 6.1 ms | 0.9 s |
| 250 us | 1.5 ms | 3.0 s |
| 100 us | 0.6 ms | 7.2 s |
| 10 us | 0.06 ms | 72 s |
## The setup
```
DHCP server ---------+
10.0.0.1 |
02:01:00:00:00:00 +-- LAN ---- bridge ~~~ nodes
|
test station --------+
10.0.0.2
02:02:00:00:00:00
```
The LAN is assumed infinitely fast, see "How frames move on the LAN" below.
Two stations are on it:
- The DHCP server, 10.0.0.1, MAC 02:01:00:00:00:00, gives each node an
address made from its MAC: 02:00:00:X:Y:Z gets 10.X+1.Y.Z, so node
addresses never clash with the LAN stations' 10.0.0.x.
- The test station, 10.0.0.2, MAC 02:02:00:00:00:00, is a Mongoose
instance. It waits for the mesh to settle, 60 seconds by default
(`-w SEC`), then sends `GET /` to each node in turn, one at a time, at the
node's own IP address. A node with no address, or one that doesn't answer
within 10 seconds, counts as failed. At the end it prints how many
requests succeeded and how long they took.
Bridges are on the LAN too, and get every frame, as if in promiscuous mode:
a frame for one of their radio nodes isn't addressed to the bridge itself.
A bridge forwards every frame it gets from the LAN to the radio, and every
frame from the radio to the LAN, with no filtering yet.
Bridges and radio nodes get MACs 02:00:00:xx:xx:xx, numbered from 1 in
command-line order: 02:00:00:00:00:01, 02:00:00:00:00:02, and so on.
### How frames move on the LAN
Every LAN station, the DHCP server, the test station and each bridge, has a
LAN interface, `lif`: a `struct mg_tcpip_if` with a receive queue. All of
them use the same driver, `s_lan_driver`. There are no cables and no switch:
- **Sending (tx).** A station sends a frame with its driver's `tx`, which is
`lan_tx()`. It looks at the destination MAC and copies the frame into the
receive queues of the stations it is for:
- a broadcast or multicast goes to every station but the sender;
- a unicast goes to the station with that MAC;
- bridges get every frame, as a real bridge in promiscuous mode would:
a frame for a radio node is addressed to the node, not to its bridge.
That's all `tx` does. Nobody's code runs on the receiving side yet.
- **Receiving (rx).** Each station takes the frames from its own queue when
the main loop polls it:
- the test station runs Mongoose, which reads the queue by itself, as it
would with a real Ethernet driver that queues frames from an interrupt;
- the DHCP server reads it in `dhcp_poll()`, and replies by sending
through its own `lif`;
- bridges read it in `bridges_poll()`, and forward it to the radio.
So a frame waits in a queue until its receiver's next poll, at most one
step of virtual time, 250 us. Ethernet is hundreds of times faster than the radio, so that
doesn't matter for what we measure. And since sending never calls the
receiver's code, a reply is never sent from inside another station's send.
For example, a node asking for an address:
1. The node's DHCP DISCOVER crosses the radio to a bridge, which sends it
out of its `lif`. It is a broadcast, so `lan_tx()` puts it into the
queues of the DHCP server, the test station and the other bridges.
2. On its next poll, the DHCP server reads it and sends an OFFER, also a
broadcast, which lands in the test station's and the bridges' queues.
3. On their next poll, the bridges read the OFFER and send it over the
radio to the node. The test station's Mongoose drops it: the test station
has a fixed address and no DHCP client.
## Files
| File | What |
|------|------|
| `l2_mesh.h`, `l2_mesh.c` | `struct mg_radio`, `struct mg_mesh`, common code |
| `l2_802154.h`, `l2_802154.c` | `mg_radio_802154`, IEEE 802.15.4 |
| `star.h`, `star.c` | `mg_mesh_star`, the star mesh |
| `simulator.h`, `simulator.c` | The simulator |
| `Makefile` | Builds and runs the simulator |
## Where we are
It works end to end: nodes get addresses over DHCP, and the test station's
requests reach them through the bridges. But the radio is ideal for now:
- The radio driver works like the LAN's: it puts whole Ethernet frames
straight into receive queues, all of them for a broadcast, else the one
with the destination MAC, and bridges get every frame. No range, no
loss, no airtime.
- Bridges forward everything, with no filtering.
- So use one bridge for now. With two, a broadcast from the LAN goes to the
radio through both, each hears the other's copy and sends it back to the
LAN, and that never ends. Deciding who forwards what, without loops, is
the job of `mg_mesh`.
`mg_radio` and `mg_mesh` are still stubs. Next steps, one at a time: the
air (range, loss, airtime), fragmenting frames into radio frames with
`mg_radio`, choosing a bridge and forwarding through nodes with `mg_mesh`,
and filtering at the bridges.
## Running
```sh
make # 100x100 m, a bridge, 11x11 nodes
make X=200 Y=50 ROWS=2 COLS=8 # Another area and grid
```
`make` puts a bridge in the middle of the area, and a node at every corner
of a `ROWS` x `COLS` grid over it. The simulator itself takes:
```
./mesh_sim -area X,Y,Z -bridge X,Y,Z -node X,Y,Z ... -t SEC -w SEC
```
Positions are in metres; `X,Y` means `Z` is 0. Repeat `-bridge` and `-node`
for more.
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "l2_802154.h"
const struct mg_radio mg_radio_802154 = {"802.15.4"};
+10
View File
@@ -0,0 +1,10 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// IEEE 802.15.4 radio
#pragma once
#include "l2_mesh.h"
extern const struct mg_radio mg_radio_802154;
+4
View File
@@ -0,0 +1,4 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "l2_mesh.h"
+21
View File
@@ -0,0 +1,21 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// Ethernet over radio, through a mesh. Mongoose sees an Ethernet interface,
// its driver splits Ethernet frames into radio frames. Two parts plug in:
// - struct mg_radio: a radio standard, e.g. mg_radio_802154
// - struct mg_mesh: how the mesh is built and routed, e.g. mg_mesh_star
#pragma once
#include "mongoose.h"
// A radio standard
struct mg_radio {
const char *name; // For logs
};
// A mesh strategy
struct mg_mesh {
const char *name; // For logs
};
+433
View File
@@ -0,0 +1,433 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// Radio mesh simulator. Bridges and nodes are stations at positions in an
// area. Time is virtual: see mg_millis(). On the LAN: a DHCP server, and a
// test station that sends HTTP requests to the nodes
#include "simulator.h"
#define STEP_MICROSECONDS 250 // Virtual time per main loop step, see README.md
static struct simulator s_sim; // The simulator
// Mongoose's clock: virtual time. A minute of network traffic takes far less
// than a minute to simulate
uint64_t mg_millis(void) {
return s_sim.now / 1000;
}
// Parse "X,Y,Z", or "X,Y" with Z 0. Return false if it is not that
static bool parse_pos(const char *s, struct pos *p) {
int n = 0; // Characters parsed
p->x = p->y = p->z = 0;
return (sscanf(s, "%lf,%lf,%lf%n", &p->x, &p->y, &p->z, &n) == 3 ||
sscanf(s, "%lf,%lf%n", &p->x, &p->y, &n) == 2) &&
s[n] == 0;
}
// Is position p inside the area of sim
static bool inside(const struct simulator *sim, const struct pos *p) {
return p->x >= 0 && p->x <= sim->area.x && p->y >= 0 && p->y <= sim->area.y &&
p->z >= 0 && p->z <= sim->area.z;
}
static void usage(const struct simulator *sim, const char *prog) {
fprintf(stderr,
"Usage: %s OPTIONS\n"
"Positions: X,Y,Z in metres, or X,Y with Z 0\n"
" -area X,Y,Z - area, a box, default: %g,%g,%g\n"
" -bridge X,Y,Z - a bridge; repeat for more\n"
" -node X,Y,Z - a radio node; repeat for more\n"
" -t SEC - simulated time, seconds, default: %g\n"
" -w SEC - mesh settle time before requests, default: %g\n",
prog, sim->area.x, sim->area.y, sim->area.z,
(double) sim->duration / 1e6, (double) sim->tester.wait / 1e6);
exit(EXIT_FAILURE);
}
// The next radio MAC, to mac: 02:00:00:xx:xx:xx, numbered from 1
static void next_mac(struct simulator *sim, uint8_t *mac) {
uint32_t n = ++sim->last_mac;
mac[0] = 2, mac[1] = mac[2] = 0, mac[3] = (uint8_t) (n >> 16);
mac[4] = (uint8_t) (n >> 8), mac[5] = (uint8_t) n;
}
// Add a bridge at position s. Return false if s is not a position
static bool add_bridge(struct simulator *sim, const char *s) {
struct bridge *b = (struct bridge *) calloc(1, sizeof(*b));
if (b == NULL || !parse_pos(s, &b->pos)) return free(b), false;
next_mac(sim, b->rif.mac);
LIST_ADD_TAIL(struct bridge, &sim->bridges, b);
sim->num_bridges++;
return true;
}
// Add a radio node at position s. Return false if s is not a position
static bool add_node(struct simulator *sim, const char *s) {
struct radio_node *n = (struct radio_node *) calloc(1, sizeof(*n));
if (n == NULL || !parse_pos(s, &n->pos)) return free(n), false;
next_mac(sim, n->rif.mac);
LIST_ADD_TAIL(struct radio_node, &sim->nodes, n);
sim->num_nodes++;
return true;
}
// Exit if position x is outside the area of sim
static void check_inside(const struct simulator *sim, const struct pos *x) {
if (!inside(sim, x)) {
fprintf(stderr, "%g,%g,%g is outside the area\n", x->x, x->y, x->z);
exit(EXIT_FAILURE);
}
}
// Check that all bridges and nodes are inside the area of sim
static void check_area(const struct simulator *sim) {
struct bridge *b;
struct radio_node *n;
for (b = sim->bridges; b != NULL; b = b->next) check_inside(sim, &b->pos);
for (n = sim->nodes; n != NULL; n = n->next) check_inside(sim, &n->pos);
}
#define LAN_QUEUE_SIZE (64 * 1024) // Receive queue of a LAN interface
// Put frame buf, len bytes, into the receive queue of LAN interface to, if
// it is not the sender, from
static void lan_put(struct mg_tcpip_if *to, struct mg_tcpip_if *from,
const void *buf, size_t len) {
if (to != from) mg_tcpip_qwrite((void *) buf, len, to);
}
// LAN driver, all LAN interfaces share it. The LAN is infinitely fast: tx
// puts the frame straight into the receive queues of the interfaces it is
// for: all, if broadcast; else the one with the destination MAC. Bridges get
// every frame: some are for their radio nodes. Each station takes frames
// from its queue when polled, see dhcp_poll(), bridges_poll(); Mongoose does
// it for the test station
static size_t lan_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
const uint8_t *dst = (const uint8_t *) buf;
bool all = dst[0] & 1; // Broadcast, multicast
struct bridge *b;
if (all || memcmp(dst, s_sim.dhcp.lif.mac, 6) == 0) {
lan_put(&s_sim.dhcp.lif, ifp, buf, len);
}
if (all || memcmp(dst, s_sim.tester.lif.mac, 6) == 0) {
lan_put(&s_sim.tester.lif, ifp, buf, len);
}
for (b = s_sim.bridges; b != NULL; b = b->next) {
lan_put(&b->lif, ifp, buf, len);
}
return len;
}
static bool lan_init(struct mg_tcpip_if *ifp) {
(void) ifp;
return true;
}
static bool lan_link(struct mg_tcpip_if *ifp, bool s1) {
(void) ifp, (void) s1;
return true;
}
static struct mg_tcpip_driver s_lan_driver = {lan_init, lan_tx, NULL, lan_link};
// Internet checksum of len bytes
static uint16_t ip_csum(const uint8_t *buf, size_t len) {
uint32_t sum = 0;
size_t i;
for (i = 0; i + 1 < len; i += 2) sum += (uint32_t) (buf[i] << 8 | buf[i + 1]);
if (len & 1) sum += (uint32_t) (buf[len - 1] << 8);
while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
return (uint16_t) ~sum;
}
// A node's address, from its MAC 02:00:00:X:Y:Z: 10.X+1.Y.Z, so it never
// clashes with the LAN stations' 10.0.0.x
static void node_ip(const uint8_t *mac, uint8_t *ip) {
ip[0] = 10, ip[1] = (uint8_t) (mac[3] + 1), ip[2] = mac[4], ip[3] = mac[5];
}
// DHCP server, a frame from its queue: a DISCOVER gets an OFFER, a REQUEST
// an ACK. Replies are broadcast. The address comes from the client's MAC
static void dhcp_rx(struct simulator *sim, const uint8_t *buf, size_t len) {
static const uint8_t opts[] = {53, 1, 0, // Message type, set below
1, 4, 255, 0, 0, 0, // Mask
3, 4, 10, 0, 0, 1, // Router
54, 4, 10, 0, 0, 1, // Server
51, 4, 0, 0, 14, 16, // Lease, 3600 s
255};
uint8_t r[14 + 20 + 8 + 240 + sizeof(opts)], type = 0;
const uint8_t *dh, *p, *end = buf + len;
size_t ihl, rlen = sizeof(r);
if ((buf[0] & 1) == 0) return; // Bcast
if (len < 34 || buf[12] != 8 || buf[13] != 0 || buf[23] != 17) return; // UDP
ihl = (size_t) (buf[14] & 15) * 4, dh = buf + 14 + ihl + 8; // DHCP
if (dh + 240 > end || buf[14 + ihl + 2] != 0 || buf[14 + ihl + 3] != 67) {
return;
}
for (p = dh + 240; p + 2 <= end && p[0] != 255; p += p[0] ? p[1] + 2 : 1) {
if (p[0] == 53 && p[1] == 1 && p + 3 <= end) type = p[2];
}
if (type != 1 && type != 3) return; // DISCOVER, REQUEST
memset(r, 0, sizeof(r));
memset(r, 0xff, 6), memcpy(r + 6, sim->dhcp.lif.mac, 6),
r[12] = 8; // Ethernet
r[14] = 0x45, r[16] = (uint8_t) ((rlen - 14) >> 8); // IPv4
r[17] = (uint8_t) (rlen - 14), r[22] = 64, r[23] = 17;
r[26] = 10, r[29] = 1, memset(r + 30, 0xff, 4); // 10.0.0.1 -> bcast
r[24] = (uint8_t) (ip_csum(r + 14, 20) >> 8);
r[25] = (uint8_t) ip_csum(r + 14, 20);
r[35] = 67, r[37] = 68; // UDP, csum 0: none
r[38] = (uint8_t) ((rlen - 34) >> 8), r[39] = (uint8_t) (rlen - 34);
r[42] = 2, r[43] = 1, r[44] = 6; // DHCP: reply, Ethernet
memcpy(r + 46, dh + 4, 4); // xid
memcpy(r + 52, dh + 10, 2); // flags
node_ip(dh + 28, r + 58); // yiaddr
r[62] = 10, r[65] = 1; // siaddr
memcpy(r + 70, dh + 28, 16); // chaddr
r[278] = 99, r[279] = 130, r[280] = 83, r[281] = 99; // Magic
memcpy(r + 282, opts, sizeof(opts));
r[284] = type == 1 ? 2 : 5; // OFFER, ACK
sim->dhcp.lif.driver->tx(r, rlen, &sim->dhcp.lif);
}
// DHCP server's work: take the frames from its queue
static void dhcp_poll(struct simulator *sim) {
char *buf;
size_t len;
while ((len = mg_queue_next(&sim->dhcp.lif.recv_queue, &buf)) > 0) {
dhcp_rx(sim, (const uint8_t *) buf, len);
mg_queue_del(&sim->dhcp.lif.recv_queue, len);
}
}
// The request on the way finished: ok, or failed
static void tester_done(struct lan_tester *t, uint64_t now, bool ok) {
double ms = (double) (now - t->start) / 1000;
if (ok) {
t->ok++, t->sum += ms;
if (t->ok == 1 || ms < t->min) t->min = ms;
if (ms > t->max) t->max = ms;
} else {
t->failed++;
}
if (t->c != NULL) t->c->is_closing = 1, t->c->fn_data = NULL;
t->c = NULL;
}
// HTTP client: GET /, the reply ends the request
static void tester_fn(struct mg_connection *c, int ev, void *ev_data) {
struct lan_tester *t = (struct lan_tester *) c->fn_data;
if (t == NULL) return; // Done with
if (ev == MG_EV_CONNECT) {
mg_printf(c, "GET / HTTP/1.0\r\n\r\n");
} else if (ev == MG_EV_HTTP_MSG) {
tester_done(t, s_sim.now, true);
} else if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) {
tester_done(t, s_sim.now, false);
}
(void) ev_data;
}
// Test station's work: Mongoose, then requests, one at a time, once the mesh
// had time to settle. A node without an IP address fails at once
static void tester_poll(struct simulator *sim) {
struct radio_node *n;
char url[64];
mg_mgr_poll(&sim->tester.mgr, 0);
if (sim->tester.c != NULL && sim->now - sim->tester.start >
(uint64_t) sim->request_timeout_ms * 1000) {
tester_done(&sim->tester, sim->now, false);
}
if (sim->now < sim->tester.wait || sim->tester.c != NULL ||
sim->tester.next == NULL)
return;
n = sim->tester.next;
sim->tester.next = n->next, sim->tester.start = sim->now;
if (n->rif.ip == 0) {
tester_done(&sim->tester, sim->now, false);
return;
}
mg_snprintf(url, sizeof(url), "http://%M/", mg_print_ip4, &n->rif.ip);
if ((sim->tester.c = mg_http_connect(&sim->tester.mgr, url, tester_fn,
&sim->tester)) == NULL) {
tester_done(&sim->tester, sim->now, false);
}
}
// Set up sim: defaults, LAN stations
static void init(struct simulator *sim) {
static const uint8_t dmac[6] = {2, 1, 0, 0, 0, 0}, // DHCP server MAC
tmac[6] = {2, 2, 0, 0, 0, 0}; // Test station MAC
memset(sim, 0, sizeof(*sim));
sim->area.x = sim->area.y = 100;
sim->duration = 600 * 1000000ULL, sim->tester.wait = 60 * 1000000ULL;
sim->request_timeout_ms = 10000;
memcpy(sim->dhcp.lif.mac, dmac, 6), sim->dhcp.lif.driver = &s_lan_driver;
mg_queue_init(&sim->dhcp.lif.recv_queue, (char *) calloc(1, LAN_QUEUE_SIZE),
LAN_QUEUE_SIZE);
memcpy(sim->tester.lif.mac, tmac, 6);
sim->tester.lif.ip = MG_IPV4(10, 0, 0, 2),
sim->tester.lif.mask = MG_IPV4(255, 0, 0, 0);
sim->tester.lif.driver = &s_lan_driver,
sim->tester.lif.enable_mac_check = true;
sim->tester.lif.recv_queue.size = 128 * 1024;
}
// Radio driver, all radio interfaces share it. An ideal radio for now:
// whole Ethernet frames, no range, no loss, no airtime. It works like the
// LAN, see lan_tx(): a frame goes into the receive queues of all other
// radio interfaces if broadcast, else of the one with the destination MAC.
// Bridges get every frame: some are for the LAN. Who forwards what, and
// loops with several bridges, are for mg_mesh
static size_t radio_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
const uint8_t *dst = (const uint8_t *) buf;
bool all = dst[0] & 1; // Broadcast, multicast
struct bridge *b;
struct radio_node *n;
for (b = s_sim.bridges; b != NULL; b = b->next) {
lan_put(&b->rif, ifp, buf, len);
}
for (n = s_sim.nodes; n != NULL; n = n->next) {
if (all || memcmp(dst, n->rif.mac, 6) == 0) {
lan_put(&n->rif, ifp, buf, len);
}
}
return len;
}
static bool radio_init(struct mg_tcpip_if *ifp) {
(void) ifp;
return true;
}
static bool radio_link(struct mg_tcpip_if *ifp, bool s1) {
(void) ifp, (void) s1;
return true;
}
static struct mg_tcpip_driver s_radio_driver = {radio_init, radio_tx, NULL,
radio_link};
// A node's HTTP server: the test station requests GET /
static void node_http_ev_handler(struct mg_connection *c, int ev,
void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
mg_http_reply(c, 200, "", "Hi from Mongoose, my MAC is %M\n", mg_print_mac,
c->fn_data);
}
(void) ev_data;
}
// Start each node's Mongoose: its radio interface, no IP address, so DHCP,
// and an HTTP server
static void nodes_init(struct simulator *sim) {
struct radio_node *n;
for (n = sim->nodes; n != NULL; n = n->next) {
n->rif.driver = &s_radio_driver;
n->rif.recv_queue.size = LAN_QUEUE_SIZE;
mg_mgr_init(&n->mgr);
mg_tcpip_init(&n->mgr, &n->rif);
mg_http_listen(&n->mgr, "http://0.0.0.0:80", node_http_ev_handler,
n->rif.mac);
}
}
// Start each bridge: its LAN and radio drivers. No Mongoose, no IP
static void bridges_init(struct simulator *sim) {
struct bridge *b;
for (b = sim->bridges; b != NULL; b = b->next) {
b->lif.driver = &s_lan_driver, b->rif.driver = &s_radio_driver;
mg_queue_init(&b->lif.recv_queue, (char *) calloc(1, LAN_QUEUE_SIZE),
LAN_QUEUE_SIZE);
mg_queue_init(&b->rif.recv_queue, (char *) calloc(1, LAN_QUEUE_SIZE),
LAN_QUEUE_SIZE);
b->lif.driver->init(&b->lif), b->rif.driver->init(&b->rif);
}
}
// Move the frames waiting in the receive queue of interface from out of
// interface to
static void bridge_forward(struct mg_tcpip_if *from, struct mg_tcpip_if *to) {
char *buf;
size_t len;
while ((len = mg_queue_next(&from->recv_queue, &buf)) > 0) {
// MG_DEBUG(("Bridging ETH frame, size %zu", len));
to->driver->tx(buf, len, to);
mg_queue_del(&from->recv_queue, len);
}
}
// Bridges' work: forward every frame, LAN to radio and radio to LAN. No
// filtering yet
static void bridges_poll(struct simulator *sim) {
struct bridge *b;
for (b = sim->bridges; b != NULL; b = b->next) {
bridge_forward(&b->lif, &b->rif);
bridge_forward(&b->rif, &b->lif);
}
}
// Poll each node's Mongoose
static void nodes_poll(struct simulator *sim) {
struct radio_node *n;
for (n = sim->nodes; n != NULL; n = n->next) mg_mgr_poll(&n->mgr, 0);
}
int main(int argc, char *argv[]) {
clock_t started; // CPU time, to show how fast the simulation runs
int i;
init(&s_sim);
for (i = 1; i < argc; i++) {
if (i + 1 >= argc) {
usage(&s_sim, argv[0]);
} else if (strcmp(argv[i], "-area") == 0) {
if (!parse_pos(argv[++i], &s_sim.area)) usage(&s_sim, argv[0]);
} else if (strcmp(argv[i], "-bridge") == 0) {
if (!add_bridge(&s_sim, argv[++i])) usage(&s_sim, argv[0]);
} else if (strcmp(argv[i], "-node") == 0) {
if (!add_node(&s_sim, argv[++i])) usage(&s_sim, argv[0]);
} else if (strcmp(argv[i], "-t") == 0) {
s_sim.duration = (uint64_t) (atof(argv[++i]) * 1e6);
} else if (strcmp(argv[i], "-w") == 0) {
s_sim.tester.wait = (uint64_t) (atof(argv[++i]) * 1e6);
} else {
usage(&s_sim, argv[0]);
}
}
printf("Area %gx%gx%g m, radio %s, mesh %s, bridges: %d, nodes: %d\n",
s_sim.area.x, s_sim.area.y, s_sim.area.z, mg_radio_802154.name,
mg_mesh_star.name, s_sim.num_bridges, s_sim.num_nodes);
check_area(&s_sim);
// mg_log_set(MG_LL_INFO);
mg_mgr_init(&s_sim.tester.mgr);
mg_tcpip_init(&s_sim.tester.mgr, &s_sim.tester.lif);
s_sim.tester.next = s_sim.nodes;
bridges_init(&s_sim);
nodes_init(&s_sim);
// Main loop: STEP_MICROSECONDS of virtual time per step
started = clock();
while (s_sim.now < s_sim.duration) {
dhcp_poll(&s_sim);
tester_poll(&s_sim);
bridges_poll(&s_sim);
nodes_poll(&s_sim);
s_sim.now += STEP_MICROSECONDS;
}
printf("Requests: %d ok, %d failed, of %d nodes", s_sim.tester.ok,
s_sim.tester.failed, s_sim.num_nodes);
if (s_sim.tester.ok > 0) {
printf("; ms: min %.1f, mean %.1f, max %.1f", s_sim.tester.min,
s_sim.tester.sum / s_sim.tester.ok, s_sim.tester.max);
}
printf("\nSimulated %g s in %.3f s\n", (double) s_sim.now / 1e6,
(double) (clock() - started) / CLOCKS_PER_SEC);
return 0;
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#pragma once
#define MG_ENABLE_TCPIP 1
#include "l2_802154.h"
#include "l2_mesh.h"
#include "mongoose.h"
#include "star.h"
// A position in metres
struct pos {
double x, y, z; // Coordinates
};
// A bridge: forwards frames between the LAN and the radio, no IP stack
struct bridge {
struct bridge *next; // Next in simulator::bridges
struct pos pos; // Where it is
struct mg_tcpip_if lif; // LAN side: LAN driver, gets every LAN frame
struct mg_tcpip_if rif; // Radio side: radio driver, gets every frame
};
// A radio node: Mongoose on a radio interface
struct radio_node {
struct radio_node *next; // Next in simulator::nodes
struct pos pos; // Where it is
struct mg_mgr mgr; // Mongoose event manager
struct mg_tcpip_if rif; // Radio interface. rif.ip from DHCP, 0: none yet
};
struct simulator;
// LAN station: a DHCP server, 10.0.0.1. Gives each client an address made
// from its MAC, see node_ip()
struct lan_dhcp_server {
struct mg_tcpip_if lif; // Its LAN interface, no Mongoose on it
};
// LAN station: the test station, 10.0.0.2, a Mongoose instance. Waits for
// the mesh to settle, then sends GET / to each node
struct lan_tester {
struct mg_mgr mgr; // Its Mongoose
struct mg_tcpip_if lif; // Its LAN interface
uint64_t wait; // Mesh settle time: start requests then, us
struct radio_node *next; // Node to request next, NULL: all done
struct mg_connection *c; // Request on the way, NULL: none
uint64_t start; // When it started, us
int ok, failed; // Requests done, by result
double min, max, sum; // Times of successful requests, ms
};
// Simulator state
struct simulator {
struct pos area; // A box: all positions inside
struct bridge *bridges; // Bridges, a list
struct radio_node *nodes; // Radio nodes, a list
int num_bridges, num_nodes; // How many
uint64_t now; // Virtual time, us since the start
uint64_t duration; // Run this long, us
uint32_t last_mac; // Low 3 bytes of the last MAC given
int request_timeout_ms; // A request that takes longer failed
struct lan_dhcp_server dhcp; // DHCP server on the LAN
struct lan_tester tester; // Test station on the LAN
};
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "star.h"
const struct mg_mesh mg_mesh_star = {"star"};
+10
View File
@@ -0,0 +1,10 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
//
// Star mesh: one hop, nodes talk to a bridge
#pragma once
#include "l2_mesh.h"
extern const struct mg_mesh mg_mesh_star;
+4
View File
@@ -80,6 +80,10 @@ static void test_statechange(void) {
onstatechange(&iface); onstatechange(&iface);
ASSERT(executed == true); ASSERT(executed == true);
executed = false; executed = false;
iface.enable_dhcp_client = true; // DHCP REQUEST unanswered: start over
iface.state = MG_TCPIP_STATE_REQ, iface.lease_expire = 0;
mg_ip_poll(&iface, true);
ASSERT(iface.state == MG_TCPIP_STATE_UP);
} }
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
static void mif6_fn(struct mg_tcpip_if *ifp, int ev, void *ev_data) { static void mif6_fn(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
@@ -6,6 +6,7 @@
#include "mongoose.h" #include "mongoose.h"
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
struct echo_server { struct echo_server {
@@ -42,12 +43,12 @@ int echo_init(uint16_t port) {
if (fd < 0 || !echo_nonblock(fd) || if (fd < 0 || !echo_nonblock(fd) ||
bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0 || listen(fd, 3) < 0) { bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0 || listen(fd, 3) < 0) {
MG_ERROR(("BSD echo init: %d", errno)); printf("BSD echo init: %d\n", errno);
if (fd >= 0) close(fd); if (fd >= 0) close(fd);
return 0; return 0;
} }
s_server.sock = fd; s_server.sock = fd;
MG_INFO(("BSD echo server on port %hu", port)); printf("BSD echo server on port %hu\n", port);
return 1; return 1;
} }
@@ -79,7 +80,7 @@ void echo_poll(void) {
if (fd >= 0) { if (fd >= 0) {
c = (struct echo_client *) calloc(1, sizeof(*c)); c = (struct echo_client *) calloc(1, sizeof(*c));
if (c == NULL || !echo_nonblock(fd)) { if (c == NULL || !echo_nonblock(fd)) {
MG_ERROR(("BSD echo accept: %d", c == NULL ? ENOMEM : errno)); printf("BSD echo accept: %d\n", c == NULL ? ENOMEM : errno);
close(fd); close(fd);
free(c); free(c);
} else { } else {
@@ -1,36 +0,0 @@
# Copyright (c) 2026 Cesanta Software Limited
# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/
MGDIR = ../../../..
CFLAGS = -W -Wall -Wextra -Wundef -Wshadow -Wdouble-promotion
CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
CFLAGS += -I. -I$(MGDIR) -Icmsis_core/CMSIS/Core/Include -Icmsis_h7/Include
CFLAGS += -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 $(CFLAGS_EXTRA)
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nosys.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map
SOURCES = main.c hal.c udp.c
SOURCES += cmsis_h7/Source/Templates/gcc/startup_stm32h723xx.s
SOURCES += $(MGDIR)/mongoose.c
all build example: firmware.bin
firmware.elf: cmsis_core cmsis_h7 hal.h link.ld Makefile $(SOURCES) $(MGDIR)/mongoose.h mongoose_config.h
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@
firmware.bin: firmware.elf
arm-none-eabi-objcopy -O binary $< $@
@echo
@echo "To flash, run 'make flash', or use STM32CubeProgrammer"
flash: firmware.bin
STM32_Programmer_CLI -c port=swd -w firmware.elf -hardRst
cmsis_core:
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
cmsis_h7:
git clone -q -c advice.detachedHead=false --depth 1 -b v1.10.6 https://github.com/STMicroelectronics/cmsis_device_h7 $@
clean:
rm -rf firmware.* cmsis_*
@@ -1,125 +0,0 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "hal.h"
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) {
uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry
if (period == 0) return false; // Avoid division by zero
if (diff < period) return false; // Period has not elapsed yet
*t += (diff / period) * period; // Preserve cadence, skip missed periods
return true;
}
static volatile uint64_t s_ticks; // Milliseconds since boot
void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms
s_ticks++;
}
uint64_t hal_get_tick(void) {
return s_ticks;
};
uint32_t SystemCoreClock = 160000000;
void SystemInit(void) { // Called automatically by startup code
hal_system_init(); // Enable FPU
hal_clock_init();
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
}
void ExitRun0Mode(void) {
}
struct stat;
__attribute__((weak)) int _fstat(int fd, struct stat *st) {
(void) fd, (void) st;
return -1;
}
extern unsigned char _end[]; // End of data section, start of heap. See link.ld
static unsigned char *s_current_heap_end = _end;
size_t hal_ram_used(void) {
return (size_t) (s_current_heap_end - _end);
}
size_t hal_ram_free(void) {
unsigned char endofstack;
return (size_t) (&endofstack - s_current_heap_end);
}
void *_sbrk(int incr) {
unsigned char *prev_heap;
unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256);
prev_heap = s_current_heap_end;
// Check how much space we got from the heap end to the stack end
if (s_current_heap_end + incr > heap_end) return (void *) -1;
s_current_heap_end += incr;
return prev_heap;
}
__attribute__((weak)) int _open(const char *path) {
(void) path;
return -1;
}
__attribute__((weak)) int _close(int fd) {
(void) fd;
return -1;
}
__attribute__((weak)) int _isatty(int fd) {
(void) fd;
return 1;
}
__attribute__((weak)) int _lseek(int fd, int ptr, int dir) {
(void) fd, (void) ptr, (void) dir;
return 0;
}
__attribute__((weak)) void _exit(int status) {
(void) status;
for (;;) asm volatile("BKPT #0");
}
__attribute__((weak)) void _kill(int pid, int sig) {
(void) pid, (void) sig;
}
__attribute__((weak)) int _getpid(void) {
return -1;
}
__attribute__((weak)) int _write(int fd, char *ptr, int len) {
(void) fd, (void) ptr, (void) len;
return -1;
}
__attribute__((weak)) int _read(int fd, char *ptr, int len) {
(void) fd, (void) ptr, (void) len;
return -1;
}
__attribute__((weak)) int _link(const char *a, const char *b) {
(void) a, (void) b;
return -1;
}
__attribute__((weak)) int _unlink(const char *a) {
(void) a;
return -1;
}
__attribute__((weak)) int _stat(const char *path, struct stat *st) {
(void) path, (void) st;
return -1;
}
__attribute__((weak)) int mkdir(const char *path, int mode) {
(void) path, (void) mode;
return -1;
}
__attribute__((weak)) void _init(void) {
}
@@ -1,225 +0,0 @@
// Copyright (c) 2022-2026 Cesanta Software Limited
// Datasheet: RM0468, devboard manual: UM2407
// https://www.st.com/resource/en/reference_manual/rm0468-stm32h723733-stm32h725735-and-stm32h730-value-line-advanced-armbased-32bit-mcus-stmicroelectronics.pdf
// https://www.st.com/resource/en/user_manual/um2407-stm32h7-nucleo144-boards-mb1364-stmicroelectronics.pdf
// Alternate functions: https://www.st.com/resource/en/datasheet/stm32h723.pdf
#pragma once
#include <stm32h723xx.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
extern bool hal_timer_expired(volatile uint64_t *t, uint64_t prd, uint64_t now);
extern uint64_t hal_get_tick(void);
extern size_t hal_ram_free(void);
extern size_t hal_ram_used(void);
extern uint32_t SystemCoreClock;
#define BIT(x) (1UL << (x))
#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set))
#define PIN(bank, num) ((((bank) - 'A') << 8) | (num))
#define PINNO(pin) (pin & 255)
#define PINBANK(pin) (pin >> 8)
#define HAL_ETH_PINS PIN('A', 1), PIN('A', 2), PIN('A', 7), \
PIN('B', 13), PIN('C', 1), PIN('C', 4), \
PIN('C', 5), PIN('G', 11), PIN('G', 13)
// System clock (2.1, Figure 1; 8.5, Figure 45; 8.5.5, Figure 47; 8.5.6, Figure
// 49; 8.5.8 Table 56; datasheet) CPU_FREQUENCY <= 550 MHz; hclk = CPU_FREQUENCY
// / HAL_HPRE ; hclk <= 275 MHz; APB clocks <= 137.5 MHz. D1 domain bus matrix (and
// so flash) runs at hclk frequency. Configure flash latency (WS) in accordance
// to hclk freq (4.3.8, Table 16) The Ethernet controller is in D2 domain and
// runs at hclk frequency
enum {
HAL_D1CPRE = 1, // actual divisor value
HAL_HPRE = 2, // actual divisor value
HAL_D1PPRE = 4, // register values, divisor value = BIT(value - 3) = / 2
HAL_D2PPRE1 = 4,
HAL_D2PPRE2 = 4,
HAL_D3PPRE = 4
};
// PLL1_P: odd division factors are not allowed (8.7.12) (except for '1')
enum { PLL1_HSI = 64, PLL1_M = 32, PLL1_N = 225, PLL1_P = 1 };
#define HAL_FLASH_LATENCY 0x33 // WRHIGHFREQ LATENCY
#define SYS_FREQUENCY ((PLL1_HSI * PLL1_N / PLL1_M / PLL1_P / HAL_D1CPRE) * 1000000)
#define AHB_FREQUENCY (SYS_FREQUENCY / HAL_HPRE)
#define APB2_FREQUENCY (AHB_FREQUENCY / (BIT(HAL_D2PPRE2 - 3)))
#define APB1_FREQUENCY (AHB_FREQUENCY / (BIT(HAL_D2PPRE1 - 3)))
enum { HAL_GPIO_MODE_INPUT, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_MODE_AF, HAL_GPIO_MODE_ANALOG };
enum { HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_OTYPE_OPEN_DRAIN };
enum { HAL_GPIO_SPEED_LOW, HAL_GPIO_SPEED_MEDIUM, HAL_GPIO_SPEED_HIGH, HAL_GPIO_SPEED_INSANE };
enum { HAL_GPIO_PULL_NONE, HAL_GPIO_PULL_UP, HAL_GPIO_PULL_DOWN };
#define HAL_GPIO(N) ((GPIO_TypeDef *) (0x40000000 + 0x18020000UL + 0x400 * (N)))
static GPIO_TypeDef *hal_gpio_bank(uint16_t pin) {
return HAL_GPIO(PINBANK(pin));
}
static inline void hal_gpio_toggle(uint16_t pin) {
GPIO_TypeDef *gpio = hal_gpio_bank(pin);
uint32_t mask = BIT(PINNO(pin));
gpio->BSRR = mask << (gpio->ODR & mask ? 16 : 0);
}
static inline int hal_gpio_read(uint16_t pin) {
return hal_gpio_bank(pin)->IDR & BIT(PINNO(pin)) ? 1 : 0;
}
static inline void hal_gpio_write(uint16_t pin, bool val) {
GPIO_TypeDef *gpio = hal_gpio_bank(pin);
gpio->BSRR = BIT(PINNO(pin)) << (val ? 0 : 16);
}
static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type,
uint8_t speed, uint8_t pull, uint8_t af) {
GPIO_TypeDef *gpio = hal_gpio_bank(pin);
uint8_t n = (uint8_t) (PINNO(pin));
RCC->AHB4ENR |= BIT(PINBANK(pin)); // Enable GPIO clock
CLRSET(gpio->OTYPER, 1UL << n, ((uint32_t) type) << n);
CLRSET(gpio->OSPEEDR, 3UL << (n * 2), ((uint32_t) speed) << (n * 2));
CLRSET(gpio->PUPDR, 3UL << (n * 2), ((uint32_t) pull) << (n * 2));
CLRSET(gpio->AFR[n >> 3], 15UL << ((n & 7) * 4),
((uint32_t) af) << ((n & 7) * 4));
CLRSET(gpio->MODER, 3UL << (n * 2), ((uint32_t) mode) << (n * 2));
}
static inline void hal_gpio_input(uint16_t pin) {
hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_NONE, 0);
}
static inline void hal_gpio_output(uint16_t pin) {
hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_NONE, 0);
}
// D2 Kernel clock (8.7.21) USART1 defaults to pclk2 (APB2), while USART2,3
// default to pclk1 (APB1). Even if using other kernel clocks, the APBx clocks
// must be enabled for CPU access, as the kernel clock drives the BRR, not the
// APB bus interface
static inline void hal_uart_init(USART_TypeDef *uart, uint16_t tx,
uint16_t rx, unsigned long baud) {
uint8_t af = 7; // Alternate function
uint32_t freq = 0; // Bus frequency. UART1 is on APB2, rest on APB1
if (uart == USART1) freq = APB2_FREQUENCY, RCC->APB2ENR |= BIT(4);
if (uart == USART2) freq = APB1_FREQUENCY, RCC->APB1LENR |= BIT(17);
if (uart == USART3) freq = APB1_FREQUENCY, RCC->APB1LENR |= BIT(18);
#if 0 // CONSTANT BAUD RATE FOR REMOTE DEBUGGING WHILE SETTING THE PLL
CLRSET(RCC->D2CCIP2R, 7 << 3, 3 << 3); // use HSI for UART1
freq = 64000000;
#endif
hal_gpio_init(tx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af);
hal_gpio_init(rx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af);
uart->CR1 = 0; // Disable this UART
uart->BRR = freq / baud; // Set baud rate
uart->CR1 = BIT(0) | BIT(2) | BIT(3); // Set UE, RE, TE
}
static inline void hal_uart_write_byte(USART_TypeDef *uart, uint8_t byte) {
uart->TDR = byte;
while ((uart->ISR & USART_ISR_TXE_TXFNF) == 0) (void) 0;
}
static inline void hal_uart_write_buf(USART_TypeDef *uart, char *buf, size_t len) {
while (len-- > 0) hal_uart_write_byte(uart, *(uint8_t *) buf++);
}
static inline int hal_uart_read_ready(USART_TypeDef *uart) {
return uart->ISR & BIT(5); // If RXNE bit is set, data is ready
}
static inline uint8_t hal_uart_read_byte(USART_TypeDef *uart) {
return (uint8_t) (uart->RDR & 255);
}
// RNG clock (rng_clk) defaults to HSI48 and is characterized at this frequency.
// Enable at SystemInit. (STM32CubeMX) It won't work with a 275MHz (AHB)
static inline void hal_rng_init(void) {
RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; // Enable RNG AHB clock (hal_rng_hclk)
// 39.6.2 Table 320 init
RNG->CR = RNG_CR_CONDRST | (0x0F << RNG_CR_RNG_CONFIG1_Pos) |
(0 << RNG_CR_RNG_CONFIG2_Pos) | (0xD << RNG_CR_RNG_CONFIG3_Pos);
RNG->HTCR = 0x17590abc;
RNG->HTCR = 0xaa74;
RNG->CR &= ~RNG_CR_CONDRST ;
while (RNG->CR & RNG_CR_CONDRST) (void) 0; // 39.7.1
RNG->CR |= RNG_CR_RNGEN; // Enable RNG
}
static inline uint32_t hal_rng_read(void) {
while ((RNG->SR & RNG_SR_DRDY) == 0) (void) 0;
return RNG->DR;
}
// Hw pull-ups on PHY RXD0,1,DV to enable autonegotiation
static inline void hal_ethernet_init(void) {
// Initialise Ethernet. Enable MAC GPIO pins, see
// https://www.st.com/resource/en/user_manual/um2407-stm32h7-nucleo144-boards-mb1364-stmicroelectronics.pdf
uint16_t pins[] = {HAL_ETH_PINS};
for (size_t i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) {
hal_gpio_init(pins[i], HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_INSANE,
HAL_GPIO_PULL_NONE, 11); // 11 is the Ethernet function
}
NVIC_EnableIRQ(ETH_IRQn); // Setup Ethernet IRQ handler
CLRSET(SYSCFG->PMCR, 7 << 21, 4 << 21); // Use RMII (12.3.1)
RCC->AHB1ENR |= BIT(15) | BIT(16) | BIT(17); // Enable Ethernet clocks
}
static inline unsigned int hal_div2prescval(unsigned int div) {
// 0 --> /1; 8 --> /2 ... 11 --> /16; 12 --> /64 ... 15 --> /512
if (div == 1) return 0;
if (div > 16) div /= 2;
unsigned int val = 7;
while (div >>= 1) ++val;
return val;
}
static inline unsigned int hal_pllrge(unsigned int f) {
unsigned int val = 0;
while (f >>= 1) ++val;
return val - 1;
}
// Enable RTC/BKP APB clock and backup domain write access.
// Must be called before accessing RTC backup registers or IWDG.
static inline void hal_backup_domain_init(void) {
RCC->APB4ENR |= RCC_APB4ENR_RTCAPBEN; // enable RTC/BKP APB clock
PWR->CR1 |= PWR_CR1_DBP; // enable backup domain write access
}
static inline void hal_clock_init(void) {
hal_backup_domain_init();
PWR->CR3 |= PWR_CR3_LDOEN; // select LDO (reset value)
while ((PWR->CSR1 & PWR_CSR1_ACTVOSRDY) == 0) (void) 0; // ACTVOSRDY
PWR->D3CR &= ~PWR_D3CR_VOS_Msk; // Select VOS0
while ((PWR->D3CR & PWR_D3CR_VOSRDY) == 0) (void) 0; // VOSRDY
CLRSET(
RCC->D1CFGR, (0x0F << 8) | (7 << 4) | (0x0F << 0),
(hal_div2prescval(HAL_D1CPRE) << 8) | (HAL_D1PPRE << 4) | (hal_div2prescval(HAL_HPRE) << 0));
RCC->D2CFGR = (HAL_D2PPRE2 << 8) | (HAL_D2PPRE1 << 4);
RCC->D3CFGR = (HAL_D3PPRE << 4);
CLRSET(RCC->PLLCFGR, 3 << 2,
hal_pllrge(PLL1_HSI / PLL1_M)
<< 2); // keep reset config (DIVP1EN, !PLL1VCOSEL), PLL1RGE
CLRSET(RCC->PLL1DIVR, (0x7F << 9) | (0x1FF << 0),
((PLL1_P - 1) << 9) | ((PLL1_N - 1) << 0)); // Set PLL1_P PLL1_N
CLRSET(RCC->PLLCKSELR, 0x3F << 4,
PLL1_M << 4); // Set PLL1_M (source defaults to HSI)
RCC->CR |= RCC_CR_PLL1ON | RCC_CR_HSI48ON; // Enable PLL1 and HSI48 (for RNG)
while ((RCC->CR & RCC_CR_PLL1RDY) == 0) (void) 0; // Wait until done
RCC->CFGR |= (3 << 0); // Set clock source to PLL1
while ((RCC->CFGR & (7 << 3)) != (3 << 3)) (void) 0; // Wait until done
FLASH->ACR = HAL_FLASH_LATENCY; // default is larger
RCC->APB4ENR |= RCC_APB4ENR_SYSCFGEN; // Enable SYSCFG
while ((RCC->CR & RCC_CR_HSI48RDY) == 0) (void) 0; // Make sure HSI48 is ready
SystemCoreClock = SYS_FREQUENCY; // Update SystemCoreClock global var
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
}
static inline void hal_system_init(void) {
SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU
__DSB();
__ISB();
}
@@ -1,32 +0,0 @@
ENTRY(Reset_Handler);
MEMORY {
flash(rx) : ORIGIN = 0x08000000, LENGTH = 1024k
dtcmram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
ram_d1 (rwx) : ORIGIN = 0x24000000, LENGTH = 320K
ram_d2 (rwx) : ORIGIN = 0x30000000, LENGTH = 32K
ram_d3 (rwx) : ORIGIN = 0x38000000, LENGTH = 16K
itcmram (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
}
_estack = ORIGIN(ram_d1) + LENGTH(ram_d1);
SECTIONS {
.vectors : { KEEP(*(.isr_vector)) } > flash
.text : { *(.text* .text.*) } > flash
.rodata : { *(.rodata*) } > flash
/* Note the .iram section, for functions copied to RAM. Required for MG_IRAM OTA support */
.data : { _sdata = .; *(.first_data) *(.iram .iram* .iram.*) *(.data SORT(.data.*)) _edata = .; } > ram_d1 AT > flash
_sidata = LOADADDR(.data);
.bss : { _sbss = .; *(.bss SORT(.bss.*) COMMON) _ebss = .; } > ram_d1
.eth_ram : { *(.eth_ram .eth_ram*) } > ram_d1 AT > flash
/* Health record. NOLOAD and in unused DTCM, so startup code neither copies
nor zeroes it and the contents survive a warm reset */
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > dtcmram
. = ALIGN(8);
_end = .;
}
@@ -1,69 +0,0 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "hal.h"
#include "mongoose.h"
int udp_init(uint16_t port);
void udp_poll(void);
#ifndef UART_DEBUG
#define UART_DEBUG USART3
#define UART_DEBUG_TX_PIN PIN('D', 8)
#define UART_DEBUG_RX_PIN PIN('D', 9)
#else
#define UART_DEBUG_TX_PIN PIN('A', 9)
#define UART_DEBUG_RX_PIN PIN('A', 10)
#endif
#define LED1 PIN('B', 0)
#define LED2 PIN('E', 1)
#define LED3 PIN('B', 14)
static void log_fn(char ch, void *param) {
hal_uart_write_buf(param, &ch, 1);
}
static void blink_task(void) {
static uint64_t blink_timer = 0;
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
hal_gpio_toggle(LED2);
}
}
uint64_t mg_millis(void) {
return hal_get_tick();
}
bool mg_random(void *buf, size_t len) {
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
uint32_t r = hal_rng_read();
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
}
return true;
}
int main(void) {
struct mg_mgr mgr;
hal_clock_init();
hal_uart_init(UART_DEBUG, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 115200);
mg_log_set_fn(log_fn, UART_DEBUG);
hal_rng_init();
hal_ethernet_init();
hal_gpio_output(LED1);
hal_gpio_output(LED2);
hal_gpio_output(LED3);
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
mg_mgr_init(&mgr);
udp_init(1234);
for (;;) {
mg_mgr_poll(&mgr, 0);
udp_poll();
blink_task();
}
return 0;
}
@@ -1,37 +0,0 @@
#pragma once
#include "hal.h"
// See https://mongoose.ws/docs/getting-started/build-options/
#define MG_ARCH MG_ARCH_ARMGCC
#define MG_TLS MG_TLS_BUILTIN
#define MG_OTA MG_OTA_STM32H7
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_BSD_SOCKETS 1
#define MG_ENABLE_CUSTOM_MILLIS 1
#define MG_ENABLE_CUSTOM_RANDOM 1
#define MG_ENABLE_DRIVER_STM32H 1
#define MG_ENABLE_PACKED_FS 1
#define MG_ETH_RAM __attribute__((section(".eth_ram")))
// #define MG_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 5
// #define MG_TCPIP_PHY_ADDR 0 // PHY address
// For static IP configuration, define MG_TCPIP_{IP,MASK,GW}
// By default, those are set to zero, meaning that DHCP is used
//
// #define MG_TCPIP_IP MG_IPV4(192, 168, 0, 10) // IP
// #define MG_TCPIP_GW MG_IPV4(192, 168, 0, 1) // Gateway
// #define MG_TCPIP_MASK MG_IPV4(255, 255, 255, 0) // Netmask
// Construct MAC address from the MCU unique ID
#define MGUID ((uint32_t *) UID_BASE)
#define MG_SET_MAC_ADDRESS(mac) \
do { \
mac[0] = 2; \
mac[1] = MGUID[0] & 255; \
mac[2] = (MGUID[0] >> 10) & 255; \
mac[3] = (MGUID[0] >> 19) & 255; \
mac[4] = MGUID[1] & 255; \
mac[5] = MGUID[2] & 255; \
} while (0)
@@ -1,68 +0,0 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
struct udp_server {
int fd;
bool pending;
struct sockaddr_in peer;
socklen_t peerlen;
size_t len;
char buf[128];
};
static struct udp_server s_server = {.fd = -1};
static int udp_nonblock(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
return flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
}
int udp_init(uint16_t port) {
struct sockaddr_in sa = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr = {.s_addr = INADDR_ANY},
};
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0 || !udp_nonblock(fd) ||
bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
MG_ERROR(("BSD UDP init: %d", errno));
if (fd >= 0) close(fd);
return 0;
}
s_server.fd = fd;
MG_INFO(("BSD UDP echo server on port %hu", port));
return 1;
}
void udp_poll(void) {
struct udp_server *s = &s_server;
struct pollfd p;
ssize_t n;
if (s->fd < 0) return;
p.fd = s->fd, p.events = s->pending ? POLLOUT : POLLIN, p.revents = 0;
if (poll(&p, 1, 0) <= 0) return;
if (s->pending) {
n = sendto(s->fd, s->buf, s->len, 0, (struct sockaddr *) &s->peer,
s->peerlen);
if (n == (ssize_t) s->len) {
s->pending = false;
} else if (n < 0 && errno != EAGAIN) {
MG_ERROR(("BSD UDP send: %d", errno));
s->pending = false;
}
} else {
s->peerlen = sizeof(s->peer);
n = recvfrom(s->fd, s->buf, sizeof(s->buf), 0,
(struct sockaddr *) &s->peer, &s->peerlen);
if (n >= 0) {
s->len = (size_t) n, s->pending = true;
} else if (errno != EAGAIN) {
MG_ERROR(("BSD UDP recv: %d", errno));
}
}
}