mirror of
https://github.com/esphome/esphome.git
synced 2026-09-22 04:28:43 +00:00
Track the established state explicitly so polling before connect() reports an error, and document the family contract
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user