Add full datagram support for BSD

This commit is contained in:
Sergio R. Caprile committed 2026-10-30 17:08:39 -03:00
1 parent b766c65615
commit cb63083bc3
18 files changed
+1403 -120

No files matched your search

+86 -30
View File
@@ -1,5 +1,6 @@
#include "bsd.h"
#include "dns.h"
#include "util.h"
#include <stdarg.h>
#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) {
+27
View File
@@ -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;
+125 -23
View File
@@ -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);
}
}
+14
View File
@@ -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
+5 -1
View File
@@ -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);
}