309 lines
9.9 KiB
C++
309 lines
9.9 KiB
C++
/**
|
|
* @file udpSockets.hpp
|
|
* @author Gustice
|
|
* @brief UDP-Socket wrapper for ESP-IDF socket implementation
|
|
*
|
|
* @copyright Copyright (c) 2024
|
|
*/
|
|
#pragma once
|
|
|
|
#include "Task.hpp"
|
|
#include "esp_log.h"
|
|
#include "etherCommon.hpp"
|
|
#include "coreExcept.hpp"
|
|
#include <cstdarg>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include "simpleStreams.hpp"
|
|
|
|
constexpr bool UDP_WITH_TIMEOUT = false;
|
|
|
|
/**
|
|
* @brief UDP-Socket wrapper for ESP-IDF socket implementation
|
|
*/
|
|
class UdpSocket : public ByteStream, public SocketBase {
|
|
public:
|
|
/// @brief Constructor for UDP-Socket-Server
|
|
/// @note Socket not active until call of attach method
|
|
/// @param addr Server/Client address
|
|
UdpSocket(const IpAdr &addr)
|
|
: Local(addr), _other(INADDR_ANY, 0) {
|
|
_listenSockId = socket(addr.ProtocolFamily, SOCK_DGRAM, addr.IpProtocol);
|
|
if (_listenSockId < 0) {
|
|
ESP_LOGE("UDP", "Unable to create socket: errno %d", errno);
|
|
throw CoreException("Unable to create socket");
|
|
}
|
|
ESP_LOGD("UDP", "Socket created");
|
|
|
|
if constexpr (NETBUF_RECVINFO && !USE_IPV6) {
|
|
int enable = 1;
|
|
lwip_setsockopt(_listenSockId, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable));
|
|
}
|
|
|
|
if constexpr (USE_IPV4 && USE_IPV6) {
|
|
if (addr.ProtocolFamily == AF_INET6) {
|
|
// Note that by default IPV6 binds to both protocols, it is must be disabled
|
|
// if both protocols used at the same time (used in CI)
|
|
int opt = 1;
|
|
setsockopt(_listenSockId, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
|
setsockopt(_listenSockId, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
|
|
}
|
|
}
|
|
|
|
if constexpr (UDP_WITH_TIMEOUT) { // Set timeout if desired
|
|
struct timeval timeout {
|
|
.tv_sec = 10,
|
|
.tv_usec = 0,
|
|
};
|
|
setsockopt(_listenSockId, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
|
}
|
|
}
|
|
|
|
/// @brief Destructor
|
|
~UdpSocket() {
|
|
active = false;
|
|
ESP_LOGD("UDP", "Shutting down socket and restarting...");
|
|
shutdown(_listenSockId, 0);
|
|
close(_listenSockId);
|
|
}
|
|
|
|
/// @brief Attach to configured socket
|
|
/// @note Blocks until successful or failed
|
|
/// @return true if successful
|
|
bool bindTo() {
|
|
active = false;
|
|
int err = bind(_listenSockId, Local.asSockAddrPtr(), sizeof(sockaddr_storage));
|
|
if (err < 0) {
|
|
ESP_LOGE("UDP", "Socket unable to bind: errno %d", errno);
|
|
throw CoreException("Socket unable to bind");
|
|
}
|
|
ESP_LOGI("UDP", "Socket bound, port %d", Local.getPort());
|
|
|
|
if constexpr (NETBUF_RECVINFO && !USE_IPV6) {
|
|
_iov.iov_base = _rxBuffer;
|
|
_iov.iov_len = sizeof(_rxBuffer);
|
|
_msg.msg_control = _cmsg_buf;
|
|
_msg.msg_controllen = sizeof(_cmsg_buf);
|
|
_msg.msg_flags = 0;
|
|
_msg.msg_iov = &_iov;
|
|
_msg.msg_iovlen = 1;
|
|
_msg.msg_name = _other.asSockAddrPtr();
|
|
_msg.msg_namelen = _other.Size;
|
|
}
|
|
|
|
active = true;
|
|
return active;
|
|
}
|
|
|
|
/// @brief Read from socket
|
|
/// @note This call blocks until data is received
|
|
/// @return read bytes
|
|
std::vector<uint8_t> read() override {
|
|
char buffer[256];
|
|
int len = 0;
|
|
if constexpr (NETBUF_RECVINFO && !USE_IPV6) {
|
|
len = recvmsg(_listenSockId, &_msg, 0);
|
|
} else {
|
|
socklen_t socklen = _other.Size;
|
|
len = recvfrom(_listenSockId, buffer, sizeof(buffer) - 1, 0, _other.asSockAddrPtr(), &socklen);
|
|
}
|
|
if (len < 0) {
|
|
ESP_LOGE("UDP", "recvfrom failed: errno %d", errno);
|
|
return {};
|
|
}
|
|
|
|
std::string addrStr;
|
|
// Get the sender's ip address as string
|
|
if (Local.ProtocolFamily == PF_INET) {
|
|
addrStr = _other.getIp();
|
|
|
|
if constexpr (NETBUF_RECVINFO && !USE_IPV6) {
|
|
struct cmsghdr *cmsgtmp;
|
|
for (cmsgtmp = CMSG_FIRSTHDR(&_msg); cmsgtmp != NULL; cmsgtmp = CMSG_NXTHDR(&_msg, cmsgtmp)) {
|
|
if (cmsgtmp->cmsg_level == IPPROTO_IP && cmsgtmp->cmsg_type == IP_PKTINFO) {
|
|
struct in_pktinfo *pktinfo;
|
|
pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsgtmp);
|
|
ESP_LOGI("UDP", "dest ip: %s", inet_ntoa(pktinfo->ipi_addr));
|
|
}
|
|
}
|
|
}
|
|
} // else if (Local.ProtocolFamily == PF_INET6) {
|
|
// inet6_ntoa_r(((struct sockaddr_in6 *)&_other.addrStore)->sin6_addr, addrStr, sizeof(addrStr) - 1);
|
|
// }
|
|
|
|
ESP_LOGD("UDP", "Received %d bytes from %s:", len, addrStr.c_str());
|
|
std::vector<uint8_t> ret(buffer, buffer + len);
|
|
return ret;
|
|
}
|
|
|
|
/// @brief Write character to socket
|
|
/// @param b character to write
|
|
void write(uint8_t b) override {}
|
|
|
|
/// @brief Write bytes to socket
|
|
/// @param data payload as series of bytes
|
|
void write(std::vector<uint8_t> data) override {
|
|
write(&data[0], data.size());
|
|
}
|
|
|
|
/// @brief Write string to specific socket
|
|
/// @param message payload
|
|
/// @param dest Destination for message
|
|
void write(std::string &message, IpAdr *dest = nullptr) {
|
|
auto data = message.c_str();
|
|
write((uint8_t *)data, message.size() + 1, dest);
|
|
}
|
|
|
|
/// @brief Write string to specific socket or to last source
|
|
/// @param data payload
|
|
/// @param size size of data
|
|
/// @param addr Destination for message (if nullptr it will be send to last source)
|
|
void write(uint8_t *data, uint size, IpAdr *dest = nullptr) {
|
|
int err;
|
|
if (dest == nullptr) {
|
|
err = sendto(_listenSockId, data, size, 0, _other.asSockAddrPtr(), sizeof(sockaddr_storage));
|
|
} else {
|
|
struct sockaddr_in *ipV4 = (struct sockaddr_in *)dest->asSockAddrPtr();
|
|
// ESP_LOGI("UDP", "EthIP: %s", inet_ntoa(ipV4->sin_addr.s_addr));
|
|
// ESP_LOGI("UDP", "PORT: %i", ntohs(ipV4->sin_port));
|
|
err = sendto(_listenSockId, data, size, 0, dest->asSockAddrPtr(), sizeof(sockaddr_storage));
|
|
}
|
|
|
|
if (err < 0) {
|
|
ESP_LOGE("UDP", "Error occurred during sending: errno %d", errno);
|
|
active = false;
|
|
}
|
|
}
|
|
|
|
/// @brief Standard destination address
|
|
const IpAdr &Local;
|
|
|
|
/// @brief query if stream is still active
|
|
/// @return true if active
|
|
bool isValid() {
|
|
return _listenSockId >= 0;
|
|
}
|
|
|
|
IpAdr getOther() {
|
|
return _other;
|
|
}
|
|
|
|
private:
|
|
int _listenSockId; // os handle for socket
|
|
IpAdr _other; // Remote Station
|
|
char _rxBuffer[128];
|
|
|
|
// Only used if NETBUF_RECVINFO is active
|
|
iovec _iov;
|
|
msghdr _msg;
|
|
u8_t _cmsg_buf[CMSG_SPACE(sizeof(struct in_pktinfo))];
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* @details according to RFC-5424
|
|
* https://datatracker.ietf.org/doc/html/rfc5424
|
|
*/
|
|
class Syslog {
|
|
public:
|
|
static constexpr uint16_t SyslogPort = 514;
|
|
static constexpr std::string_view NilValue = "-";
|
|
|
|
enum class Severity {
|
|
Emergency = 0,
|
|
Alert = 1,
|
|
Critical = 2,
|
|
Error = 3,
|
|
Warning = 4,
|
|
Notice = 5,
|
|
Informational = 6,
|
|
Debug = 7,
|
|
};
|
|
static constexpr uint8_t SeverityMask = 0x000F;
|
|
|
|
enum class Facility {
|
|
kern = 0 << 3,
|
|
User = 1 << 3,
|
|
Mail = 2 << 3,
|
|
Daemon = 3 << 3,
|
|
Auth = 4 << 3,
|
|
Syslog = 5 << 3,
|
|
Lpr = 6 << 3,
|
|
News = 7 << 3,
|
|
Uucp = 8 << 3,
|
|
Cron = 9 << 3,
|
|
Authpriv = 10 << 3,
|
|
Ftp = 11 << 3,
|
|
Ntp = 12 << 3,
|
|
LogAudit = 13 << 3,
|
|
LogAlert = 14 << 3,
|
|
Clock = 15 << 3,
|
|
Local0 = 16 << 3,
|
|
Local1 = 17 << 3,
|
|
Local2 = 18 << 3,
|
|
Local3 = 19 << 3,
|
|
Local4 = 20 << 3,
|
|
Local5 = 21 << 3,
|
|
Local6 = 22 << 3,
|
|
Local7 = 23 << 3,
|
|
};
|
|
|
|
static void log(Severity severity, std::string message) {
|
|
if (_Socket == nullptr) {
|
|
return;
|
|
}
|
|
|
|
char buffer[512];
|
|
// V T H A P M D Msg
|
|
// alternatively with BOM flag UTF8 \xEF\xBB\xBF
|
|
auto pos = sprintf(buffer, "<%i>1 - %s %s - - - %s",
|
|
static_cast<int>(_Facility) | static_cast<int>(severity),
|
|
_HostName.c_str(),
|
|
_AppName.c_str(),
|
|
message.c_str());
|
|
_Socket->write((uint8_t *)buffer, pos, &_Target);
|
|
}
|
|
|
|
template <typename...Params>
|
|
static void logv(Severity severity, const char *fmt, Params&&... args) {
|
|
if (_Socket == nullptr) {
|
|
return;
|
|
}
|
|
// if (severity > _severityThresh) {
|
|
// return;
|
|
// }
|
|
|
|
char buffer[512];
|
|
// V T H A P M D Msg
|
|
// alternatively with BOM flag UTF8 \xEF\xBB\xBF
|
|
auto pos = sprintf(buffer, "<%i>1 - %s %s - - - ",
|
|
static_cast<int>(_Facility) | static_cast<int>(severity),
|
|
_HostName.c_str(),
|
|
_AppName.c_str());
|
|
pos += sprintf(&buffer[pos], fmt, std::forward<Params> (args)...);
|
|
|
|
_Socket->write((uint8_t *)buffer, pos, &_Target);
|
|
}
|
|
|
|
static void setupTask(IpAdr target) {
|
|
if (_Socket) {
|
|
return;
|
|
}
|
|
ESP_LOGI("UDP", "Starting syslog session to %s", target.getIp().c_str());
|
|
_Facility = Facility::Syslog;
|
|
_HostName = "ESP";
|
|
_AppName = "App";
|
|
_Target = IpAdr{target.getIp(), SyslogPort};
|
|
|
|
static fos::Task syslogTask(SyslogTask, "SysLog", 4096);
|
|
}
|
|
|
|
private:
|
|
static void SyslogTask();
|
|
static Facility _Facility;
|
|
static std::string _HostName;
|
|
static std::string _AppName;
|
|
static UdpSocket *_Socket;
|
|
static IpAdr _Target;
|
|
static Severity _severityThresh;
|
|
}; |