From cb63083bc353921b358893254b5ed95c55c39aec Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Tue, 27 Oct 2026 10:33:44 -0300 Subject: [PATCH] Add full datagram support for BSD --- mongoose.c | 270 ++++++++++++++---- mongoose.h | 41 +++ src/bsd.c | 116 ++++++-- src/bsd.h | 27 ++ src/net_builtin.c | 148 ++++++++-- src/net_builtin.h | 14 + src/sock.c | 6 +- test/bsd/Makefile | 15 +- test/bsd/bsd_udp.c | 55 ++++ test/bsd/bsd_x_test.c | 232 ++++++++++++++- .../minimal-bsd-baremetal/echo.c | 7 +- .../minimal-bsd-udp-baremetal/Makefile | 36 +++ .../minimal-bsd-udp-baremetal/hal.c | 125 ++++++++ .../minimal-bsd-udp-baremetal/hal.h | 225 +++++++++++++++ .../minimal-bsd-udp-baremetal/link.ld | 32 +++ .../minimal-bsd-udp-baremetal/main.c | 69 +++++ .../mongoose_config.h | 37 +++ .../minimal-bsd-udp-baremetal/udp.c | 68 +++++ 18 files changed, 1403 insertions(+), 120 deletions(-) create mode 100644 test/bsd/bsd_udp.c create mode 100644 tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/Makefile create mode 100644 tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/hal.c create mode 100644 tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/hal.h create mode 100644 tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/link.ld create mode 100644 tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/main.c create mode 100644 tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/mongoose_config.h create mode 100644 tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/udp.c diff --git a/mongoose.c b/mongoose.c index e06b827f..1030ead4 100644 --- a/mongoose.c +++ b/mongoose.c @@ -167,6 +167,7 @@ fail: #endif + #include #if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS @@ -277,6 +278,57 @@ static struct mg_connection *bsd_new_conn(struct mg_bsd_sock *s) { 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 // Complete operations blocked on c before Mongoose destroys it. static void bsd_finish_waiters(struct mg_bsd_sock *s, bool error) { @@ -457,9 +509,9 @@ static int bsd_ready(struct mg_bsd_sock *s) { return 0; } 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) - ready |= BSD_WRITE; // Established TCP socket + ready |= BSD_WRITE; // UDP socket or established TCP socket return ready; } @@ -473,7 +525,7 @@ static void bsd_close(struct mg_bsd_sock *s) { if (t->orphan && t->listener == s) { if (t->c != NULL) { t->c->fn_data = NULL; - mg_close_conn(t->c); + t->c->is_draining = 1; t->c = NULL; } memset(t, 0, sizeof(*t)); // Release orphan @@ -481,7 +533,7 @@ static void bsd_close(struct mg_bsd_sock *s) { } if (s->c != NULL) { s->c->fn_data = NULL; - mg_close_conn(s->c); + s->c->is_draining = 1; s->c = NULL; } memset(s, 0, sizeof(*s)); // Release socket @@ -660,8 +712,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) { cmd->result = -1, cmd->err = ECONNREFUSED; return true; } - if (!c->is_connecting) { - cmd->result = 0; + if (s->is_udp || !c->is_connecting) { + cmd->result = 0; // UDP may still resolve ARP; TCP waits for SYN/SYN-ACK return true; } if (s->nonblock) { @@ -685,11 +737,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) { return true; } if (c->recv.len > 0) { - size_t n = cmd->op_recv.len; - 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; + cmd->result = (int) bsd_recv(s, cmd->op_recv.buf, cmd->op_recv.len, + cmd->op_recv.flags, NULL, NULL); return true; } if (s->err != 0) { @@ -710,10 +759,17 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) { case BSD_OP_SEND: if ((s = bsd_get(cmd->op_send.fd)) == NULL) { cmd->result = -1, cmd->err = EBADF; - } else if ((c = s->c) == NULL || s->err != 0 || c->is_closing || - c->is_draining) { + } else if (s->err != 0 || s->closed || + ((c = s->c) != NULL && (c->is_closing || c->is_draining))) { cmd->result = -1, cmd->err = EPIPE; - } else if (mg_send(c, cmd->op_send.buf, cmd->op_send.len)) { + } else if (c == NULL || (s->is_udp && !c->is_client)) { + 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; } else if (s->nonblock || (cmd->op_send.flags & MSG_DONTWAIT)) { cmd->result = -1, cmd->err = EAGAIN; @@ -728,16 +784,20 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) { case BSD_OP_SENDTO: if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) { 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 || c->is_draining) { cmd->result = -1, cmd->err = EPIPE; + } else if (c->is_client) { + cmd->result = -1, cmd->err = EISCONN; } else { const struct sockaddr_in *sa = (const struct sockaddr_in *) cmd->op_sendto.addr; - c->rem.port = sa->sin_port; - c->rem.addr.ip4 = sa->sin_addr.s_addr; - c->rem.is_ip6 = false; - if (mg_send(c, cmd->op_sendto.buf, cmd->op_sendto.len)) { + if (bsd_udp_too_big(c, cmd->op_sendto.len)) { + cmd->result = -1, cmd->err = EMSGSIZE; + } else if (bsd_udp_send(c, sa->sin_addr.s_addr, sa->sin_port, + cmd->op_sendto.buf, cmd->op_sendto.len)) { cmd->result = (int) cmd->op_sendto.len; } else if (s->nonblock || (cmd->op_sendto.flags & MSG_DONTWAIT)) { cmd->result = -1, cmd->err = EAGAIN; @@ -754,6 +814,9 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) { if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) { cmd->result = -1, cmd->err = EBADF; return true; + } else if (!s->is_udp) { + cmd->result = -1, cmd->err = EOPNOTSUPP; + return true; } if ((c = s->c) == NULL) { cmd->result = s->closed && s->err == 0 ? 0 : -1; @@ -761,18 +824,11 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) { return true; } if (c->recv.len > 0) { - size_t n = cmd->op_recvfrom.len; - if (c->recv.len < n) n = c->recv.len; - memcpy(cmd->op_recvfrom.buf, c->recv.buf, n); - if (!(cmd->op_recvfrom.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n); - 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; + cmd->result = (int) bsd_recv(s, cmd->op_recvfrom.buf, + cmd->op_recvfrom.len, + cmd->op_recvfrom.flags, + cmd->op_recvfrom.src, + cmd->op_recvfrom.addrlen); return true; } if (s->err != 0) { @@ -9274,6 +9330,36 @@ static void l2addr_resolved(struct mg_connection *c); static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem, bool is_udp, struct pkt *pkt); +#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 static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) { @@ -9879,30 +9965,57 @@ 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) { struct mg_connection *c = getpeer(ifp->mgr, pkt, true); struct connstate *s; + struct mg_addr rem; uint8_t *l2addr; + size_t len = pkt->pay.len; if (c == NULL) return false; // No UDP listener on this port s = (struct connstate *) (c + 1); - c->rem.port = pkt->udp->sport; #if MG_ENABLE_IPV6 if (c->loc.is_ip6) { // matching of v4/v6 to dest is done bt getpeer() if (!udp6csum_ok(pkt->ip6, pkt->udp)) return false; - c->rem.addr.ip6[0] = pkt->ip6->src[0], - c->rem.addr.ip6[1] = pkt->ip6->src[1], c->rem.is_ip6 = true; + if (c->is_bsd && c->is_client && + (c->rem.port != pkt->udp->sport || + !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 #endif { if (!udpcsum_ok(pkt->ip, pkt->udp)) return false; - c->rem.addr.ip4 = pkt->ip->src; + if (c->is_bsd && c->is_client && + (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; } - if ((l2addr = get_return_l2addr(ifp, &c->rem, true, pkt)) == NULL) + rem.port = pkt->udp->sport; + 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 - memcpy(s->mac, l2addr, sizeof(s->mac)); + if (!c->is_bsd || c->is_client) 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) { proto_error(c, false, "max_recv_buf_size reached"); - } else if (c->recv.size - c->recv.len < pkt->pay.len && - !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) { + } else if (c->recv.size - c->recv.len < len && + !mg_iobuf_resize(&c->recv, c->recv.len + len)) { proto_error(c, false, "oom"); } 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); c->recv.len += pkt->pay.len; mg_call(c, MG_EV_READ, &pkt->pay.len); @@ -10955,6 +11068,9 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { static void close_conn(struct mg_connection *c) { 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_close_conn(c); } @@ -10976,6 +11092,11 @@ static void send_syn(struct mg_connection *c) { static void l2addr_resolved(struct mg_connection *c) { if (c->is_udp) { 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); } else { send_syn(c); @@ -10990,20 +11111,25 @@ void mg_connect_resolved(struct mg_connection *c) { s->retransmit = ifp->enable_tcp_retransmit; s->txq.align = MG_TCPIP_TXQ_ALIGN; c->is_resolving = 0; - if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; - c->loc.port = mg_htons(ifp->eport++); + if (c->loc.port == 0) { + if (ifp->eport < MG_EPHEMERAL_PORT_BASE) + ifp->eport = MG_EPHEMERAL_PORT_BASE; + c->loc.port = mg_htons(ifp->eport++); + } // otherwise retain an existing bound port #if MG_ENABLE_IPV6 if (c->rem.is_ip6) { - if (c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll + if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0 && + c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll c->loc.addr.ip6[0] = ifp->ip6ll[0], c->loc.addr.ip6[1] = ifp->ip6ll[1]; - } else { // use global address + } else if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0) { + // Use global address 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; } else #endif { - c->loc.addr.ip4 = ifp->ip; + if (c->loc.addr.ip4 == 0) c->loc.addr.ip4 = ifp->ip; } MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port, &c->rem)); @@ -11034,11 +11160,20 @@ void mg_connect_resolved(struct mg_connection *c) { uint32_t rem_ip = c->rem.addr.ip4; if (ifp->ip && MG_IPISLOCAL(ifp, rem_ip) && rem_ip != ifp->gw) { // skip if gw (onstatechange -> ARP) - // If we're in the same LAN, fire an ARP lookup. - MG_DEBUG(("%lu ARP lookup...", c->id)); - mg_tcpip_arp_request(ifp, rem_ip, NULL); - settmout(c, MIP_TTYPE_ARP); - c->is_arplooking = 1; + // We're in the same LAN +#if MG_ENABLE_BSD_SOCKETS + if (c->is_bsd && c->is_udp && // see if we already have L2 data + (l2addr = sendto_cache_find(ifp, rem_ip)) != NULL) { + memcpy(s->mac, l2addr, sizeof(s->mac)); + 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) { memcpy(s->mac, ifp->gwmac, sizeof(s->mac)); l2addr_resolved(c); @@ -11058,13 +11193,36 @@ bool mg_open_listener(struct mg_connection *c, const char *url) { } static void write_conn(struct mg_connection *c) { - long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len) - : mg_io_send(c, c->send.buf, c->send.len); + long len; + size_t off = 0; + 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 if (len == MG_IO_ERR) { proto_error(c, false, "tx err"); - } else if (len > 0) { - mg_iobuf_del(&c->send, 0, (size_t) len); + } else if (len > 0 || empty) { + mg_iobuf_del(&c->send, 0, off + (size_t) len); mg_call(c, MG_EV_WRITE, &len); } } @@ -15373,7 +15531,11 @@ static long recv_raw(struct mg_connection *c, void *buf, size_t len) { union usa usa; socklen_t slen = tousa(&c->rem, &usa); n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen); - if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin)); + if (n > 0) { + struct mg_addr rem; + tomgaddr(&usa, &rem, slen != sizeof(usa.sin)); + c->rem = rem; + } } else { n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING); } diff --git a/mongoose.h b/mongoose.h index 6ac3ed93..a68128d9 100644 --- a/mongoose.h +++ b/mongoose.h @@ -4620,6 +4620,17 @@ struct mg_tcpip_driver { typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev, 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 // 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. @@ -4682,6 +4693,9 @@ struct mg_tcpip_if { 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) 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 timer_1000ms; // Timestamp of last 1-second tick uint64_t lease_expire; // DHCP lease expiry time in ms @@ -4893,10 +4907,37 @@ void mg_bsd_poll(struct mg_mgr *); #ifndef EPIPE #define EPIPE 32 #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 #define EPROTONOSUPPORT 93 #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 typedef unsigned int socklen_t; typedef unsigned long nfds_t; diff --git a/src/bsd.c b/src/bsd.c index f301e10d..387dec2e 100644 --- a/src/bsd.c +++ b/src/bsd.c @@ -1,5 +1,6 @@ #include "bsd.h" #include "dns.h" +#include "util.h" #include #if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS @@ -110,6 +111,57 @@ static struct mg_connection *bsd_new_conn(struct mg_bsd_sock *s) { 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 // Complete operations blocked on c before Mongoose destroys it. static void bsd_finish_waiters(struct mg_bsd_sock *s, bool error) { @@ -290,9 +342,9 @@ static int bsd_ready(struct mg_bsd_sock *s) { return 0; } 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) - ready |= BSD_WRITE; // Established TCP socket + ready |= BSD_WRITE; // UDP socket or established TCP socket return ready; } @@ -306,7 +358,7 @@ static void bsd_close(struct mg_bsd_sock *s) { if (t->orphan && t->listener == s) { if (t->c != NULL) { t->c->fn_data = NULL; - mg_close_conn(t->c); + t->c->is_draining = 1; t->c = NULL; } memset(t, 0, sizeof(*t)); // Release orphan @@ -314,7 +366,7 @@ static void bsd_close(struct mg_bsd_sock *s) { } if (s->c != NULL) { s->c->fn_data = NULL; - mg_close_conn(s->c); + s->c->is_draining = 1; s->c = NULL; } memset(s, 0, sizeof(*s)); // Release socket @@ -493,8 +545,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) { cmd->result = -1, cmd->err = ECONNREFUSED; return true; } - if (!c->is_connecting) { - cmd->result = 0; + if (s->is_udp || !c->is_connecting) { + cmd->result = 0; // UDP may still resolve ARP; TCP waits for SYN/SYN-ACK return true; } if (s->nonblock) { @@ -518,11 +570,8 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) { return true; } if (c->recv.len > 0) { - size_t n = cmd->op_recv.len; - 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; + cmd->result = (int) bsd_recv(s, cmd->op_recv.buf, cmd->op_recv.len, + cmd->op_recv.flags, NULL, NULL); return true; } if (s->err != 0) { @@ -543,10 +592,17 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) { case BSD_OP_SEND: if ((s = bsd_get(cmd->op_send.fd)) == NULL) { cmd->result = -1, cmd->err = EBADF; - } else if ((c = s->c) == NULL || s->err != 0 || c->is_closing || - c->is_draining) { + } else if (s->err != 0 || s->closed || + ((c = s->c) != NULL && (c->is_closing || c->is_draining))) { cmd->result = -1, cmd->err = EPIPE; - } else if (mg_send(c, cmd->op_send.buf, cmd->op_send.len)) { + } else if (c == NULL || (s->is_udp && !c->is_client)) { + 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; } else if (s->nonblock || (cmd->op_send.flags & MSG_DONTWAIT)) { cmd->result = -1, cmd->err = EAGAIN; @@ -561,16 +617,20 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) { case BSD_OP_SENDTO: if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) { 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 || c->is_draining) { cmd->result = -1, cmd->err = EPIPE; + } else if (c->is_client) { + cmd->result = -1, cmd->err = EISCONN; } else { const struct sockaddr_in *sa = (const struct sockaddr_in *) cmd->op_sendto.addr; - c->rem.port = sa->sin_port; - c->rem.addr.ip4 = sa->sin_addr.s_addr; - c->rem.is_ip6 = false; - if (mg_send(c, cmd->op_sendto.buf, cmd->op_sendto.len)) { + if (bsd_udp_too_big(c, cmd->op_sendto.len)) { + cmd->result = -1, cmd->err = EMSGSIZE; + } else if (bsd_udp_send(c, sa->sin_addr.s_addr, sa->sin_port, + cmd->op_sendto.buf, cmd->op_sendto.len)) { cmd->result = (int) cmd->op_sendto.len; } else if (s->nonblock || (cmd->op_sendto.flags & MSG_DONTWAIT)) { cmd->result = -1, cmd->err = EAGAIN; @@ -587,6 +647,9 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) { if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) { cmd->result = -1, cmd->err = EBADF; return true; + } else if (!s->is_udp) { + cmd->result = -1, cmd->err = EOPNOTSUPP; + return true; } if ((c = s->c) == NULL) { cmd->result = s->closed && s->err == 0 ? 0 : -1; @@ -594,18 +657,11 @@ static bool bsd_run(struct mg_bsd_cmd *cmd) { return true; } if (c->recv.len > 0) { - size_t n = cmd->op_recvfrom.len; - if (c->recv.len < n) n = c->recv.len; - memcpy(cmd->op_recvfrom.buf, c->recv.buf, n); - if (!(cmd->op_recvfrom.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n); - 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; + cmd->result = (int) bsd_recv(s, cmd->op_recvfrom.buf, + cmd->op_recvfrom.len, + cmd->op_recvfrom.flags, + cmd->op_recvfrom.src, + cmd->op_recvfrom.addrlen); return true; } if (s->err != 0) { diff --git a/src/bsd.h b/src/bsd.h index 5a01f70c..0b9e94e6 100644 --- a/src/bsd.h +++ b/src/bsd.h @@ -55,10 +55,37 @@ void mg_bsd_poll(struct mg_mgr *); #ifndef EPIPE #define EPIPE 32 #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 #define EPROTONOSUPPORT 93 #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 typedef unsigned int socklen_t; typedef unsigned long nfds_t; diff --git a/src/net_builtin.c b/src/net_builtin.c index 95f29e93..c348495a 100644 --- a/src/net_builtin.c +++ b/src/net_builtin.c @@ -609,6 +609,36 @@ static void l2addr_resolved(struct mg_connection *c); static uint8_t *get_return_l2addr(struct mg_tcpip_if *ifp, struct mg_addr *rem, bool is_udp, struct pkt *pkt); +#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 static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) { @@ -1214,30 +1244,57 @@ 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) { struct mg_connection *c = getpeer(ifp->mgr, pkt, true); struct connstate *s; + struct mg_addr rem; uint8_t *l2addr; + size_t len = pkt->pay.len; if (c == NULL) return false; // No UDP listener on this port s = (struct connstate *) (c + 1); - c->rem.port = pkt->udp->sport; #if MG_ENABLE_IPV6 if (c->loc.is_ip6) { // matching of v4/v6 to dest is done bt getpeer() if (!udp6csum_ok(pkt->ip6, pkt->udp)) return false; - c->rem.addr.ip6[0] = pkt->ip6->src[0], - c->rem.addr.ip6[1] = pkt->ip6->src[1], c->rem.is_ip6 = true; + if (c->is_bsd && c->is_client && + (c->rem.port != pkt->udp->sport || + !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 #endif { if (!udpcsum_ok(pkt->ip, pkt->udp)) return false; - c->rem.addr.ip4 = pkt->ip->src; + if (c->is_bsd && c->is_client && + (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; } - if ((l2addr = get_return_l2addr(ifp, &c->rem, true, pkt)) == NULL) + rem.port = pkt->udp->sport; + 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 - memcpy(s->mac, l2addr, sizeof(s->mac)); + if (!c->is_bsd || c->is_client) 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) { proto_error(c, false, "max_recv_buf_size reached"); - } else if (c->recv.size - c->recv.len < pkt->pay.len && - !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) { + } else if (c->recv.size - c->recv.len < len && + !mg_iobuf_resize(&c->recv, c->recv.len + len)) { proto_error(c, false, "oom"); } 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); c->recv.len += pkt->pay.len; mg_call(c, MG_EV_READ, &pkt->pay.len); @@ -2290,6 +2347,9 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { static void close_conn(struct mg_connection *c) { 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_close_conn(c); } @@ -2311,6 +2371,11 @@ static void send_syn(struct mg_connection *c) { static void l2addr_resolved(struct mg_connection *c) { if (c->is_udp) { 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); } else { send_syn(c); @@ -2325,20 +2390,25 @@ void mg_connect_resolved(struct mg_connection *c) { s->retransmit = ifp->enable_tcp_retransmit; s->txq.align = MG_TCPIP_TXQ_ALIGN; c->is_resolving = 0; - if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; - c->loc.port = mg_htons(ifp->eport++); + if (c->loc.port == 0) { + if (ifp->eport < MG_EPHEMERAL_PORT_BASE) + ifp->eport = MG_EPHEMERAL_PORT_BASE; + c->loc.port = mg_htons(ifp->eport++); + } // otherwise retain an existing bound port #if MG_ENABLE_IPV6 if (c->rem.is_ip6) { - if (c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll + if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0 && + c->rem.addr.ip6[0] == ifp->ip6ll[0]) { // same local LAN, use ll c->loc.addr.ip6[0] = ifp->ip6ll[0], c->loc.addr.ip6[1] = ifp->ip6ll[1]; - } else { // use global address + } else if (c->loc.addr.ip6[0] == 0 && c->loc.addr.ip6[1] == 0) { + // Use global address 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; } else #endif { - c->loc.addr.ip4 = ifp->ip; + if (c->loc.addr.ip4 == 0) c->loc.addr.ip4 = ifp->ip; } MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port, &c->rem)); @@ -2369,11 +2439,20 @@ void mg_connect_resolved(struct mg_connection *c) { uint32_t rem_ip = c->rem.addr.ip4; if (ifp->ip && MG_IPISLOCAL(ifp, rem_ip) && rem_ip != ifp->gw) { // skip if gw (onstatechange -> ARP) - // If we're in the same LAN, fire an ARP lookup. - MG_DEBUG(("%lu ARP lookup...", c->id)); - mg_tcpip_arp_request(ifp, rem_ip, NULL); - settmout(c, MIP_TTYPE_ARP); - c->is_arplooking = 1; + // We're in the same LAN +#if MG_ENABLE_BSD_SOCKETS + if (c->is_bsd && c->is_udp && // see if we already have L2 data + (l2addr = sendto_cache_find(ifp, rem_ip)) != NULL) { + memcpy(s->mac, l2addr, sizeof(s->mac)); + 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) { memcpy(s->mac, ifp->gwmac, sizeof(s->mac)); l2addr_resolved(c); @@ -2393,13 +2472,36 @@ bool mg_open_listener(struct mg_connection *c, const char *url) { } static void write_conn(struct mg_connection *c) { - long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len) - : mg_io_send(c, c->send.buf, c->send.len); + long len; + size_t off = 0; + 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 if (len == MG_IO_ERR) { proto_error(c, false, "tx err"); - } else if (len > 0) { - mg_iobuf_del(&c->send, 0, (size_t) len); + } else if (len > 0 || empty) { + mg_iobuf_del(&c->send, 0, off + (size_t) len); mg_call(c, MG_EV_WRITE, &len); } } diff --git a/src/net_builtin.h b/src/net_builtin.h index 23aead26..66a4128e 100644 --- a/src/net_builtin.h +++ b/src/net_builtin.h @@ -26,6 +26,17 @@ struct mg_tcpip_driver { typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev, 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 // 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. @@ -88,6 +99,9 @@ struct mg_tcpip_if { 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) 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 timer_1000ms; // Timestamp of last 1-second tick uint64_t lease_expire; // DHCP lease expiry time in ms diff --git a/src/sock.c b/src/sock.c index 23f3cb2d..1716bf9b 100644 --- a/src/sock.c +++ b/src/sock.c @@ -299,7 +299,11 @@ static long recv_raw(struct mg_connection *c, void *buf, size_t len) { union usa usa; socklen_t slen = tousa(&c->rem, &usa); n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen); - if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin)); + if (n > 0) { + struct mg_addr rem; + tomgaddr(&usa, &rem, slen != sizeof(usa.sin)); + c->rem = rem; + } } else { n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING); } diff --git a/test/bsd/Makefile b/test/bsd/Makefile index 5740935c..a7dfbf9e 100644 --- a/test/bsd/Makefile +++ b/test/bsd/Makefile @@ -13,7 +13,7 @@ else C_WARN ?= -Wno-deprecated endif -.PHONY: all bsd bsd_test bsd_client bsd_nortos bsd_nortos_test shm_tap_bridge \ +.PHONY: all bsd bsd_test bsd_client bsd_udp bsd_nortos bsd_nortos_test shm_tap_bridge \ shm_port_bridge bsd_s390 bsd_nortos_s390 clean 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 -all bsd: shm_tap_bridge bsd_client FreeRTOS-Kernel bsd_test +all bsd: shm_tap_bridge bsd_client bsd_udp FreeRTOS-Kernel bsd_test ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_test $(ARGS) # also -bsd_nortos: shm_tap_bridge bsd_client bsd_nortos_test +bsd_nortos: shm_tap_bridge bsd_client bsd_udp bsd_nortos_test ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_nortos_test $(ARGS) # requires shm_tap_bridge to be running @@ -70,6 +70,9 @@ bsd_nortos_test: bsd_client: bsd_client.c $(CC) bsd_client.c -pthread -o $@ +bsd_udp: bsd_udp.c + $(CC) bsd_udp.c -o $@ + # bridge shm queue <--> TAP interface shm_tap_bridge: shm_tap_bridge.c shm_queue.h $(CC) shm_tap_bridge.c -pthread -o $@ @@ -79,7 +82,7 @@ bsd_s390: CFLAGS_MONGOOSE += -DMG_ENABLE_BSD_PROTOTYPES=0 bsd_s390: ASAN= bsd_s390: CC = $(DOCKER) mdashnet/s390 cc bsd_s390: RUN = $(DOCKER) --init mdashnet/s390 -bsd_s390: shm_port_bridge bsd_client FreeRTOS-Kernel bsd_test +bsd_s390: shm_port_bridge bsd_client bsd_udp 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' #also @@ -87,14 +90,14 @@ bsd_nortos_s390: CFLAGS_MONGOOSE += -DMG_ENABLE_BSD_PROTOTYPES=0 bsd_nortos_s390: ASAN= bsd_nortos_s390: CC = $(DOCKER) mdashnet/s390 cc bsd_nortos_s390: RUN = $(DOCKER) --init mdashnet/s390 -bsd_nortos_s390: shm_port_bridge bsd_client bsd_nortos_test +bsd_nortos_s390: shm_port_bridge bsd_client bsd_udp 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' shm_port_bridge: shm_port_bridge.c shm_queue.h $(CC) shm_port_bridge.c $(CFLAGS) -pthread -o $@ clean: # Cleanup. Delete built program and all build artifacts - rm -rf bsd_test bsd_client shm_port_bridge *.o *.obj FreeRTOS-Kernel + rm -rf bsd_test bsd_client bsd_udp shm_port_bridge *.o *.obj FreeRTOS-Kernel FreeRTOS-Kernel: # FreeRTOS sources git clone -q -c advice.detachedHead=false --depth 1 -b V11.2.0 https://github.com/FreeRTOS/FreeRTOS-Kernel $@ diff --git a/test/bsd/bsd_udp.c b/test/bsd/bsd_udp.c new file mode 100644 index 00000000..f25dc23c --- /dev/null +++ b/test/bsd/bsd_udp.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/test/bsd/bsd_x_test.c b/test/bsd/bsd_x_test.c index 7a72f62e..42ef7c80 100644 --- a/test/bsd/bsd_x_test.c +++ b/test/bsd/bsd_x_test.c @@ -33,6 +33,175 @@ static int s_abort = 0; #define DASHBOARD(x) \ 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) { char *cmd[4]; pid_t pid; @@ -46,6 +215,18 @@ static pid_t start_client(const char *ip, const char *connections) { 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) { pid_t r = waitpid(pid, status, WNOHANG); ASSERT(r >= 0); @@ -60,12 +241,17 @@ static void check_client(int status) { static int open_listener(int backlog, bool nonblock) { int lfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in sa; + char buf; memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(1234); sa.sin_addr.s_addr = INADDR_ANY; 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(listen(lfd, backlog) == 0); if (nonblock) ASSERT(fcntl(lfd, F_SETFL, O_NONBLOCK) == 0); @@ -130,11 +316,12 @@ static volatile bool s_select_waiting, s_select_done; static volatile bool s_poll_waiting, s_poll_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_tx_held, s_tx_waits, s_tx_fail, s_tx_error; +static volatile bool s_tx_fail, s_tx_error; static volatile bool s_send_waiting; 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_expect_error; +static volatile bool s_udp_ready, s_udp_done; static size_t (*s_tx)(const void *, size_t, struct mg_tcpip_if *); struct client_data { @@ -152,6 +339,7 @@ static void select_task(void *args); static void readiness_task(void *args); static void backlog_task(void *args); static void tx_gate_task(void *args); +static void udp_task(void *args); struct atask_args { bool listener_nonblock; @@ -306,6 +494,21 @@ static void tx_gate_task(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) { char buf; int fd = (int) (uintptr_t) args; @@ -519,12 +722,22 @@ static void task2(void *args) { struct mg_mgr *mgr = (struct mg_mgr *)args; // Event manager char *ip; int status; - pid_t first, second; + pid_t first, second, udp; vTaskDelay(pdMS_TO_TICKS(500)); // 500 ms s_error = false; s_wait = false; 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; ASSERT(xTaskCreate(poll_task, "poll", 512, NULL, configMAX_PRIORITIES - 1, NULL) == pdPASS); @@ -852,6 +1065,20 @@ static void nortos_echo(struct mg_mgr *mgr, struct nortos_echo_client *clients, 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 const char bootstrap = '\0'; static const char grow[512] = {0}; @@ -953,6 +1180,7 @@ static void nortos_test(struct mg_mgr *mgr) { ASSERT(close(fd) == 0); mg_mgr_poll(mgr, 0); DASHBOARD("nortos_nonblocking_connect"); + nortos_udp(mgr, ip); mg_free(ip); } diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/echo.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/echo.c index 8a805f92..b3e84cae 100644 --- a/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/echo.c +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/echo.c @@ -6,7 +6,6 @@ #include "mongoose.h" -#include #include struct echo_server { @@ -43,12 +42,12 @@ int echo_init(uint16_t port) { if (fd < 0 || !echo_nonblock(fd) || bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0 || listen(fd, 3) < 0) { - printf("BSD echo init: %d\n", errno); + MG_ERROR(("BSD echo init: %d", errno)); if (fd >= 0) close(fd); return 0; } s_server.sock = fd; - printf("BSD echo server on port %hu\n", port); + MG_INFO(("BSD echo server on port %hu", port)); return 1; } @@ -80,7 +79,7 @@ void echo_poll(void) { if (fd >= 0) { c = (struct echo_client *) calloc(1, sizeof(*c)); if (c == NULL || !echo_nonblock(fd)) { - printf("BSD echo accept: %d\n", c == NULL ? ENOMEM : errno); + MG_ERROR(("BSD echo accept: %d", c == NULL ? ENOMEM : errno)); close(fd); free(c); } else { diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/Makefile b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/Makefile new file mode 100644 index 00000000..5225ed61 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/Makefile @@ -0,0 +1,36 @@ +# 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_* diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/hal.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/hal.c new file mode 100644 index 00000000..e519bff3 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/hal.c @@ -0,0 +1,125 @@ +// 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) { +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/hal.h b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/hal.h new file mode 100644 index 00000000..4fef7c79 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/hal.h @@ -0,0 +1,225 @@ +// 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 + +#include +#include +#include +#include + +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(); +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/link.ld b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/link.ld new file mode 100644 index 00000000..cacf44e7 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/link.ld @@ -0,0 +1,32 @@ +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 = .; +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/main.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/main.c new file mode 100644 index 00000000..3f772cc6 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/main.c @@ -0,0 +1,69 @@ +// 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; +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/mongoose_config.h b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/mongoose_config.h new file mode 100644 index 00000000..a8672ed2 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/mongoose_config.h @@ -0,0 +1,37 @@ +#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) diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/udp.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/udp.c new file mode 100644 index 00000000..89e840f0 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-udp-baremetal/udp.c @@ -0,0 +1,68 @@ +// 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)); + } + } +}