Track the established state explicitly so polling before connect() reports an error, and document the family contract

This commit is contained in:
J. Nick Koston
2026-09-05 15:03:05 +02:00
parent e86be05f74
commit 457e224e19
3 changed files with 26 additions and 18 deletions
+17 -13
View File
@@ -60,9 +60,9 @@ static int lwip_err_to_errno(err_t err) {
case ERR_MEM:
return ENOMEM;
case ERR_BUF:
return EAGAIN; // no free local port
return EAGAIN; // transient, e.g. no free local port
case ERR_RTE:
return EHOSTUNREACH; // no route or no address yet
return EHOSTUNREACH; // no route, e.g. no address yet
case ERR_VAL:
case ERR_ARG:
return EINVAL;
@@ -449,7 +449,7 @@ err_t LWIPRawImpl::s_connected_fn(void *arg, struct tcp_pcb *pcb, err_t err) {
// LWIP CALLBACK — same constraints as s_err_fn. err is always ERR_OK; a
// failed connect arrives through s_err_fn instead.
auto *arg_this = reinterpret_cast<LWIPRawImpl *>(arg);
arg_this->connect_err_ = 0;
arg_this->connect_err_ = EISCONN;
esphome::wake_loop_any_context();
return ERR_OK;
}
@@ -460,8 +460,8 @@ int LWIPRawImpl::connect(const struct sockaddr *addr, socklen_t addrlen) {
errno = EBADF;
return -1;
}
if (this->connect_err_ != 0) {
errno = EALREADY;
if (this->connect_err_ == EINPROGRESS || this->connect_err_ == EISCONN) {
errno = this->connect_err_ == EINPROGRESS ? EALREADY : EISCONN;
return -1;
}
ip_addr_t ip;
@@ -499,15 +499,19 @@ ConnectPollResult LWIPRawImpl::poll_connect(int &err_out) const {
err_out = this->connect_err_ == 0 || this->connect_err_ == EINPROGRESS ? ECONNRESET : this->connect_err_;
return ConnectPollResult::CONNECT_POLL_ERROR;
}
if (this->connect_err_ == EINPROGRESS) {
yield_to_sys(); // so the SYN-ACK is processed between polls
return ConnectPollResult::CONNECT_POLL_PENDING;
switch (this->connect_err_) {
case EINPROGRESS:
yield_to_sys(); // so the SYN-ACK is processed between polls
return ConnectPollResult::CONNECT_POLL_PENDING;
case EISCONN:
return ConnectPollResult::CONNECT_POLL_CONNECTED;
case 0:
err_out = EINVAL; // no connect was started
return ConnectPollResult::CONNECT_POLL_ERROR;
default:
err_out = this->connect_err_;
return ConnectPollResult::CONNECT_POLL_ERROR;
}
if (this->connect_err_ != 0) {
err_out = this->connect_err_;
return ConnectPollResult::CONNECT_POLL_ERROR;
}
return ConnectPollResult::CONNECT_POLL_CONNECTED;
}
err_t LWIPRawImpl::s_recv_fn(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, err_t err) {
@@ -61,11 +61,11 @@ class LWIPRawCommon {
bool nodelay_ = false;
sa_family_t family_ = 0;
uint8_t recv_timeout_cs_ = 0; // SO_RCVTIMEO in centiseconds (0 = no timeout, max 2.55s)
// State of a connect() started on this socket: 0 when none is pending (or
// it completed), EINPROGRESS while the SYN is out, otherwise the errno the
// State of connect() on this socket: 0 before one was started, EINPROGRESS
// while the SYN is out, EISCONN once established, otherwise the errno the
// lwip callbacks recorded for its failure. Fits the padding byte here.
uint8_t connect_err_ = 0;
static_assert(EINPROGRESS < 256 && ECONNREFUSED < 256 && ECONNRESET < 256 && ETIMEDOUT < 256,
static_assert(EINPROGRESS < 256 && EISCONN < 256 && ECONNREFUSED < 256 && ECONNRESET < 256 && ETIMEDOUT < 256,
"connect_err_ stores errno values in a byte");
};
// The connect state must stay inside the padding: no socket, listening or
@@ -97,6 +97,8 @@ class LWIPRawImpl : public LWIPRawCommon {
}
/// Start a non-blocking connect. Always returns -1 with errno EINPROGRESS
/// when the SYN was queued; completion is reported by poll_connect().
/// addr must be of the family the socket was created with; an IPv4 peer
/// on an AF_INET6 socket arrives as a v4-mapped sockaddr_in6.
int connect(const struct sockaddr *addr, socklen_t addrlen);
// Intentionally unlocked like ready(): reads one pointer and one byte that
// the callbacks write in the order the checks depend on (error byte first,
@@ -157,7 +159,8 @@ class LWIPRawImpl : public LWIPRawCommon {
size_t rx_buf_offset_ = 0;
bool rx_closed_ = false;
};
static_assert(sizeof(LWIPRawImpl) == sizeof(LWIPRawCommon) + sizeof(pbuf *) + sizeof(size_t) + sizeof(void *),
// rx_buf_, rx_buf_offset_, then rx_closed_ padded to a word
static_assert(sizeof(LWIPRawImpl) == sizeof(LWIPRawCommon) + sizeof(pbuf *) + sizeof(size_t) + 4,
"LWIPRawImpl layout changed");
/// Listening socket implementation for LWIP raw TCP.
+2 -1
View File
@@ -145,7 +145,8 @@ inline socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const st
/// Set a sockaddr to the any address and specified port for the IP version used by socket_ip().
socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port);
/// Check a non-blocking connect() for completion without blocking. On
/// Check a non-blocking connect() for completion without blocking. Only
/// meaningful after connect() returned -1 with errno EINPROGRESS. On
/// CONNECT_POLL_ERROR, err_out holds the socket's SO_ERROR (or errno when the
/// poll itself failed) on fd based implementations, and the failure recorded
/// by the lwip callbacks on the raw lwip implementation.