Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 41 additions & 24 deletions ArduinoCore-Linux/cores/arduino/Ethernet.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,48 +170,55 @@ class EthernetClient : public Client {

// checks if we are connected - using a timeout
virtual uint8_t connected() override {
if (!is_connected) return false; // connect has failed
if (p_sock->connected()) return true; // check socket
long timeout = millis() + getConnectionTimeout();
uint8_t result = p_sock->connected();
while (result <= 0 && millis() < timeout) {
delay(200);
result = p_sock->connected();
}
return result;
if (!is_connected || !p_sock) return false;

// A disconnected socket should be reported immediately. Retrying here
// turns normal peer shutdown into a multi-second stall for callers like
// TelnetClient::closeOnDisconnect().
is_connected = p_sock->connected();
return is_connected;
}

// support conversion to bool
operator bool() override { return connected(); }

// opens a conection
virtual int connect(IPAddress ipAddress, uint16_t port) override {
return connect(ipAddress, port, getConnectionTimeout());
}

int connect(IPAddress ipAddress, uint16_t port, int32_t timeout_ms) {
String str = String(ipAddress[0]) + String(".") + String(ipAddress[1]) +
String(".") + String(ipAddress[2]) + String(".") +
String(ipAddress[3]);
this->address = ipAddress;
this->port = port;
return connect(str.c_str(), port);
return connect(str.c_str(), port, timeout_ms);
}

// opens a connection
virtual int connect(const char* address, uint16_t port) override {
return connect(address, port, getConnectionTimeout());
}

int connect(const char* address, uint16_t port, int32_t timeout_ms) {
Logger.info(WIFICLIENT, "connect");
this->port = port;
if (connectedFast()) {
p_sock->close();
}
IPAddress adr = resolveAddress(address, port);
IPAddress adr = resolveAddress(address);
if (adr == IPAddress(0, 0, 0, 0)) {
is_connected = false;
return 0;
}

// performs the actual connection
String str = adr.toString();
Logger.info("Connecting to ", str.c_str());
p_sock->connect(str.c_str(), port);
is_connected = true;
return 1;
int result = p_sock->connect(str.c_str(), port, timeout_ms);
is_connected = result > 0;
return is_connected ? 1 : 0;
}

virtual size_t write(char c) { return write((uint8_t)c); }
Expand Down Expand Up @@ -334,21 +341,31 @@ class EthernetClient : public Client {
IPAddress address{0, 0, 0, 0};
uint16_t port = 0;

IPAddress resolveHostname(const char* hostname) {
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;

struct addrinfo* result = nullptr;
if (getaddrinfo(hostname, nullptr, &hints, &result) != 0 || result == nullptr) {
Logger.error(WIFICLIENT, "Hostname resolution failed");
return IPAddress(0, 0, 0, 0);
}

auto* addr = reinterpret_cast<struct sockaddr_in*>(result->ai_addr);
IPAddress resolved(addr->sin_addr.s_addr);
freeaddrinfo(result);
return resolved;
}

// resolves the address and returns sockaddr_in
IPAddress resolveAddress(const char* address, uint16_t port) {
IPAddress resolveAddress(const char* address) {
struct sockaddr_in serv_addr4;
memset(&serv_addr4, 0, sizeof(serv_addr4));
serv_addr4.sin_family = AF_INET;
serv_addr4.sin_port = htons(port);
if (::inet_pton(AF_INET, address, &serv_addr4.sin_addr) <= 0) {
// Not an IP, try to resolve hostname
struct hostent* he = ::gethostbyname(address);
if (he == nullptr || he->h_addr_list[0] == nullptr) {
Logger.error(WIFICLIENT, "Hostname resolution failed");
serv_addr4.sin_addr.s_addr = 0;
} else {
memcpy(&serv_addr4.sin_addr, he->h_addr_list[0], he->h_length);
}
return resolveHostname(address);
}
return IPAddress(serv_addr4.sin_addr.s_addr);
}
Expand Down
177 changes: 155 additions & 22 deletions ArduinoCore-Linux/cores/arduino/SocketImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <cerrno>
#ifdef __APPLE__
#include <net/route.h>
#include <sys/sysctl.h>
Expand All @@ -48,30 +50,57 @@ uint8_t SocketImpl::connected() {
if (sock < 0) return false;
char buf[2];
int result = ::recv(sock, &buf, 1, MSG_PEEK | MSG_DONTWAIT);
// if peek is working we are connected - if not we do further checks
is_connected = result >= 0;
if (!is_connected) {
int error_code;
socklen_t error_code_size;
// int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t
// *optlen);
int result =
getsockopt(sock, SOL_SOCKET, SO_ERROR, &error_code, &error_code_size);
if (result != 0) {
char msg[50];
sprintf(msg, "%d", result);
Logger.debug(SOCKET_IMPL, "getsockopt->", msg);
}
if (result > 0) {
is_connected = true;
return true;
}

if (result == 0) {
Logger.info(SOCKET_IMPL, "peer closed connection");
close();
is_connected = false;
return false;
}

if (errno == EAGAIN || errno == EWOULDBLOCK) {
is_connected = true;
return true;
}

is_connected = (result == 0);
int error_code = 0;
socklen_t error_code_size = sizeof(error_code);
// int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t
// *optlen);
int sockopt_result =
getsockopt(sock, SOL_SOCKET, SO_ERROR, &error_code, &error_code_size);
if (sockopt_result != 0) {
char msg[50];
sprintf(msg, "%d", sockopt_result);
Logger.debug(SOCKET_IMPL, "getsockopt->", msg);
}

return is_connected;
if (sockopt_result == 0 && error_code == 0) {
is_connected = true;
return true;
}

close();
is_connected = false;
return false;
}

// opens a conection
int SocketImpl::connect(const char *address, uint16_t port) {
return connect(address, port, -1);
}

int SocketImpl::connect(const char *address, uint16_t port, int32_t timeout_ms) {
if (sock >= 0) {
close();
}

if ((sock = ::socket(AF_INET, SOCK_STREAM, 0)) < 0) {
is_connected = false;
Logger.error(SOCKET_IMPL, "could not create socket");
return -1;
}
Expand All @@ -89,13 +118,77 @@ int SocketImpl::connect(const char *address, uint16_t port) {

// Convert IPv4 and IPv6 addresses from text to binary form
if (::inet_pton(AF_INET, address_ip, &serv_addr.sin_addr) <= 0) {
close();
Logger.error(SOCKET_IMPL, "invalid address");
return -2;
}

if (::connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
Logger.error(SOCKET_IMPL, "could not connect");
return -3;
if (timeout_ms < 0) {
if (::connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
close();
Logger.error(SOCKET_IMPL, "could not connect");
return -3;
}
} else {
int flags = fcntl(sock, F_GETFL, 0);
if (flags < 0) {
flags = 0;
}

if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
close();
Logger.error(SOCKET_IMPL, "could not set nonblocking connect");
return -3;
}

int result = ::connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (result < 0) {
if (errno != EINPROGRESS && errno != EWOULDBLOCK) {
fcntl(sock, F_SETFL, flags);
close();
Logger.error(SOCKET_IMPL, "could not connect");
return -3;
}

fd_set writefds;
FD_ZERO(&writefds);
FD_SET(sock, &writefds);

fd_set errorfds;
FD_ZERO(&errorfds);
FD_SET(sock, &errorfds);

timeval timeout {
.tv_sec = timeout_ms / 1000,
.tv_usec = (timeout_ms % 1000) * 1000,
};
Comment thread
MitchBradley marked this conversation as resolved.
Outdated

result = select(sock + 1, nullptr, &writefds, &errorfds, &timeout);
if (result == 0) {
fcntl(sock, F_SETFL, flags);
close();
Logger.error(SOCKET_IMPL, "connect timeout");
return -4;
}

if (result < 0) {
fcntl(sock, F_SETFL, flags);
close();
Logger.error(SOCKET_IMPL, "select failed during connect");
return -3;
}

int error_code = 0;
socklen_t error_len = sizeof(error_code);
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error_code, &error_len) != 0 || error_code != 0) {
fcntl(sock, F_SETFL, flags);
close();
Logger.error(SOCKET_IMPL, "could not connect");
return -3;
}
}

fcntl(sock, F_SETFL, flags);
}

is_connected = true;
Expand All @@ -112,8 +205,24 @@ size_t SocketImpl::write(const uint8_t *str, size_t len) {

// provides the available bytes
size_t SocketImpl::available() {
if (sock < 0) {
is_connected = false;
return 0;
}

int bytes_available;
ioctl(sock, FIONREAD, &bytes_available);
if (ioctl(sock, FIONREAD, &bytes_available) != 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
close();
is_connected = false;
}
return 0;
}

if (bytes_available == 0 && !connected()) {
return 0;
}

char msg[50];
sprintf(msg, "%d", bytes_available);
Logger.debug(SOCKET_IMPL, "available->", msg);
Expand All @@ -122,7 +231,27 @@ size_t SocketImpl::available() {

// direct read
size_t SocketImpl::read(uint8_t *buffer, size_t len) {
size_t result = ::recv(sock, buffer, len, MSG_DONTWAIT | MSG_NOSIGNAL);
if (sock < 0) {
is_connected = false;
return static_cast<size_t>(-1);
}

ssize_t result = ::recv(sock, buffer, len, MSG_DONTWAIT | MSG_NOSIGNAL);
if (result == 0) {
Logger.info(SOCKET_IMPL, "read EOF");
close();
is_connected = false;
return static_cast<size_t>(-1);
}
Comment on lines +233 to +244

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SocketImpl::read() returns static_cast<size_t>(-1) for EOF and for non-fatal nonblocking cases (EAGAIN/EWOULDBLOCK). Because the return type is size_t, this becomes a very large positive value, and call sites that store it in an int (e.g. EthernetClient::read) can misinterpret errors as successful reads. Prefer returning 0 when no bytes are available/EOF (and updating is_connected), or change the API to return a signed type (ssize_t/int) consistently through the stack.

Copilot uses AI. Check for mistakes.

if (result < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
close();
is_connected = false;
}
return static_cast<size_t>(-1);
}

char lenStr[80];
sprintf(lenStr, "%ld -> %ld", len, result);
Logger.debug(SOCKET_IMPL, "read->", lenStr);
Expand All @@ -139,7 +268,11 @@ int SocketImpl::peek() {

void SocketImpl::close() {
Logger.info(SOCKET_IMPL, "close");
::close(sock);
if (sock >= 0) {
::close(sock);
sock = -1;
}
is_connected = false;
}

// Linux-compatible implementation: parse /proc/net/route for default interface
Expand Down
2 changes: 2 additions & 0 deletions ArduinoCore-Linux/cores/arduino/SocketImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class SocketImpl {
virtual uint8_t connected();
// opens a conection
virtual int connect(const char* address, uint16_t port);
// opens a connection with a timeout in milliseconds
virtual int connect(const char* address, uint16_t port, int32_t timeout_ms);
// sends some data
virtual size_t write(const uint8_t* str, size_t len);
// provides the available bytes
Expand Down
Loading