[socket] Simplify UDP implementation and deduplicate lwip helpers

This commit is contained in:
J. Nick Koston
2026-08-28 10:26:05 -05:00
parent c71a599f5a
commit 7cbc81ad66
7 changed files with 109 additions and 200 deletions
@@ -48,40 +48,41 @@ int lwip_ip_to_sockaddr(sa_family_t family, const ip_addr_t *ip, uint16_t port_h
}
bool sockaddr_to_lwip(const struct sockaddr *addr, socklen_t addrlen, ip_addr_t *ip, uint16_t *port) {
// headers.h defines sockaddr and sockaddr_in with the same size, so this covers AF_INET
if (addrlen < sizeof(struct sockaddr))
return false;
#if LWIP_IPV6
if (addr->sa_family == AF_INET) {
if (addrlen < sizeof(sockaddr_in))
return false;
auto *addr4 = reinterpret_cast<const sockaddr_in *>(addr);
*port = ntohs(addr4->sin_port);
ip->type = IPADDR_TYPE_V4;
ip->u_addr.ip4.addr = addr4->sin_addr.s_addr;
IP_SET_TYPE_VAL(*ip, IPADDR_TYPE_V4);
ip_2_ip4(ip)->addr = addr4->sin_addr.s_addr;
return true;
}
#if LWIP_IPV6
if (addr->sa_family == AF_INET6) {
if (addrlen < sizeof(sockaddr_in6))
return false;
auto *addr6 = reinterpret_cast<const sockaddr_in6 *>(addr);
*port = ntohs(addr6->sin6_port);
ip->type = IPADDR_TYPE_V6;
memcpy(&ip->u_addr.ip6.addr, &addr6->sin6_addr.un.u8_addr, 16);
return true;
}
#else
if (addr->sa_family == AF_INET) {
if (addrlen < sizeof(sockaddr_in))
return false;
auto *addr4 = reinterpret_cast<const sockaddr_in *>(addr);
*port = ntohs(addr4->sin_port);
ip->addr = addr4->sin_addr.s_addr;
IP_SET_TYPE_VAL(*ip, IPADDR_TYPE_V6);
memcpy(&ip_2_ip6(ip)->addr, &addr6->sin6_addr.un.u8_addr, 16);
return true;
}
#endif
return false;
}
bool sockaddr_to_lwip_bind(sa_family_t family, const struct sockaddr *addr, socklen_t addrlen, ip_addr_t *ip,
uint16_t *port) {
if (!sockaddr_to_lwip(addr, addrlen, ip, port))
return false;
#if LWIP_IPV6
if (family == AF_INET6)
IP_SET_TYPE_VAL(*ip, IPADDR_TYPE_ANY);
#endif
return true;
}
int lwip_bind_err(err_t err) {
if (err == ERR_OK)
return 0;
@@ -3,9 +3,6 @@
#ifdef USE_SOCKET_IMPL_LWIP_TCP
#include <cerrno>
#include <cstring>
#include "headers.h"
#include "lwip/ip.h"
@@ -42,11 +39,15 @@ int lwip_ip_to_sockaddr(sa_family_t family, const ip_addr_t *ip, uint16_t port_h
socklen_t *addrlen);
/// Convert sockaddr to lwip ip_addr_t and host-order port.
/// For IPv6, sets type to IPADDR_TYPE_V6 (callers that need dual-stack should
/// override to IPADDR_TYPE_ANY after calling).
/// Shared by both TCP (LWIPRawCommon) and UDP (LWIPRawUDPImpl) bind/sendto paths.
/// For IPv6, sets type to IPADDR_TYPE_V6 — correct for sendto destinations.
/// Bind paths must use sockaddr_to_lwip_bind() instead.
bool sockaddr_to_lwip(const struct sockaddr *addr, socklen_t addrlen, ip_addr_t *ip, uint16_t *port);
/// sockaddr_to_lwip variant for bind: promotes AF_INET6 sockets to
/// IPADDR_TYPE_ANY so they accept both IPv4 and IPv6 (dual-stack).
bool sockaddr_to_lwip_bind(sa_family_t family, const struct sockaddr *addr, socklen_t addrlen, ip_addr_t *ip,
uint16_t *port);
/// Map lwip bind error to errno. Returns 0 on success, -1 on error with errno set.
int lwip_bind_err(err_t err);
@@ -100,16 +100,10 @@ int LWIPRawCommon::bind(const struct sockaddr *name, socklen_t addrlen) {
}
ip_addr_t ip;
uint16_t port;
if (!sockaddr_to_lwip(name, addrlen, &ip, &port)) {
if (!sockaddr_to_lwip_bind(this->family_, name, addrlen, &ip, &port)) {
errno = EINVAL;
return -1;
}
#if LWIP_IPV6
// Use IPADDR_TYPE_ANY for dual-stack (accept both IPv4 and IPv6)
if (this->family_ == AF_INET6) {
ip.type = IPADDR_TYPE_ANY;
}
#endif
err_t err = tcp_bind(this->pcb_, &ip, port);
LWIP_LOG(" -> err %d", err);
return lwip_bind_err(err);
+48 -107
View File
@@ -24,27 +24,14 @@ namespace esphome::socket {
// ---- LWIPRawUDPSendImpl (send-only) methods ----
LWIPRawUDPSendImpl::LWIPRawUDPSendImpl(sa_family_t family) : family_(family) {
LWIP_LOCK();
#if LWIP_IPV6
this->pcb_ = udp_new_ip_type(family == AF_INET6 ? IPADDR_TYPE_ANY : IPADDR_TYPE_V4);
#else
this->pcb_ = udp_new();
#endif
}
LWIPRawUDPSendImpl::~LWIPRawUDPSendImpl() {
// Early return avoids acquiring the lwip lock when pcb_ is already null
// (e.g., after LWIPRawUDPImpl::close() already cleaned up).
if (this->pcb_ == nullptr)
return;
LWIP_LOCK();
udp_remove(this->pcb_);
this->pcb_ = nullptr;
// Guard avoids acquiring the lwip lock when already closed
if (this->pcb_ != nullptr)
this->close();
}
int LWIPRawUDPSendImpl::bind_internal_locked_(const struct sockaddr *name, socklen_t addrlen) {
// Caller must hold LWIP_LOCK
int LWIPRawUDPSendImpl::bind(const struct sockaddr *name, socklen_t addrlen) {
LWIP_LOCK();
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
@@ -55,26 +42,13 @@ int LWIPRawUDPSendImpl::bind_internal_locked_(const struct sockaddr *name, sockl
}
ip_addr_t ip;
uint16_t port;
if (!sockaddr_to_lwip(name, addrlen, &ip, &port)) {
if (!sockaddr_to_lwip_bind(this->family_, name, addrlen, &ip, &port)) {
errno = EINVAL;
return -1;
}
#if LWIP_IPV6
// For bind, use IPADDR_TYPE_ANY on IPv6 sockets to accept both IPv4 and IPv6
// packets (dual-stack). sockaddr_to_lwip uses IPADDR_TYPE_V6 which is correct
// for sendto destinations but too restrictive for bind.
if (this->family_ == AF_INET6) {
ip.type = IPADDR_TYPE_ANY;
}
#endif
return lwip_bind_err(udp_bind(this->pcb_, &ip, port));
}
int LWIPRawUDPSendImpl::bind(const struct sockaddr *name, socklen_t addrlen) {
LWIP_LOCK();
return this->bind_internal_locked_(name, addrlen);
}
int LWIPRawUDPSendImpl::close() {
LWIP_LOCK();
return this->close_internal_locked_();
@@ -99,11 +73,6 @@ int LWIPRawUDPSendImpl::ip2sockaddr_(const ip_addr_t *ip, uint16_t port, struct
ssize_t LWIPRawUDPSendImpl::sendto(const void *buf, size_t len, int flags, const struct sockaddr *dest_addr,
socklen_t addrlen) {
(void) flags; // Flags (MSG_DONTWAIT, etc.) are ignored; raw lwip is always non-blocking
LWIP_LOCK();
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
if (buf == nullptr || dest_addr == nullptr) {
errno = EINVAL;
return -1;
@@ -122,6 +91,12 @@ ssize_t LWIPRawUDPSendImpl::sendto(const void *buf, size_t len, int flags, const
return -1;
}
LWIP_LOCK();
if (this->pcb_ == nullptr) {
errno = EBADF;
return -1;
}
// Allocate pbuf and copy data
struct pbuf *pb = pbuf_alloc(PBUF_TRANSPORT, (uint16_t) len, PBUF_RAM);
if (pb == nullptr) {
@@ -164,34 +139,16 @@ int LWIPRawUDPSendImpl::setsockopt(int level, int optname, const void *optval, s
}
return 0;
}
if (level == IPPROTO_IP && optname == IP_ADD_MEMBERSHIP) {
if (level == IPPROTO_IP && (optname == IP_ADD_MEMBERSHIP || optname == IP_DROP_MEMBERSHIP)) {
if (optval == nullptr || optlen < sizeof(struct ip_mreq)) {
errno = EINVAL;
return -1;
}
auto *mreq = reinterpret_cast<const struct ip_mreq *>(optval);
ip4_addr_t multiaddr;
multiaddr.addr = mreq->imr_multiaddr.s_addr;
ip4_addr_t ifaddr;
ifaddr.addr = mreq->imr_interface.s_addr;
err_t err = igmp_joingroup(&ifaddr, &multiaddr);
if (err != ERR_OK) {
errno = EIO;
return -1;
}
return 0;
}
if (level == IPPROTO_IP && optname == IP_DROP_MEMBERSHIP) {
if (optval == nullptr || optlen < sizeof(struct ip_mreq)) {
errno = EINVAL;
return -1;
}
auto *mreq = reinterpret_cast<const struct ip_mreq *>(optval);
ip4_addr_t multiaddr;
multiaddr.addr = mreq->imr_multiaddr.s_addr;
ip4_addr_t ifaddr;
ifaddr.addr = mreq->imr_interface.s_addr;
err_t err = igmp_leavegroup(&ifaddr, &multiaddr);
ip4_addr_t multiaddr{mreq->imr_multiaddr.s_addr};
ip4_addr_t ifaddr{mreq->imr_interface.s_addr};
err_t err =
optname == IP_ADD_MEMBERSHIP ? igmp_joingroup(&ifaddr, &multiaddr) : igmp_leavegroup(&ifaddr, &multiaddr);
if (err != ERR_OK) {
errno = EIO;
return -1;
@@ -232,12 +189,9 @@ int LWIPRawUDPSendImpl::setblocking(bool blocking) {
// ---- LWIPRawUDPImpl methods ----
LWIPRawUDPImpl::LWIPRawUDPImpl(sa_family_t family) : LWIPRawUDPSendImpl(family) {
// Register recv here (not in bind) so unbound client sockets can receive replies
if (this->pcb_ != nullptr) {
LWIP_LOCK();
udp_recv(this->pcb_, LWIPRawUDPImpl::s_recv_fn, this);
}
LWIPRawUDPImpl::LWIPRawUDPImpl(sa_family_t family, struct udp_pcb *pcb) : LWIPRawUDPSendImpl(family, pcb) {
// Registered here (not in bind) so unbound client sockets can receive replies
udp_recv(this->pcb_, LWIPRawUDPImpl::s_recv_fn, this);
}
LWIPRawUDPImpl::~LWIPRawUDPImpl() {
@@ -252,15 +206,10 @@ int LWIPRawUDPImpl::close() {
if (this->pcb_ != nullptr) {
udp_recv(this->pcb_, nullptr, nullptr);
}
// Flush any queued rx packets
while (this->rx_count_ > 0) {
auto &pkt = this->rx_queue_[this->rx_read_idx_];
if (pkt.pb != nullptr) {
pbuf_free(pkt.pb);
pkt.pb = nullptr;
}
// Flush queued rx packets; slots within rx_count_ always hold a live pbuf
for (; this->rx_count_ > 0; this->rx_count_--) {
pbuf_free(this->rx_queue_[this->rx_read_idx_].pb);
this->rx_read_idx_ = (this->rx_read_idx_ + 1) & UDP_RX_MASK;
this->rx_count_--;
}
// close_internal_locked_() returns EBADF if already closed, which is fine from destructor
return this->close_internal_locked_();
@@ -284,32 +233,18 @@ ssize_t LWIPRawUDPImpl::recvfrom(void *buf, size_t len, struct sockaddr *src_add
}
auto &pkt = this->rx_queue_[this->rx_read_idx_];
size_t pkt_len = pkt.pb->tot_len;
size_t copy_len = std::min(len, pkt_len);
// Fill in source address if requested.
// If ip2sockaddr_ fails (e.g., addrlen too small), fail the recvfrom but
// still consume the packet — the failure is deterministic (depends only on
// family_ and *addrlen), so keeping the packet would wedge the queue forever.
bool addr_ok = true;
if (src_addr != nullptr && addrlen != nullptr &&
this->ip2sockaddr_(&pkt.src_addr, pkt.src_port, src_addr, addrlen) != 0) {
addr_ok = false;
// On address conversion failure, still consume the packet — the failure is
// deterministic (family_ and *addrlen), so keeping it would wedge the queue
ssize_t ret = -1;
if (src_addr == nullptr || addrlen == nullptr ||
this->ip2sockaddr_(&pkt.src_addr, pkt.src_port, src_addr, addrlen) == 0) {
ret = (ssize_t) std::min(len, (size_t) pkt.pb->tot_len);
pbuf_copy_partial(pkt.pb, buf, ret, 0);
}
// Copy data from pbuf chain — done after validation so caller buffer is
// not modified on error paths.
if (addr_ok) {
pbuf_copy_partial(pkt.pb, buf, copy_len, 0);
}
// Free the pbuf and advance the read pointer
pbuf_free(pkt.pb);
pkt.pb = nullptr;
this->rx_read_idx_ = (this->rx_read_idx_ + 1) & UDP_RX_MASK;
this->rx_count_--;
return addr_ok ? (ssize_t) copy_len : -1;
return ret;
}
void LWIPRawUDPImpl::s_recv_fn(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) {
@@ -347,29 +282,35 @@ void LWIPRawUDPImpl::recv_fn_(struct pbuf *p, const ip_addr_t *addr, u16_t port)
// ---- UDP Factory functions ----
static struct udp_pcb *new_udp_pcb(int domain) {
#if LWIP_IPV6
return udp_new_ip_type(domain == AF_INET6 ? IPADDR_TYPE_ANY : IPADDR_TYPE_V4);
#else
return udp_new();
#endif
}
std::unique_ptr<UDPSendSocket> socket_udp_send(int domain, int protocol) {
(void) protocol; // Raw lwip UDP ignores protocol; kept for API compatibility
auto sock = make_unique<LWIPRawUDPSendImpl>((sa_family_t) domain);
if (!sock->is_valid()) {
LWIP_LOCK();
auto *pcb = new_udp_pcb(domain);
if (pcb == nullptr) {
errno = ENOMEM;
return nullptr;
}
return sock;
return make_unique<LWIPRawUDPSendImpl>((sa_family_t) domain, pcb);
}
std::unique_ptr<UDPSocket> socket_udp(int domain, int protocol) {
(void) protocol; // Raw lwip UDP ignores protocol; kept for API compatibility
auto sock = make_unique<LWIPRawUDPImpl>((sa_family_t) domain);
if (!sock->is_valid()) {
LWIP_LOCK();
auto *pcb = new_udp_pcb(domain);
if (pcb == nullptr) {
errno = ENOMEM;
return nullptr;
}
return sock;
}
std::unique_ptr<UDPSocket> socket_udp_loop_monitored(int domain, int protocol) {
// LWIPRawUDPImpl has wake built into the recv callback, so no extra monitoring needed
return socket_udp(domain, protocol);
// Ctor registers the recv callback under the lock held here
return make_unique<LWIPRawUDPImpl>((sa_family_t) domain, pcb);
}
#undef LWIP_LOCK
+13 -16
View File
@@ -4,9 +4,7 @@
#ifdef USE_SOCKET_IMPL_LWIP_TCP
#include <array>
#include <cerrno>
#include <cstring>
#include <memory>
#include <cstdint>
#include "headers.h"
#include "lwip/ip.h"
@@ -19,7 +17,8 @@ namespace esphome::socket {
/// No receive capability — use LWIPRawUDPImpl for sockets that need to receive.
class LWIPRawUDPSendImpl {
public:
LWIPRawUDPSendImpl(sa_family_t family);
/// The pcb is allocated by the factory (like the TCP impl); never null here.
LWIPRawUDPSendImpl(sa_family_t family, struct udp_pcb *pcb) : pcb_(pcb), family_(family) {}
~LWIPRawUDPSendImpl();
LWIPRawUDPSendImpl(const LWIPRawUDPSendImpl &) = delete;
LWIPRawUDPSendImpl &operator=(const LWIPRawUDPSendImpl &) = delete;
@@ -35,7 +34,6 @@ class LWIPRawUDPSendImpl {
int setblocking(bool blocking);
bool is_valid() const { return this->pcb_ != nullptr; }
bool ready() const { return false; }
int get_fd() const { return -1; }
@@ -43,14 +41,11 @@ class LWIPRawUDPSendImpl {
/// Convert lwip ip_addr_t and port to sockaddr.
int ip2sockaddr_(const ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen);
/// Shared bind logic — parses sockaddr and calls udp_bind. Caller must hold LWIP_LOCK.
int bind_internal_locked_(const struct sockaddr *name, socklen_t addrlen);
/// Shared close logic — unregisters and removes udp pcb. Caller must hold LWIP_LOCK.
/// Shared close logic — removes the udp pcb. Caller must hold LWIP_LOCK.
int close_internal_locked_();
struct udp_pcb *pcb_{nullptr};
sa_family_t family_{0};
struct udp_pcb *pcb_;
sa_family_t family_;
};
/// UDP socket with receive support for LWIP raw API.
@@ -59,13 +54,13 @@ class LWIPRawUDPSendImpl {
/// pointer would leak queued pbufs on destruction).
class LWIPRawUDPImpl : private LWIPRawUDPSendImpl {
public:
LWIPRawUDPImpl(sa_family_t family);
/// Caller (the factory) must hold the lwip lock; registers the recv callback.
LWIPRawUDPImpl(sa_family_t family, struct udp_pcb *pcb);
~LWIPRawUDPImpl();
using LWIPRawUDPSendImpl::bind;
using LWIPRawUDPSendImpl::get_fd;
using LWIPRawUDPSendImpl::getsockopt;
using LWIPRawUDPSendImpl::is_valid;
using LWIPRawUDPSendImpl::sendto;
using LWIPRawUDPSendImpl::setblocking;
using LWIPRawUDPSendImpl::setsockopt;
@@ -101,10 +96,12 @@ class LWIPRawUDPImpl : private LWIPRawUDPSendImpl {
static constexpr uint8_t UDP_RX_QUEUE_SIZE = 4;
static constexpr uint8_t UDP_RX_MASK = UDP_RX_QUEUE_SIZE - 1;
static_assert((UDP_RX_QUEUE_SIZE & UDP_RX_MASK) == 0, "UDP_RX_QUEUE_SIZE must be power of 2");
// Fields are written by recv_fn_ before rx_count_ makes a slot visible,
// so per-member initializers would only add dead zeroing of the array.
struct UDPRxPacket {
ip_addr_t src_addr{};
struct pbuf *pb{nullptr};
uint16_t src_port{0};
ip_addr_t src_addr;
struct pbuf *pb;
uint16_t src_port;
};
std::array<UDPRxPacket, UDP_RX_QUEUE_SIZE> rx_queue_{};
uint16_t rx_dropped_{0};
+1 -7
View File
@@ -116,13 +116,7 @@ size_t format_sockaddr_to(const struct sockaddr *addr_ptr, socklen_t len, std::s
return 0;
}
std::unique_ptr<Socket> socket_ip(int type, int protocol) {
#if USE_NETWORK_IPV6
return socket(AF_INET6, type, protocol);
#else
return socket(AF_INET, type, protocol);
#endif /* USE_NETWORK_IPV6 */
}
std::unique_ptr<Socket> socket_ip(int type, int protocol) { return socket(IP_DOMAIN, type, protocol); }
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_address, uint16_t port) {
#if USE_NETWORK_IPV6
+22 -41
View File
@@ -49,6 +49,13 @@ using UDPSendSocket = LWIPRawUDPSendImpl;
using UDPSocket = LWIPRawUDPImpl;
#endif
// Domain used by the socket_ip_* helpers: newest available IP domain.
#if USE_NETWORK_IPV6
inline constexpr int IP_DOMAIN = AF_INET6;
#else
inline constexpr int IP_DOMAIN = AF_INET;
#endif
#ifdef USE_LWIP_FAST_SELECT
/// Shared ready() helper using cached lwip_sock pointer for direct rcvevent read.
/// cached_sock == nullptr means the socket is not monitored (monitor_loop was false, fd
@@ -113,56 +120,34 @@ std::unique_ptr<Socket> socket_ip(int type, int protocol);
/// File descriptors >= FD_SETSIZE will not be monitored and will log an error.
std::unique_ptr<Socket> socket_loop_monitored(int domain, int type, int protocol);
/// Create a send-only UDP socket of the given domain and protocol.
/// Create a send-only UDP socket (socket_udp_send), a UDP socket with receive
/// support (socket_udp), or a UDP socket monitored for data in the main loop
/// (socket_udp_loop_monitored).
#ifdef USE_SOCKET_IMPL_LWIP_TCP
std::unique_ptr<UDPSendSocket> socket_udp_send(int domain, int protocol);
std::unique_ptr<UDPSocket> socket_udp(int domain, int protocol);
// Wake is built into the recv callback, so monitoring needs nothing extra.
inline std::unique_ptr<UDPSocket> socket_udp_loop_monitored(int domain, int protocol) {
return socket_udp(domain, protocol);
}
#else
inline std::unique_ptr<UDPSendSocket> socket_udp_send(int domain, int protocol) {
return esphome::socket::socket(domain, SOCK_DGRAM, protocol);
}
#endif
/// Create a send-only UDP socket in the newest available IP domain.
inline std::unique_ptr<UDPSendSocket> socket_ip_udp_send(int protocol) {
#if USE_NETWORK_IPV6
return socket_udp_send(AF_INET6, protocol);
#else
return socket_udp_send(AF_INET, protocol);
#endif
}
/// Create a UDP socket (send + receive) of the given domain and protocol.
#ifdef USE_SOCKET_IMPL_LWIP_TCP
std::unique_ptr<UDPSocket> socket_udp(int domain, int protocol);
#else
inline std::unique_ptr<UDPSocket> socket_udp(int domain, int protocol) {
return esphome::socket::socket(domain, SOCK_DGRAM, protocol);
}
#endif
/// Create a UDP socket (send + receive) in the newest available IP domain.
inline std::unique_ptr<UDPSocket> socket_ip_udp(int protocol) {
#if USE_NETWORK_IPV6
return socket_udp(AF_INET6, protocol);
#else
return socket_udp(AF_INET, protocol);
#endif
}
/// Create a UDP socket and monitor it for data in the main loop.
/// On LWIP_TCP platforms, wake is built into the recv callback so this just delegates to socket_udp().
/// On BSD/LWIP_SOCKETS platforms, this registers the socket with the Application's select() loop.
#ifdef USE_SOCKET_IMPL_LWIP_TCP
std::unique_ptr<UDPSocket> socket_udp_loop_monitored(int domain, int protocol);
#else
// Registers the socket with the Application's select() loop.
inline std::unique_ptr<UDPSocket> socket_udp_loop_monitored(int domain, int protocol) {
return socket_loop_monitored(domain, SOCK_DGRAM, protocol);
}
#endif
/// socket_udp* variants using the newest available IP domain.
inline std::unique_ptr<UDPSendSocket> socket_ip_udp_send(int protocol) { return socket_udp_send(IP_DOMAIN, protocol); }
inline std::unique_ptr<UDPSocket> socket_ip_udp(int protocol) { return socket_udp(IP_DOMAIN, protocol); }
inline std::unique_ptr<UDPSocket> socket_ip_udp_loop_monitored(int protocol) {
#if USE_NETWORK_IPV6
return socket_udp_loop_monitored(AF_INET6, protocol);
#else
return socket_udp_loop_monitored(AF_INET, protocol);
#endif
return socket_udp_loop_monitored(IP_DOMAIN, protocol);
}
/// Create a listening socket of the given domain, type and protocol.
@@ -182,11 +167,7 @@ inline std::unique_ptr<ListenSocket> socket_listen_loop_monitored(int domain, in
}
#endif
inline std::unique_ptr<ListenSocket> socket_ip_loop_monitored(int type, int protocol) {
#if USE_NETWORK_IPV6
return socket_listen_loop_monitored(AF_INET6, type, protocol);
#else
return socket_listen_loop_monitored(AF_INET, type, protocol);
#endif
return socket_listen_loop_monitored(IP_DOMAIN, type, protocol);
}
/// Set a sockaddr to the specified address and port for the IP version used by socket_ip().