/** * @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 "utils.hpp" #include #include #include 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 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 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 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(_Facility) | static_cast(severity), _HostName.c_str(), _AppName.c_str(), message.c_str()); _Socket->write((uint8_t *)buffer, pos, &_Target); } template static void logv(Severity severity, const char *fmt, Params&&... args) { 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 - - - ", static_cast(_Facility) | static_cast(severity), _HostName.c_str(), _AppName.c_str()); pos += sprintf(&buffer[pos], fmt, std::forward (args)...); _Socket->write((uint8_t *)buffer, pos, &_Target); } static void setupTask(IpAdr target) { if (_Socket) { return; } _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; };