1
0
mirror of https://github.com/esphome/esphome.git synced 2025-02-28 15:58:16 +00:00

Add support for socket client mode and getaddrinfo

This commit is contained in:
Otto winter 2022-02-02 22:38:27 +01:00
parent 62b366a5ec
commit a39b2c4ac7
No known key found for this signature in database
GPG Key ID: 5B48AF485DF70D0E
10 changed files with 511 additions and 10 deletions

View File

@ -1,5 +1,6 @@
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.core import CORE
CODEOWNERS = ["@esphome/core"]
@ -26,3 +27,6 @@ async def to_code(config):
cg.add_define("USE_SOCKET_IMPL_LWIP_TCP")
elif impl == IMPLEMENTATION_BSD_SOCKETS:
cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS")
if CORE.target_platform in ["esp8266", "esp32"]:
cg.add_define("USE_SOCKET_HAS_LWIP")

View File

@ -48,6 +48,43 @@ class BSDSocketImpl : public Socket {
return make_unique<BSDSocketImpl>(fd);
}
int bind(const struct sockaddr *addr, socklen_t addrlen) override { return ::bind(fd_, addr, addrlen); }
int connect(const struct sockaddr *addr, socklen_t addrlen) override { return ::connect(fd_, addr, addrlen); }
int connect_finished() override {
fd_set wfds;
struct timeval tv;
FD_ZERO(&wfds);
FD_SET(fd_, &wfds);
tv.tv_sec = 0;
tv.tv_usec = 0;
int retval = ::select(fd_ + 1, nullptr, &wfds, nullptr, &tv);
if (retval == -1) {
// reuse errno
return -1;
}
if (retval == 0) {
// timeout, not writable yet
errno = EINPROGRESS;
return -1;
}
if (!FD_ISSET(fd_, &wfds)) {
errno = ECONNREFUSED;
return -1;
}
int so_error;
socklen_t len = sizeof(so_error);
int ret = this->getsockopt(SOL_SOCKET, SO_ERROR, &so_error, &len);
if (ret == -1) {
// reuse errno
return -1;
}
if (so_error == 0) {
return 0;
}
errno = ECONNREFUSED;
return -1;
}
int close() override {
int ret = ::close(fd_);
closed_ = true;

View File

@ -0,0 +1,33 @@
#pragma once
#include <memory>
#include "headers.h"
namespace esphome {
namespace socket {
struct GetaddrinfoFuture {
public:
virtual ~GetaddrinfoFuture() = default;
// returns true when the request has completed (successfully or with an error)
virtual bool completed() = 0;
/**
* @brief Fetch the completed result into res.
*
* Should only be called after completed() returned true.
* Make sure to call freeaddrinfo() to free the addrinfo storage
* when it's no longer needed.
*
* @return See posix getaddrinfo() return values.
*/
virtual int fetch_result(struct addrinfo **res) = 0;
};
std::unique_ptr<GetaddrinfoFuture> getaddrinfo_async(const char *node, const char *service,
const struct addrinfo *hints);
} // namespace socket
} // namespace esphome
#ifdef USE_ESP8266
void freeaddrinfo(struct addrinfo *ai);
#endif

View File

@ -8,6 +8,7 @@
#define LWIP_INTERNAL
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include <cerrno>
#include <cstdint>
#include <sys/types.h>
@ -107,6 +108,34 @@ struct iovec {
#define ESPHOME_INADDR_NONE INADDR_NONE
#endif
#ifndef EAI_FAIL
#define EAI_BADFLAGS (-1)
#define EAI_NONAME (-2)
#define EAI_AGAIN (-3)
#define EAI_FAIL (-4)
#define EAI_FAMILY (-6)
#define EAI_SOCKTYPE (-7)
#define EAI_SERVICE (-8)
#define EAI_MEMORY (-10)
#define EAI_SYSTEM (-11)
#define EAI_OVERFLOW (-12)
#endif // !EAI_FAIL
#ifndef IPPROTO_UDP
#define IPPROTO_UDP 17
#endif
struct addrinfo { // NOLINT(readability-identifier-naming)
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};
#endif // USE_SOCKET_IMPL_LWIP_TCP
#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
@ -118,6 +147,7 @@ struct iovec {
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <netdb.h>
#ifdef USE_ARDUINO
// arduino-esp32 declares a global var called INADDR_NONE which is replaced

View File

@ -0,0 +1,189 @@
#include "getaddrinfo.h"
#include "esphome/core/defines.h"
#ifdef USE_SOCKET_HAS_LWIP
#include <utility>
#include "lwip/dns.h"
#include "lwip/ip_addr.h"
#include "lwip/netdb.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace socket {
static const char *const TAG = "socket.lwipgetaddrinfo";
struct LwipDNSResult {
bool completed;
bool error;
ip_addr_t ipaddr;
};
struct LwipDNSCallbackArg {
std::weak_ptr<LwipDNSResult> res;
};
void lwip_dns_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) {
LwipDNSCallbackArg *arg = reinterpret_cast<LwipDNSCallbackArg *>(callback_arg);
{
std::shared_ptr<LwipDNSResult> result = arg->res.lock();
if (result) {
if (ipaddr == nullptr) {
result->error = true;
} else {
result->error = false;
ip_addr_copy(result->ipaddr, *ipaddr);
}
result->completed = true;
}
}
delete arg; // NOLINT(cppcoreguidelines-owning-memory)
}
class LwipGetaddrinfoFuture : public GetaddrinfoFuture {
public:
LwipGetaddrinfoFuture(std::shared_ptr<LwipDNSResult> result, int hint_ai_socktype, int hint_ai_protocol,
uint16_t portno)
: result_(std::move(result)),
hint_ai_socktype_(hint_ai_socktype),
hint_ai_protocol_(hint_ai_protocol),
portno_(portno) {}
~LwipGetaddrinfoFuture() override = default;
bool completed() override { return result_->completed; }
int fetch_result(struct addrinfo **res) override {
if (res == nullptr)
return EAI_FAIL;
*res = nullptr;
if (!result_->completed)
return EAI_FAIL;
if (result_->error)
return EAI_FAIL;
size_t alloc_size = sizeof(struct addrinfo) + sizeof(struct sockaddr_storage);
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
void *storage = malloc(alloc_size);
memset(storage, 0, alloc_size);
struct addrinfo *ai = reinterpret_cast<struct addrinfo *>(storage);
struct sockaddr_storage *sa = reinterpret_cast<struct sockaddr_storage *>(ai + 1);
#if LWIP_IPV4 && LWIP_IPV6
bool isipv6 = result_->ipaddr.type == IPADDR_TYPE_V6;
#elif LWIP_IPV4
bool isipv6 = false;
#elif LWIP_IPV6
bool isipv6 = true;
#endif
bool istcp = true;
if ((hint_ai_socktype_ != 0 && hint_ai_socktype_ == SOCK_DGRAM) ||
(hint_ai_protocol_ != 0 && hint_ai_protocol_ == IPPROTO_UDP)) {
istcp = false;
}
ai->ai_family = isipv6 ? AF_INET6 : AF_INET;
ai->ai_socktype = istcp ? SOCK_STREAM : SOCK_DGRAM;
ai->ai_protocol = istcp ? IPPROTO_TCP : IPPROTO_UDP;
if (isipv6) {
#if LWIP_IPV6
struct sockaddr_in6 *sa6 = reinterpret_cast<struct sockaddr_in6 *>(sa);
inet6_addr_from_ip6addr(&sa6->sin6_addr, ip_2_ip6(&result_->ipaddr)) sa6->sin6_family = AF_INET6;
sa6->sin6_len = sizeof(struct sockaddr_in6);
sa6->sin6_port = htons(portno_);
#endif // LWIP_IPV6
} else {
struct sockaddr_in *sa4 = reinterpret_cast<struct sockaddr_in *>(sa);
inet_addr_from_ip4addr(&sa4->sin_addr, ip_2_ip4(&result_->ipaddr));
sa4->sin_family = AF_INET;
sa4->sin_len = sizeof(struct sockaddr_in);
sa4->sin_port = htons(portno_);
}
ai->ai_addrlen = sizeof(struct sockaddr_storage);
ai->ai_addr = reinterpret_cast<struct sockaddr *>(sa);
*res = ai;
return 0;
}
protected:
std::shared_ptr<LwipDNSResult> result_;
int hint_ai_socktype_;
int hint_ai_protocol_;
uint16_t portno_;
};
std::unique_ptr<GetaddrinfoFuture> getaddrinfo_async(const char *node, const char *service,
const struct addrinfo *hints) {
std::shared_ptr<LwipDNSResult> result = std::make_shared<LwipDNSResult>();
result->completed = false;
uint16_t portno = 0;
if (service != nullptr) {
optional<uint16_t> i = parse_number<uint16_t>(service);
if (!i.has_value()) {
result->completed = true;
result->error = true;
return std::unique_ptr<GetaddrinfoFuture>{new LwipGetaddrinfoFuture(result, 0, 0, 0)};
}
portno = *i;
}
int hint_ai_socktype = 0, hint_ai_protocol = 0;
uint8_t dns_addrtype = LWIP_DNS_ADDRTYPE_DEFAULT;
if (hints != nullptr) {
hint_ai_socktype = hints->ai_socktype;
hint_ai_protocol = hints->ai_protocol;
if (hints->ai_family == AF_INET) {
dns_addrtype = LWIP_DNS_ADDRTYPE_IPV4;
} else if (hints->ai_family == AF_INET6) {
dns_addrtype = LWIP_DNS_ADDRTYPE_IPV6;
}
}
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
LwipDNSCallbackArg *callback_arg = new LwipDNSCallbackArg;
callback_arg->res = result;
ip_addr_t immediate_result;
err_t err = dns_gethostbyname_addrtype(node, &immediate_result, lwip_dns_callback, callback_arg, dns_addrtype);
if (err == ERR_OK) {
// immediate result
result->completed = true;
result->error = false;
ip_addr_copy(result->ipaddr, immediate_result);
// callback won't be called
delete callback_arg; // NOLINT(cppcoreguidelines-owning-memory)
} else if (err == ERR_INPROGRESS) {
// result notified via callback
} else {
// error
result->completed = true;
result->error = true;
// callback won't be called
delete callback_arg; // NOLINT(cppcoreguidelines-owning-memory)
}
return std::unique_ptr<GetaddrinfoFuture>{
new LwipGetaddrinfoFuture(result, hint_ai_socktype, hint_ai_protocol, portno)};
}
} // namespace socket
} // namespace esphome
#ifdef USE_ESP8266
void freeaddrinfo(struct addrinfo *ai) {
while (ai != NULL) {
struct addrinfo *next = ai->ai_next;
delete ai;
ai = next;
}
}
#endif
#endif // USE_SOCKET_HAS_LWIP

View File

@ -69,7 +69,7 @@ class LWIPRawImpl : public Socket {
}
if (name == nullptr) {
errno = EINVAL;
return 0;
return -1;
}
ip_addr_t ip;
in_port_t port;
@ -126,6 +126,76 @@ class LWIPRawImpl : public Socket {
}
return 0;
}
int connect(const struct sockaddr *addr, socklen_t addrlen) override {
if (pcb_ == nullptr) {
errno = EBADF;
return -1;
}
if (addr == nullptr) {
errno = EINVAL;
return -1;
}
if (connecting_) {
errno = EALREADY;
return -1;
}
ip_addr_t ipaddr;
uint16_t port;
if (addr->sa_family == AF_INET) {
const struct sockaddr_in *sa4 = reinterpret_cast<const struct sockaddr_in *>(addr);
inet_addr_to_ip4addr(ip_2_ip4(&ipaddr), &sa4->sin_addr);
#if LWIP_IPV4 && LWIP_IPV6
ipaddr.type = IPADDR_TYPE_V4;
#endif
port = ntohs(sa4->sin_port);
#if LWIP_IPV6
} else if (addr->sa_family == AF_INET6) {
const struct sockaddr_in6 *sa6 = reinterpret_cast<const struct sockaddr_in6 *>(addr);
inet6_addr_to_ip6addr(ip_2_ip6(&ipaddr), &sa6->sin_addr);
ipaddr.type = IPADDR_TYPE_V6;
port = ntohs(sa6->sin_port);
#endif // LWIP_IPV6
} else {
errno = EAFNOSUPPORT;
return -1;
}
connecting_ = true;
connected_ = false;
connect_error_ = false;
LWIP_LOG("tcp_connect(%u)", port);
err_t err = tcp_connect(pcb_, &ipaddr, port, LWIPRawImpl::s_connected_fn);
if (err == ERR_VAL) {
errno = EINVAL;
return -1;
}
if (err != ERR_OK) {
errno = EIO;
return -1;
}
errno = EINPROGRESS;
return -1;
}
int connect_finished() override {
if (connected_) {
return 0;
}
if (connect_error_) {
errno = ECONNREFUSED;
return -1;
}
if (connecting_) {
errno = EINPROGRESS;
return -1;
}
// no connect started
errno = EALREADY;
return -1;
}
int close() override {
if (pcb_ == nullptr) {
errno = ECONNRESET;
@ -369,9 +439,10 @@ class LWIPRawImpl : public Socket {
for (int i = 0; i < iovcnt; i++) {
ssize_t err = read(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
if (err == -1) {
if (ret != 0)
if (ret != 0) {
// if we already read some don't return an error
break;
}
return err;
}
ret += err;
@ -433,9 +504,10 @@ class LWIPRawImpl : public Socket {
ssize_t written = internal_write(buf, len);
if (written == -1)
return -1;
if (written == 0)
if (written == 0) {
// no need to output if nothing written
return 0;
}
if (nodelay_) {
int err = internal_output();
if (err == -1)
@ -448,18 +520,20 @@ class LWIPRawImpl : public Socket {
for (int i = 0; i < iovcnt; i++) {
ssize_t err = internal_write(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
if (err == -1) {
if (written != 0)
if (written != 0) {
// if we already read some don't return an error
break;
}
return err;
}
written += err;
if ((size_t) err != iov[i].iov_len)
break;
}
if (written == 0)
if (written == 0) {
// no need to output if nothing written
return 0;
}
if (nodelay_) {
int err = internal_output();
if (err == -1)
@ -524,6 +598,18 @@ class LWIPRawImpl : public Socket {
}
return ERR_OK;
}
err_t connected_fn(err_t err) {
LWIP_LOG("connected(err=%d)", err);
if (err != ERR_OK) {
connected_ = false;
connect_error_ = false;
} else {
connected_ = true;
connect_error_ = true;
}
connecting_ = false;
return ERR_OK;
}
static err_t s_accept_fn(void *arg, struct tcp_pcb *newpcb, err_t err) {
LWIPRawImpl *arg_this = reinterpret_cast<LWIPRawImpl *>(arg);
@ -540,6 +626,11 @@ class LWIPRawImpl : public Socket {
return arg_this->recv_fn(pb, err);
}
static err_t s_connected_fn(void *arg, struct tcp_pcb *pcb, err_t err) {
LWIPRawImpl *arg_this = reinterpret_cast<LWIPRawImpl *>(arg);
return arg_this->connected_fn(err);
}
protected:
int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) {
if (family_ == AF_INET) {
@ -590,6 +681,9 @@ class LWIPRawImpl : public Socket {
// instead use it for determining whether to call lwip_output
bool nodelay_ = false;
sa_family_t family_ = 0;
bool connecting_ = false;
bool connected_ = false;
bool connect_error_ = false;
};
std::unique_ptr<Socket> socket(int domain, int type, int protocol) {

View File

@ -7,7 +7,7 @@ namespace esphome {
namespace socket {
std::unique_ptr<Socket> socket_ip(int type, int protocol) {
#if LWIP_IPV6
#ifdef USE_SOCKET_IPV6
return socket(AF_INET6, type, protocol);
#else
return socket(AF_INET, type, protocol);
@ -15,7 +15,7 @@ std::unique_ptr<Socket> socket_ip(int type, int protocol) {
}
socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port) {
#if LWIP_IPV6
#if USE_SOCKET_IPV6
if (addrlen < sizeof(sockaddr_in6)) {
errno = EINVAL;
return 0;

View File

@ -5,6 +5,12 @@
#include "headers.h"
#include "esphome/core/optional.h"
#ifdef USE_SOCKET_IMPL_LWIP_TCP
#if LWIP_IPV6
#define USE_SOCKET_IPV6
#endif
#endif
namespace esphome {
namespace socket {
@ -17,10 +23,17 @@ class Socket {
virtual std::unique_ptr<Socket> accept(struct sockaddr *addr, socklen_t *addrlen) = 0;
virtual int bind(const struct sockaddr *addr, socklen_t addrlen) = 0;
virtual int connect(const struct sockaddr *addr, socklen_t addrlen) = 0;
/**
* @brief Helper to check if a socket connect() that was EINPROGRESS is now finished.
*
* If the connect finnished successfully, returns 0.
* If it's still in progress, returns -1 and sets errno to EINPROGRESS.
* Other errors result in return code -1 and errno like in blocking connect().
*/
virtual int connect_finished() = 0;
virtual int close() = 0;
// not supported yet:
// virtual int connect(const std::string &address) = 0;
// virtual int connect(const struct sockaddr *addr, socklen_t addrlen) = 0;
virtual int shutdown(int how) = 0;
virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;

View File

@ -0,0 +1,99 @@
#include "getaddrinfo.h"
#include "esphome/core/defines.h"
#ifndef USE_SOCKET_HAS_LWIP
#include <thread>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace socket {
static const char *const TAG = "socket.threadgetaddrinfo";
struct ThreadGetaddrinfoResult {
bool completed;
int return_code;
struct addrinfo *res;
};
class ThreadGetaddrinfoFuture : public GetaddrinfoFuture {
public:
ThreadGetaddrinfoFuture(std::shared_ptr<ThreadGetaddrinfoResult> result) : result_(result) {}
~ThreadGetaddrinfoFuture() override = default;
bool completed() override { return result_->completed; }
int fetch_result(struct addrinfo **res) {
if (res == nullptr)
return EAI_FAIL;
*res = nullptr;
if (!result_->completed)
return EAI_FAIL;
if (result_->return_code != 0)
return result_->return_code;
*res = result_->res;
return 0;
}
protected:
std::shared_ptr<ThreadGetaddrinfoResult> result_;
};
void worker(std::shared_ptr<ThreadGetaddrinfoResult> result, const char *node, const char *service,
const struct addrinfo *hints) {
result->return_code = getaddrinfo(node, service, hints, &result->res);
result->completed = true;
if (hints != nullptr) {
delete hints->ai_addr;
delete hints->ai_canonname;
delete hints;
}
delete node;
delete service;
}
std::unique_ptr<GetaddrinfoFuture> getaddrinfo_async(const char *node, const char *service,
const struct addrinfo *hints) {
std::shared_ptr<ThreadGetaddrinfoResult> result = std::make_shared<ThreadGetaddrinfoResult>();
result->completed = false;
struct addrinfo *hints_copy = nullptr;
if (hints != nullptr) {
hints_copy = new struct addrinfo;
hints_copy->ai_flags = hints->ai_flags;
hints_copy->ai_family = hints->ai_family;
hints_copy->ai_socktype = hints->ai_socktype;
hints_copy->ai_protocol = hints->ai_protocol;
hints_copy->ai_addrlen = hints->ai_addrlen;
if (ai->ai_addr != nullptr) {
hints_copy->ai_addr = malloc(hints->ai_addrlen);
memcpy(hints_copy->ai_addr, hints->ai_addr, hints->ai_addrlen);
}
if (ai->ai_canonname != nullptr) {
hints_copy->ai_canonname = strdup(hints->ai_canonname);
}
hints_copy->ai_next = nullptr;
}
const char *node_copy = nullptr, *service_copy = nullptr;
if (node != nullptr)
node_copy = strdup(node);
if (service != nullptr)
service_copy = strdup(service);
std::thread thread(worker, result, node_copy, service_copy, hints_copy);
thread.detach();
return std::unique_ptr<GetaddrinfoFuture>{new ThreadGetaddrinfoFuture(result)};
}
} // namespace socket
} // namespace esphome
#endif // !USE_SOCKET_HAS_LWIP

View File

@ -70,6 +70,7 @@
#ifdef USE_ESP_IDF
#define USE_ARDUINO_VERSION_CODE VERSION_CODE(4, 3, 0)
#endif
#define USE_SOCKET_HAS_LWIP
#endif
// ESP8266-specific feature flags
@ -79,6 +80,7 @@
#define USE_ESP8266_PREFERENCES_FLASH
#define USE_HTTP_REQUEST_ESP8266_HTTPS
#define USE_SOCKET_IMPL_LWIP_TCP
#define USE_SOCKET_HAS_LWIP
#endif
// Disabled feature flags