diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 6040514..32121e1 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -58,14 +58,9 @@ template void CommandDistributor::broadcastReply(clientType t #ifdef CD_HANDLE_RING // wifi or ethernet ring streams with multiple client types RingStream * CommandDistributor::ring=0; - CommandDistributor::clientType CommandDistributor::clients[20]={ - NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE, - NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE, - NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE, - NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE, - NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE}; +CommandDistributor::clientType CommandDistributor::clients[MAX_NUM_TCP_CLIENTS]={ NONE_TYPE }; // 0 is and must be NONE_TYPE -// Parse is called by Wifi or Ethernet interface to determine which +// Parse is called by Withrottle or Ethernet interface to determine which // protocol the client is using and call the appropriate part of dcc++Ex void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) { if (clientId>=sizeof (clients)) { diff --git a/CommandDistributor.h b/CommandDistributor.h index de40bdb..4fba277 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -37,13 +37,13 @@ class CommandDistributor { public: - enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE,WEBSOCK_CONNECTING_TYPE,WEBSOCKET_TYPE}; + enum clientType: byte {NONE_TYPE = 0,COMMAND_TYPE,WITHROTTLE_TYPE,WEBSOCK_CONNECTING_TYPE,WEBSOCKET_TYPE}; // independent of other types, NONE_TYPE must be 0 private: static void broadcastToClients(clientType type); static StringBuffer * broadcastBufferWriter; #ifdef CD_HANDLE_RING static RingStream * ring; - static clientType clients[20]; + static clientType clients[MAX_NUM_TCP_CLIENTS]; #endif public : static void parse(byte clientId,byte* buffer, RingStream * ring); diff --git a/EXmDNS.cpp b/EXmDNS.cpp new file mode 100644 index 0000000..b5250df --- /dev/null +++ b/EXmDNS.cpp @@ -0,0 +1,200 @@ +/* + * © 2024 Harald Barth + * © 2024 Paul M. Antoine + * All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +#include +#include "EthernetInterface.h" +#ifdef DO_MDNS +#include "EXmDNS.h" + +// fixed values for mDNS +static IPAddress mdnsMulticastIPAddr = IPAddress(224, 0, 0, 251); +#define MDNS_SERVER_PORT 5353 + +// dotToLen() +// converts stings of form ".foo.barbar.x" to a string with the +// dots replaced with lenght. So string above would result in +// "\x03foo\x06barbar\x01x" in C notation. If not NULL, *substr +// will point to the beginning of the last component, in this +// example that would be "\x01x". +// +static void dotToLen(char *str, char **substr) { + char *dotplace = NULL; + char *s; + byte charcount = 0; + for (s = str;/*see break*/ ; s++) { + if (*s == '.' || *s == '\0') { + // take care of accumulated + if (dotplace != NULL && charcount != 0) { + *dotplace = charcount; + } + if (*s == '\0') + break; + if (substr && *s == '.') + *substr = s; + // set new values + dotplace = s; + charcount = 0; + } else { + charcount++; + } + } +} + +MDNS::MDNS(EthernetUDP& udp) { + _udp = &udp; +} +MDNS::~MDNS() { + _udp->stop(); + if (_name) free(_name); + if (_serviceName) free(_serviceName); + if (_serviceProto) free(_serviceProto); +} +int MDNS::begin(const IPAddress& ip, char* name) { + // if we were called very soon after the board was booted, we need to give the + // EthernetShield (WIZnet) some time to come up. Hence, we delay until millis() is at + // least 3000. This is necessary, so that if we need to add a service record directly + // after begin, the announce packet does not get lost in the bowels of the WIZnet chip. + //while (millis() < 3000) + // delay(100); + + _ipAddress = ip; + _name = (char *)malloc(strlen(name)+2); + byte n; + for(n = 0; nbeginMulticast(mdnsMulticastIPAddr, MDNS_SERVER_PORT); +} + +int MDNS::addServiceRecord(const char* name, uint16_t port, MDNSServiceProtocol_t proto) { + // we ignore proto, assume TCP + (void)proto; + _serviceName = (char *)malloc(strlen(name) + 2); + byte n; + for(n = 0; n BROADCASTTIME * 1000UL)) { + return; + } + lastrun = now; + DNSHeader_t dnsHeader = {0, 0, 0, 0, 0, 0}; + // DNSHeader_t dnsHeader = { 0 }; + + _udp->beginPacket(mdnsMulticastIPAddr, MDNS_SERVER_PORT); + + // dns header + dnsHeader.flags = HTONS((uint16_t)0x8400); // Response, authorative + dnsHeader.answerCount = HTONS(4 /*5 if TXT but we do not do that */); + _udp->write((uint8_t*)&dnsHeader, sizeof(DNSHeader_t)); + + // rr #1, the PTR record from generic _services.x.local to service.x.local + _udp->write((uint8_t*)dns_rr_services, sizeof(dns_rr_services)); + + byte buf[10]; + buf[0] = 0x00; + buf[1] = 0x0c; //PTR + buf[2] = 0x00; + buf[3] = 0x01; //IN + *((uint32_t*)(buf+4)) = HTONL(120); //TTL in sec + *((uint16_t*)(buf+8)) = HTONS( _serviceProto[0] + 1 + strlen(dns_rr_tcplocal) + 1); + _udp->write(buf, 10); + + _udp->write(_serviceProto,_serviceProto[0]+1); + _udp->write(dns_rr_tcplocal, strlen(dns_rr_tcplocal)+1); + + // rr #2, the PTR record from proto.x to name.proto.x + _udp->write(_serviceProto,_serviceProto[0]+1); + _udp->write(dns_rr_tcplocal, strlen(dns_rr_tcplocal)+1); + *((uint16_t*)(buf+8)) = HTONS(strlen(_serviceName) + strlen(dns_rr_tcplocal) + 1); // recycle most of buf + _udp->write(buf, 10); + + _udp->write(_serviceName, strlen(_serviceName)); + _udp->write(dns_rr_tcplocal, strlen(dns_rr_tcplocal)+1); + // rr #3, the SRV record for the service that points to local name + _udp->write(_serviceName, strlen(_serviceName)); + _udp->write(dns_rr_tcplocal, strlen(dns_rr_tcplocal)+1); + + buf[1] = 0x21; // recycle most of buf but here SRV + buf[2] = 0x80; // cache flush + *((uint16_t*)(buf+8)) = HTONS(strlen(_name) + strlen(dns_rr_local) + 1 + 6); + _udp->write(buf, 10); + + byte srv[6]; + // priority and weight + srv[0] = srv[1] = srv[2] = srv[3] = 0; + // port + *((uint16_t*)(srv+4)) = HTONS(_servicePort); + _udp->write(srv, 6); + // target + _udp->write(_name, _name[0]+1); + _udp->write(dns_rr_local, strlen(dns_rr_local)+1); + + // rr #4, the A record for the name.local + _udp->write(_name, _name[0]+1); + _udp->write(dns_rr_local, strlen(dns_rr_local)+1); + + buf[1] = 0x01; // recycle most of buf but here A + *((uint16_t*)(buf+8)) = HTONS(4); + _udp->write(buf, 10); + byte ip[4]; + ip[0] = _ipAddress[0]; + ip[1] = _ipAddress[1]; + ip[2] = _ipAddress[2]; + ip[3] = _ipAddress[3]; + _udp->write(ip, 4); + + _udp->endPacket(); + _udp->flush(); + // +} +#endif //DO_MDNS diff --git a/EXmDNS.h b/EXmDNS.h new file mode 100644 index 0000000..0ec1e5e --- /dev/null +++ b/EXmDNS.h @@ -0,0 +1,50 @@ +/* + * © 2024 Harald Barth + * © 2024 Paul M. Antoine + * All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ +#ifdef DO_MDNS +#define BROADCASTTIME 15 //seconds + +// We do this ourselves because every library is different and/or broken... +#define HTONS(x) ((uint16_t)(((x) << 8) | (((x) >> 8) & 0xFF))) +#define HTONL(x) ( ((uint32_t)(x) << 24) | (((uint32_t)(x) << 8) & 0xFF0000) | \ + (((uint32_t)(x) >> 8) & 0xFF00) | ((uint32_t)(x) >> 24) ) + +typedef enum _MDNSServiceProtocol_t +{ + MDNSServiceTCP, + MDNSServiceUDP +} MDNSServiceProtocol_t; + +class MDNS { +public: + MDNS(EthernetUDP& udp); + ~MDNS(); + int begin(const IPAddress& ip, char* name); + int addServiceRecord(const char* name, uint16_t port, MDNSServiceProtocol_t proto); + void run(); +private: + EthernetUDP *_udp; + IPAddress _ipAddress; + char* _name; + char* _serviceName; + char* _serviceProto; + int _servicePort; +}; +#endif //DO_MDNS diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index f8306cb..266afd8 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -31,13 +31,12 @@ #include "CommandDistributor.h" #include "WiThrottle.h" #include "DCCTimer.h" -#if __has_include ( "MDNS_Generic.h") - #include "MDNS_Generic.h" - #define DO_MDNS - EthernetUDP udp; - MDNS mdns(udp); -#endif +#ifdef DO_MDNS +#include "EXmDNS.h" +EthernetUDP udp; +MDNS mdns(udp); +#endif //extern void looptimer(unsigned long timeout, const FSH* message); #define looptimer(a,b) @@ -116,10 +115,10 @@ void EthernetInterface::setup() outboundRing=new RingStream(OUTBOUND_RING_SIZE); #ifdef DO_MDNS - mdns.begin(Ethernet.localIP(), WIFI_HOSTNAME); // hostname + if (!mdns.begin(Ethernet.localIP(), (char *)WIFI_HOSTNAME)) + DIAG(F("mdns.begin fail")); // hostname mdns.addServiceRecord(WIFI_HOSTNAME "._withrottle", IP_PORT, MDNSServiceTCP); - // Not sure if we need to run it once, but just in case! - mdns.run(); + mdns.run(); // run it right away to get out info ASAP #endif connected=true; } @@ -144,7 +143,9 @@ void EthernetInterface::acceptClient() { // STM32 version return; } } - DIAG(F("Ethernet OVERFLOW")); + // reached here only if more than MAX_SOCK_NUM clients want to connect + DIAG(F("Ethernet more than %d clients, not accepting new connection"), MAX_SOCK_NUM); + client.stop(); } #else void EthernetInterface::acceptClient() { // non-STM32 version diff --git a/EthernetInterface.h b/EthernetInterface.h index 16156fa..e5458d9 100644 --- a/EthernetInterface.h +++ b/EthernetInterface.h @@ -3,7 +3,7 @@ * © 2021 Neil McKechnie * © 2021 Mike S * © 2021 Fred Decker - * © 2020-2022 Harald Barth + * © 2020-2024 Harald Barth * © 2020-2024 Chris Harlow * © 2020 Gregor Baues * All rights reserved. @@ -31,24 +31,32 @@ #define EthernetInterface_h #include "defines.h" +#if ETHERNET_ON == true #include "DCCEXParser.h" #include //#include #if defined (ARDUINO_TEENSY41) #include //TEENSY Ethernet Treiber #include + #ifndef MAX_SOCK_NUM #define MAX_SOCK_NUM 4 + #endif + // can't use our MDNS because of a namespace clash with Teensy's NativeEthernet library! + // #define DO_MDNS #elif defined (ARDUINO_NUCLEO_F429ZI) || defined (ARDUINO_NUCLEO_F439ZI) || defined (ARDUINO_NUCLEO_F4X9ZI) #include -// #include "STM32lwipopts.h" #include #include extern "C" struct netif gnetif; #define STM32_ETHERNET - #define MAX_SOCK_NUM 8 + #define MAX_SOCK_NUM MAX_NUM_TCP_CLIENTS + #define DO_MDNS #else #include "Ethernet.h" + #define DO_MDNS #endif + + #include "RingStream.h" /** @@ -77,5 +85,5 @@ class EthernetInterface { static void dropClient(byte socketnum); }; - +#endif // ETHERNET_ON #endif diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index b1c3d8f..e7b6ef2 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202501171827Z" +#define GITHUB_SHA "devel-more-ether-202503022012Z" diff --git a/STM32lwipopts.h.copyme b/STM32lwipopts.h.copyme new file mode 100644 index 0000000..eb9d624 --- /dev/null +++ b/STM32lwipopts.h.copyme @@ -0,0 +1,101 @@ +/* + * © 2024 Harald Barth + * All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ +// +// Rewrite of the STM32lwipopts.h file from STM +// To be copied into where lwipopts_default.h resides +// typically into STM32Ethernet/src/STM32lwipopts.h +// or STM32Ethernet\src\STM32lwipopts.h +// search for `lwipopts_default.h` and copy this file into the +// same directory but name it STM32lwipopts.h +// +#ifndef __STM32LWIPOPTS_H__ +#define __STM32LWIPOPTS_H__ + +// include this here and then override things we do differnet +#include "lwipopts_default.h" + +// we can not include our "defines.h" here +// so we need to duplicate that define +#define MAX_NUM_TCP_CLIENTS_HERE 9 + +#ifdef MAX_NUM_TCP_CLIENTS + #if MAX_NUM_TCP_CLIENTS != MAX_NUM_TCP_CLIENTS_HERE + #error MAX_NUM_TCP_CLIENTS and MAX_NUM_TCP_CLIENTS_HERE must be same + #endif +#else + #define MAX_NUM_TCP_CLIENTS MAX_NUM_TCP_CLIENTS_HERE +#endif + +// increase ARP cache +#undef MEMP_NUM_APR_QUEUE +#define MEMP_NUM_ARP_QUEUE MAX_NUM_TCP_CLIENTS+3 // one for each client (all on different HW) and a few extra + +// Example for debug +//#define LWIP_DEBUG 1 +//#define TCP_DEBUG LWIP_DBG_ON + +// NOT STRICT NECESSARY ANY MORE BUT CAN BE USED TO SAVE RAM +#undef MEM_LIBC_MALLOC +#define MEM_LIBC_MALLOC 1 // use the same malloc as for everything else +#undef MEMP_MEM_MALLOC +#define MEMP_MEM_MALLOC 1 // uses malloc which means no pools which means slower but not mean 32KB up front + +#undef MEMP_NUM_TCP_PCB +#define MEMP_NUM_TCP_PCB MAX_NUM_TCP_CLIENTS+1 // one extra so we can reject number N+1 from our code +#define MEMP_NUM_TCP_PCB_LISTEN 6 + +#undef MEMP_NUM_TCP_SEG +#define MEMP_NUM_TCP_SEG MAX_NUM_TCP_CLIENTS + +#undef MEMP_NUM_SYS_TIMEOUT +#define MEMP_NUM_SYS_TIMEOUT MAX_NUM_TCP_CLIENTS+2 + +#undef PBUF_POOL_SIZE +#define PBUF_POOL_SIZE MAX_NUM_TCP_CLIENTS + +#undef LWIO_ICMP +#define LWIP_ICMP 1 +#undef LWIP_RAW +#define LWIP_RAW 1 /* PING changed to 1 */ +#undef DEFAULT_RAW_RECVMBOX_SIZE +#define DEFAULT_RAW_RECVMBOX_SIZE 3 /* for ICMP PING */ + +#undef LWIP_DHCP +#define LWIP_DHCP 1 +#undef LWIP_UDP +#define LWIP_UDP 1 + +/* +The STM32F4x7 allows computing and verifying the IP, UDP, TCP and ICMP checksums by hardware: + - To use this feature let the following define uncommented. + - To disable it and process by CPU comment the the checksum. +*/ + +#if CHECKSUM_GEN_TCP == 1 +#error On STM32 TCP checksum should be in HW +#endif + +#undef LWIP_IGMP +#define LWIP_IGMP 1 + +//#define SO_REUSE 1 +//#define SO_REUSE_RXTOALL 1 + +#endif /* __STM32LWIPOPTS_H__ */ diff --git a/config.example.h b/config.example.h index 6aba226..c0cc971 100644 --- a/config.example.h +++ b/config.example.h @@ -137,6 +137,16 @@ The configuration file for DCC-EX Command Station // //#define ENABLE_ETHERNET true +///////////////////////////////////////////////////////////////////////////////////// +// +// MAX_NUM_TCP_CLIENTS: If you on STM32 Ethernet (and only there) want more than +// 9 (*) TCP clients, change this number to for example 20 here **AND** in +// STM32lwiopts.h and follow the instructions in STM32lwiopts.h +// +// (*) It would be 10 if there would not be a bug in LwIP by STM32duino. +// +//#define MAX_NUM_TCP_CLIENTS 20 + ///////////////////////////////////////////////////////////////////////////////////// // diff --git a/defines.h b/defines.h index 2c3ee55..561ed64 100644 --- a/defines.h +++ b/defines.h @@ -239,4 +239,25 @@ #endif #endif +#if defined(ARDUINO_ARCH_STM32) +// The LwIP library for the STM32 wired ethernet has by default 10 TCP +// clients defined but because of a bug in the library #11 is not +// rejected but kicks out any old connection. By restricting our limit +// to 9 the #10 will be rejected by our code so that the number can +// never get to 11 which would kick an existing connection. +// If you want to change this value, do that in +// config.h AND in STM32lwipopts.h. + #ifndef MAX_NUM_TCP_CLIENTS + #define MAX_NUM_TCP_CLIENTS 9 + #endif +#else + #if defined(ARDUINO_ARCH_ESP32) +// Espressif LWIP stack + #define MAX_NUM_TCP_CLIENTS 10 + #else +// Wifi shields etc + #define MAX_NUM_TCP_CLIENTS 8 + #endif #endif + +#endif //DEFINES_H diff --git a/platformio.ini b/platformio.ini index 0e4e6c5..d1634ce 100644 --- a/platformio.ini +++ b/platformio.ini @@ -96,7 +96,6 @@ lib_deps = ${env.lib_deps} arduino-libraries/Ethernet SPI - MDNS_Generic lib_ignore = WiFi101 WiFi101_Generic @@ -115,7 +114,6 @@ framework = arduino lib_deps = ${env.lib_deps} arduino-libraries/Ethernet - MDNS_Generic SPI lib_ignore = WiFi101 WiFi101_Generic @@ -270,7 +268,6 @@ framework = arduino lib_deps = ${env.lib_deps} stm32duino/STM32Ethernet @ ^1.4.0 stm32duino/STM32duino LwIP @ ^2.1.3 - MDNS_Generic lib_ignore = WiFi101 WiFi101_Generic WiFiEspAT @@ -294,7 +291,6 @@ framework = arduino lib_deps = ${env.lib_deps} stm32duino/STM32Ethernet @ ^1.4.0 stm32duino/STM32duino LwIP @ ^2.1.3 - MDNS_Generic lib_ignore = WiFi101 WiFi101_Generic WiFiEspAT