From a5827e24d8481d7ae6114f98309300d26bf67ab0 Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 7 Nov 2024 19:47:27 +0100 Subject: [PATCH] different modes for UDP writer, allow to select network --- lib/socketserver/GwSocketHelper.h | 5 + lib/socketserver/GwUdpWriter.cpp | 184 ++++++++++++++++++++++++++---- lib/socketserver/GwUdpWriter.h | 51 ++++++++- web/config.json | 34 +++++- 4 files changed, 250 insertions(+), 24 deletions(-) diff --git a/lib/socketserver/GwSocketHelper.h b/lib/socketserver/GwSocketHelper.h index 8dea507..67ba3a6 100644 --- a/lib/socketserver/GwSocketHelper.h +++ b/lib/socketserver/GwSocketHelper.h @@ -17,4 +17,9 @@ class GwSocketHelper{ if (setsockopt(socket, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) != ESP_OK) return false; return true; } + static bool isMulticast(const String &addr){ + in_addr iaddr; + if (inet_pton(AF_INET,addr.c_str(),&iaddr) != 1) return false; + return IN_MULTICAST(ntohl(iaddr.s_addr)); + } }; \ No newline at end of file diff --git a/lib/socketserver/GwUdpWriter.cpp b/lib/socketserver/GwUdpWriter.cpp index 51a5037..c91880e 100644 --- a/lib/socketserver/GwUdpWriter.cpp +++ b/lib/socketserver/GwUdpWriter.cpp @@ -4,36 +4,175 @@ #include "GwBuffer.h" #include "GwSocketConnection.h" #include "GwSocketHelper.h" +#include "GWWifi.h" + +GwUdpWriter::WriterSocket::WriterSocket(GwLog *l,int p,const String &src,const String &dst, SourceMode sm) : + sourceMode(sm), source(src), destination(dst), port(p),logger(l) +{ + if (inet_pton(AF_INET, dst.c_str(), &dstA.sin_addr) != 1) + { + LOG_ERROR("UDPW: invalid destination ip address %s", dst.c_str()); + return; + } + if (sourceMode != SourceMode::S_UNBOUND) + { + if (inet_pton(AF_INET, src.c_str(), &srcA) != 1) + { + LOG_ERROR("UDPW: invalid source ip address %s", src.c_str()); + return; + } + } + dstA.sin_family=AF_INET; + dstA.sin_port=htons(port); + fd=socket(AF_INET,SOCK_DGRAM,IPPROTO_IP); + if (fd < 0){ + LOG_ERROR("UDPW: unable to create udp socket: %d",errno); + return; + } + int enable = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int)); + switch (sourceMode) + { + case SourceMode::S_SRC: + { + sockaddr_in bindA; + bindA.sin_family = AF_INET; + bindA.sin_port = htons(0); // let system select + bindA.sin_addr = srcA; + if (bind(fd, (struct sockaddr *)&bindA, sizeof(bindA)) != 0) + { + LOG_ERROR("UDPW: bind failed for address %s: %d", source.c_str(), errno); + ::close(fd); + fd = -1; + return; + } + } + break; + case SourceMode::S_MC: + { + if (setsockopt(fd,IPPROTO_IP,IP_MULTICAST_IF,&srcA,sizeof(srcA)) != 0){ + LOG_ERROR("UDPW: unable to set MC source %s: %d",source.c_str(),errno); + ::close(fd); + fd=-1; + return; + } + int loop=0; + setsockopt(fd,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop)); + } + break; + default: + //not bound + break; + } +} +bool GwUdpWriter::WriterSocket::changed(const String &newSrc, const String &newDst){ + if (newDst != destination) return true; + if (sourceMode == SourceMode::S_UNBOUND) return false; + return newSrc != source; +} +size_t GwUdpWriter::WriterSocket::send(const char *buf,size_t len){ + if (fd < 0) return 0; + ssize_t err = sendto(fd,buf,len,0,(struct sockaddr *)&dstA, sizeof(dstA)); + if (err < 0){ + LOG_DEBUG(GwLog::DEBUG,"UDPW %s error sending: %d",destination.c_str(), errno); + return 0; + } + return err; +} GwUdpWriter::GwUdpWriter(const GwConfigHandler *config, GwLog *logger, int minId) { this->config = config; this->logger = logger; this->minId = minId; + port=config->getInt(GwConfigDefinitions::udpwPort); + +} +void GwUdpWriter::checkStaSocket(){ + String src; + String bc; + if (type == T_BCAP || type == T_MCAP || type == T_NORM || type == T_UNKNOWN ) return; + bool connected=false; + if (WiFi.isConnected()){ + src=WiFi.localIP().toString(); + bc=WiFi.broadcastIP().toString(); + connected=true; + } + else{ + if (staSocket == nullptr) return; + } + String dst; + WriterSocket::SourceMode sm=WriterSocket::SourceMode::S_SRC; + switch (type){ + case T_BCALL: + case T_BCSTA: + sm=WriterSocket::SourceMode::S_SRC; + dst=bc; + break; + case T_MCALL: + case T_MCSTA: + dst=config->getString(GwConfigDefinitions::udpwMC); + sm=WriterSocket::SourceMode::S_MC; + break; + + } + if (staSocket != nullptr) + { + if (!connected || staSocket->changed(src, dst)) + { + staSocket->close(); + delete staSocket; + staSocket = nullptr; + LOG_INFO("changing/stopping UDPW(sta) socket"); + } + } + if (staSocket == nullptr && connected) + { + LOG_INFO("creating new UDP(sta) socket src=%s, dst=%s", src.c_str(), dst.c_str()); + staSocket = new WriterSocket(logger, port, src, dst, WriterSocket::SourceMode::S_SRC); + } } void GwUdpWriter::begin() { - fd=socket(AF_INET,SOCK_DGRAM,IPPROTO_IP); - if (fd < 0){ - LOG_ERROR("unable to create udp socket"); - return; + if (type != T_UNKNOWN) return; //already started + type=(UType)(config->getInt(GwConfigDefinitions::udpwType)); + LOG_INFO("UDPW begin, mode=%d",(int)type); + String src=WiFi.softAPIP().toString(); + String dst; + WriterSocket::SourceMode sm=WriterSocket::SourceMode::S_UNBOUND; + bool createApSocket=false; + switch(type){ + case T_BCALL: + case T_BCAP: + createApSocket=true; + dst=WiFi.softAPBroadcastIP().toString(); + sm=WriterSocket::SourceMode::S_SRC; + break; + case T_MCALL: + case T_MCAP: + createApSocket=true; + dst=config->getString(GwConfigDefinitions::udpwMC); + sm=WriterSocket::SourceMode::S_SRC; + break; + case T_NORM: + createApSocket=true; + dst=config->getString(GwConfigDefinitions::udpwAddress); + sm=WriterSocket::SourceMode::S_UNBOUND; } - int enable = 1; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); - setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int)); - port=config->getInt(GwConfigDefinitions::udpwPort); - //TODO: check port - address=config->getString(GwConfigDefinitions::udpwAddress); - LOG_INFO("UDP writer created, address=%s, port=%d", - address.c_str(),port); - inet_pton(AF_INET, address.c_str(), &destination.sin_addr); - destination.sin_family = AF_INET; - destination.sin_port = htons(port); + if (createApSocket){ + LOG_INFO("creating new UDPW(ap) socket src=%s, dst=%s", src.c_str(), dst.c_str()); + apSocket=new WriterSocket(logger,port,src,dst,sm); + } + checkStaSocket(); } void GwUdpWriter::loop(bool handleRead, bool handleWrite) { + if (handleWrite){ + checkStaSocket(); + } } @@ -45,12 +184,17 @@ size_t GwUdpWriter::sendToClients(const char *buf, int source,bool partial) { if (source == minId) return 0; size_t len=strlen(buf); - ssize_t err = sendto(fd,buf,len,0,(struct sockaddr *)&destination, sizeof(destination)); - if (err < 0){ - LOG_DEBUG(GwLog::DEBUG,"UDP writer error sending: %d",errno); - return 0; + bool hasSent=false; + size_t res=0; + if (apSocket != nullptr){ + res=apSocket->send(buf,len); + if (res > 0) hasSent=true; } - return err; + if (staSocket != nullptr){ + res=staSocket->send(buf,len); + if (res > 0) hasSent=true; + } + return hasSent?len:0; } diff --git a/lib/socketserver/GwUdpWriter.h b/lib/socketserver/GwUdpWriter.h index e98ea67..e17a17e 100644 --- a/lib/socketserver/GwUdpWriter.h +++ b/lib/socketserver/GwUdpWriter.h @@ -9,14 +9,59 @@ #include class GwUdpWriter: public GwChannelInterface{ + public: + using UType=enum{ + T_BCALL=0, + T_BCAP=1, + T_BCSTA=2, + T_NORM=3, + T_MCALL=4, + T_MCAP=5, + T_MCSTA=6, + T_UNKNOWN=-1 + }; private: + class WriterSocket{ + public: + int fd=-1; + struct in_addr srcA; + struct sockaddr_in dstA; + String source; + String destination; + int port; + GwLog *logger; + using SourceMode=enum { + S_UNBOUND=0, + S_MC, + S_SRC + }; + SourceMode sourceMode; + WriterSocket(GwLog *logger,int p,const String &src,const String &dst, SourceMode sm); + void close(){ + if (fd > 0){ + ::close(fd); + } + fd=-1; + } + ~WriterSocket(){ + close(); + } + bool changed(const String &newSrc, const String &newDst); + size_t send(const char *buf,size_t len); + }; const GwConfigHandler *config; GwLog *logger; - int fd=-1; + /** + * we use fd/address to send to the AP network + * and fd2,address2 to send to the station network + * for type "normal" we only use fd + */ + WriterSocket *apSocket=nullptr; //also for T_NORM + WriterSocket *staSocket=nullptr; int minId; int port; - String address; - struct sockaddr_in destination; + UType type=T_UNKNOWN; + void checkStaSocket(); public: GwUdpWriter(const GwConfigHandler *config,GwLog *logger,int minId); ~GwUdpWriter(); diff --git a/web/config.json b/web/config.json index b4336d6..3e44a4b 100644 --- a/web/config.json +++ b/web/config.json @@ -842,6 +842,23 @@ "description": "the UDP port we send to", "category": "UDP writer" }, + { + "name": "udpwType", + "label": "remote address type", + "type": "list", + "default": "0", + "description": "to which networks/addresses do we send\nbc-all: send broadcast to AP and wifi client network\nbc-ap: send broadcast to access point only\nbc-cli: send broadcast to wifi client network\nnormal: normal target address\nmc-all: multicast to AP and wifi client network\nmc-ap:multicast to AP network\nmc-cli: muticast to wifi client network", + "list":[ + {"l":"bc-all","v":"0"}, + {"l":"bc-ap","v":"1"}, + {"l":"bc-cli","v":"2"}, + {"l":"normal","v":"3"}, + {"l":"mc-all","v":"4"}, + {"l":"mc-ap","v":"5"}, + {"l":"mc-cli","v":"6"} + ], + "category": "UDP writer" + }, { "name": "udpwAddress", "label": "remote address", @@ -849,7 +866,22 @@ "default": "", "check": "checkIpAddress", "description": "the IP address we connect to in the form 192.168.1.2", - "category": "UDP writer" + "category": "UDP writer", + "condition":{ + "udpwType":["3"] + } + }, + { + "name": "udpwMC", + "label": "multicast address", + "type": "string", + "default": "224.0.0.1", + "check": "checkMCAddress", + "description": "the multicast address we send to 224.0.0.0...239.255.255.255", + "category": "UDP writer", + "condition":{ + "udpwType":["4","5","6"] + } }, { "name": "udpwWriteFilter",