From f41afb14b15aed9f565df93f341a5bff3120f11b Mon Sep 17 00:00:00 2001 From: Jorropo Date: Thu, 20 Mar 2025 11:41:29 +0100 Subject: [PATCH 1/4] raise the multicast UDP TTL limit (#6343) Since 96ba94843b25f787148e3353a7476ed175518b3b we don't spray packets to all machines on the network. So we can allow ourself to raise the TTL limit, this allows users who run L3 IGMP Routing infrastructure to pass meshtastic frames over UDP. Co-authored-by: Ben Meadors --- src/mesh/udp/UdpMulticastThread.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/udp/UdpMulticastThread.h b/src/mesh/udp/UdpMulticastThread.h index e5eb28d00..ab1c7bc93 100644 --- a/src/mesh/udp/UdpMulticastThread.h +++ b/src/mesh/udp/UdpMulticastThread.h @@ -22,7 +22,7 @@ class UdpMulticastThread : public concurrency::OSThread void start() { - if (udp.listenMulticast(udpIpAddress, UDP_MULTICAST_DEFAUL_PORT)) { + if (udp.listenMulticast(udpIpAddress, UDP_MULTICAST_DEFAUL_PORT, 64)) { LOG_DEBUG("UDP Listening on IP: %s", WiFi.localIP().toString().c_str()); udp.onPacket([this](AsyncUDPPacket packet) { onReceive(packet); }); } else { From d1068fd1e451146d2982b17ee27838a60fb26b5b Mon Sep 17 00:00:00 2001 From: Jorropo Date: Thu, 20 Mar 2025 14:47:39 +0100 Subject: [PATCH 2/4] Add UDP multicast support on linux. (#6342) * Add UDP multicast support on linux. Closes #6326 We tested it an it works. This is really hacky to say the least. * Add libuv to Linux packaging * Trunkadunk * Correct ref * Add libuv1-dev to setup-native --------- Co-authored-by: vidplace7 Co-authored-by: Ben Meadors --- .devcontainer/Dockerfile | 1 + .github/actions/setup-native/action.yml | 2 +- Dockerfile | 4 ++-- alpine.Dockerfile | 4 ++-- arch/portduino/portduino.ini | 4 +++- debian/control | 1 + meshtasticd.spec.rpkg | 1 + src/main.cpp | 5 +++++ src/mesh/udp/UdpMulticastThread.h | 15 ++++++++++++++- 9 files changed, 30 insertions(+), 7 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4b9f069ab..30af24bd2 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -29,6 +29,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ gpg \ gnupg2 \ libusb-1.0-0-dev \ + libuv1-dev \ libi2c-dev \ libxcb-xkb-dev \ libxkbcommon-dev \ diff --git a/.github/actions/setup-native/action.yml b/.github/actions/setup-native/action.yml index 36c95d943..05f95cd40 100644 --- a/.github/actions/setup-native/action.yml +++ b/.github/actions/setup-native/action.yml @@ -11,4 +11,4 @@ runs: - name: Install libs needed for native build shell: bash run: | - sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev + sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev diff --git a/Dockerfile b/Dockerfile index fd1bb6164..733a46325 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ ENV TZ=Etc/UTC ENV PIP_ROOT_USER_ACTION=ignore RUN apt-get update && apt-get install --no-install-recommends -y \ wget g++ zip git ca-certificates \ - libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev \ + libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \ libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev pkg-config \ && apt-get clean && rm -rf /var/lib/apt/lists/* \ && pip install --no-cache-dir -U platformio \ @@ -38,7 +38,7 @@ ENV TZ=Etc/UTC USER root RUN apt-get update && apt-get --no-install-recommends -y install \ - libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libulfius2.7 libusb-1.0-0-dev liborcania2.3 libssl3 \ + libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libuv1 libusb-1.0-0-dev liborcania2.3 libulfius2.7 libssl3 \ && apt-get clean && rm -rf /var/lib/apt/lists/* \ && mkdir -p /var/lib/meshtasticd \ && mkdir -p /etc/meshtasticd/config.d \ diff --git a/alpine.Dockerfile b/alpine.Dockerfile index b6d91a75a..17afc2964 100644 --- a/alpine.Dockerfile +++ b/alpine.Dockerfile @@ -9,7 +9,7 @@ FROM python:3.13-alpine3.21 AS builder ENV PIP_ROOT_USER_ACTION=ignore RUN apk --no-cache add \ bash g++ libstdc++-dev linux-headers zip git ca-certificates libgpiod-dev yaml-cpp-dev bluez-dev \ - libusb-dev i2c-tools-dev openssl-dev pkgconf argp-standalone \ + libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \ && rm -rf /var/cache/apk/* \ && pip install --no-cache-dir -U platformio \ && mkdir /tmp/firmware @@ -32,7 +32,7 @@ FROM alpine:3.21 USER root RUN apk --no-cache add \ - libstdc++ libgpiod yaml-cpp libusb i2c-tools \ + libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \ && rm -rf /var/cache/apk/* \ && mkdir -p /var/lib/meshtasticd \ && mkdir -p /etc/meshtasticd/config.d \ diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 7ea6a77a2..734a4f91e 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -1,6 +1,6 @@ ; The Portduino based 'native' environment. Currently supported on Linux targets with real LoRa hardware (or simulated). [portduino_base] -platform = https://github.com/meshtastic/platform-native.git#562d189828f09fbf4c4093b3c0104bae9d8e9ff9 +platform = https://github.com/meshtastic/platform-native.git#df71ed0040e9aad767a002829330965b78fc452a framework = arduino build_src_filter = @@ -34,10 +34,12 @@ build_flags = -Isrc/platform/portduino -DRADIOLIB_EEPROM_UNSUPPORTED -DPORTDUINO_LINUX_HARDWARE + -DHAS_UDP_MULTICAST -lpthread -lstdc++fs -lbluetooth -lgpiod -lyaml-cpp -li2c + -luv -std=c++17 diff --git a/debian/control b/debian/control index b3a8eb58e..693cd6aa5 100644 --- a/debian/control +++ b/debian/control @@ -17,6 +17,7 @@ Build-Depends: debhelper-compat (= 13), libbluetooth-dev, libusb-1.0-0-dev, libi2c-dev, + libuv1-dev, openssl, libssl-dev, libulfius-dev, diff --git a/meshtasticd.spec.rpkg b/meshtasticd.spec.rpkg index 0a0f03557..a09261056 100644 --- a/meshtasticd.spec.rpkg +++ b/meshtasticd.spec.rpkg @@ -36,6 +36,7 @@ BuildRequires: pkgconfig(libgpiod) BuildRequires: pkgconfig(bluez) BuildRequires: pkgconfig(libusb-1.0) BuildRequires: libi2c-devel +BuildRequires: pkgconfig(libuv) # Web components: BuildRequires: pkgconfig(openssl) BuildRequires: pkgconfig(liborcania) diff --git a/src/main.cpp b/src/main.cpp index 797d911d1..e9e0c9d4b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -822,6 +822,11 @@ void setup() #ifdef HAS_UDP_MULTICAST LOG_DEBUG("Start multicast thread"); udpThread = new UdpMulticastThread(); +#ifdef ARCH_PORTDUINO + // FIXME: portduino does not ever call onNetworkConnected so call it here because I don't know what happen if I call + // onNetworkConnected there + udpThread->start(); +#endif #endif service = new MeshService(); service->init(); diff --git a/src/mesh/udp/UdpMulticastThread.h b/src/mesh/udp/UdpMulticastThread.h index ab1c7bc93..e2c3be369 100644 --- a/src/mesh/udp/UdpMulticastThread.h +++ b/src/mesh/udp/UdpMulticastThread.h @@ -23,7 +23,12 @@ class UdpMulticastThread : public concurrency::OSThread void start() { if (udp.listenMulticast(udpIpAddress, UDP_MULTICAST_DEFAUL_PORT, 64)) { +#if !defined(ARCH_PORTDUINO) + // FIXME(PORTDUINO): arduino lacks IPAddress::toString() LOG_DEBUG("UDP Listening on IP: %s", WiFi.localIP().toString().c_str()); +#else + LOG_DEBUG("UDP Listening"); +#endif udp.onPacket([this](AsyncUDPPacket packet) { onReceive(packet); }); } else { LOG_DEBUG("Failed to listen on UDP"); @@ -33,7 +38,10 @@ class UdpMulticastThread : public concurrency::OSThread void onReceive(AsyncUDPPacket packet) { size_t packetLength = packet.length(); +#ifndef ARCH_PORTDUINO + // FIXME(PORTDUINO): arduino lacks IPAddress::toString() LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength); +#endif meshtastic_MeshPacket mp; LOG_DEBUG("Decoding MeshPacket from UDP len=%u", packetLength); bool isPacketDecoded = pb_decode_from_bytes(packet.data(), packetLength, &meshtastic_MeshPacket_msg, &mp); @@ -48,9 +56,14 @@ class UdpMulticastThread : public concurrency::OSThread bool onSend(const meshtastic_MeshPacket *mp) { - if (!mp || WiFi.status() != WL_CONNECTED) { + if (!mp || !udp) { return false; } +#if !defined(ARCH_PORTDUINO) + if (WiFi.status() != WL_CONNECTED) { + return false; + } +#endif LOG_DEBUG("Broadcasting packet over UDP (id=%u)", mp->id); uint8_t buffer[meshtastic_MeshPacket_size]; size_t encodedLength = pb_encode_to_bytes(buffer, sizeof(buffer), &meshtastic_MeshPacket_msg, mp); From 46235f6f8beb07b88639a3026525435c9877ee55 Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 20 Mar 2025 09:49:28 -0400 Subject: [PATCH 3/4] RP2xx0: Add UDP Multicast support (#6327) --- src/mesh/udp/UdpMulticastThread.h | 2 +- variants/rpipico2w/platformio.ini | 3 ++- variants/rpipicow/platformio.ini | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/mesh/udp/UdpMulticastThread.h b/src/mesh/udp/UdpMulticastThread.h index e2c3be369..7067cced9 100644 --- a/src/mesh/udp/UdpMulticastThread.h +++ b/src/mesh/udp/UdpMulticastThread.h @@ -83,4 +83,4 @@ class UdpMulticastThread : public concurrency::OSThread IPAddress udpIpAddress; AsyncUDP udp; }; -#endif // ARCH_ESP32 \ No newline at end of file +#endif // HAS_UDP_MULTICAST \ No newline at end of file diff --git a/variants/rpipico2w/platformio.ini b/variants/rpipico2w/platformio.ini index 351774221..282be1a42 100644 --- a/variants/rpipico2w/platformio.ini +++ b/variants/rpipico2w/platformio.ini @@ -23,8 +23,9 @@ build_flags = ${rp2350_base.build_flags} -DHAS_WIFI=1 -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m33" -fexceptions # for exception handling in MQTT + -DHAS_UDP_MULTICAST=1 build_src_filter = ${rp2350_base.build_src_filter} + lib_deps = ${rp2350_base.lib_deps} ${networking_base.lib_deps} -debug_build_flags = ${rp2350_base.build_flags}, -g \ No newline at end of file +debug_build_flags = ${rp2350_base.build_flags}, -g diff --git a/variants/rpipicow/platformio.ini b/variants/rpipicow/platformio.ini index 7a43ece3b..4b714434a 100644 --- a/variants/rpipicow/platformio.ini +++ b/variants/rpipicow/platformio.ini @@ -10,9 +10,10 @@ build_flags = ${rp2040_base.build_flags} -DHW_SPI1_DEVICE -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m0plus" -fexceptions # for exception handling in MQTT + -DHAS_UDP_MULTICAST=1 build_src_filter = ${rp2040_base.build_src_filter} + lib_deps = ${rp2040_base.lib_deps} ${networking_base.lib_deps} debug_build_flags = ${rp2040_base.build_flags}, -g -debug_tool = cmsis-dap ; for e.g. Picotool \ No newline at end of file +debug_tool = cmsis-dap ; for e.g. Picotool From 0d95b1afcc56a5c027e6fc5a8465ac580d52f722 Mon Sep 17 00:00:00 2001 From: raulperdomo <36527079+raulperdomo@users.noreply.github.com> Date: Thu, 20 Mar 2025 11:40:13 -0400 Subject: [PATCH 4/4] Added bounds checking to memcpy and use memory-safe strlcpy (#6351) * Added bounds checking to memcpy and use memory-safe strlcpy for reading serial data in processWXSerial() function. * Fixed linting with trunk --- src/modules/SerialModule.cpp | 134 ++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 811d1ec91..34ece2312 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -468,81 +468,83 @@ void SerialModule::processWXSerial() // Extract the current line char line[meshtastic_Constants_DATA_PAYLOAD_LEN]; memset(line, '\0', sizeof(line)); - memcpy(line, &serialBytes[lineStart], lineEnd - lineStart); - if (strstr(line, "Wind") != NULL) // we have a wind line - { - gotwind = true; - // Find the positions of "=" signs in the line - char *windDirPos = strstr(line, "WindDir = "); - char *windSpeedPos = strstr(line, "WindSpeed = "); - char *windGustPos = strstr(line, "WindGust = "); + if (lineEnd - lineStart < sizeof(line) - 1) { + memcpy(line, &serialBytes[lineStart], lineEnd - lineStart); + if (strstr(line, "Wind") != NULL) // we have a wind line + { + gotwind = true; + // Find the positions of "=" signs in the line + char *windDirPos = strstr(line, "WindDir = "); + char *windSpeedPos = strstr(line, "WindSpeed = "); + char *windGustPos = strstr(line, "WindGust = "); - if (windDirPos != NULL) { - // Extract data after "=" for WindDir - strcpy(windDir, windDirPos + 15); // Add 15 to skip "WindDir = " - double radians = GeoCoord::toRadians(strtof(windDir, nullptr)); - dir_sum_sin += sin(radians); - dir_sum_cos += cos(radians); - dirCount++; - } else if (windSpeedPos != NULL) { - // Extract data after "=" for WindSpeed - strcpy(windVel, windSpeedPos + 15); // Add 15 to skip "WindSpeed = " - float newv = strtof(windVel, nullptr); - velSum += newv; - velCount++; - if (newv < lull || lull == -1) - lull = newv; + if (windDirPos != NULL) { + // Extract data after "=" for WindDir + strlcpy(windDir, windDirPos + 15, sizeof(windDir)); // Add 15 to skip "WindDir = " + double radians = GeoCoord::toRadians(strtof(windDir, nullptr)); + dir_sum_sin += sin(radians); + dir_sum_cos += cos(radians); + dirCount++; + } else if (windSpeedPos != NULL) { + // Extract data after "=" for WindSpeed + strlcpy(windVel, windSpeedPos + 15, sizeof(windVel)); // Add 15 to skip "WindSpeed = " + float newv = strtof(windVel, nullptr); + velSum += newv; + velCount++; + if (newv < lull || lull == -1) + lull = newv; - } else if (windGustPos != NULL) { - strcpy(windGust, windGustPos + 15); // Add 15 to skip "WindSpeed = " - float newg = strtof(windGust, nullptr); - if (newg > gust) - gust = newg; - } + } else if (windGustPos != NULL) { + strlcpy(windGust, windGustPos + 15, sizeof(windGust)); // Add 15 to skip "WindSpeed = " + float newg = strtof(windGust, nullptr); + if (newg > gust) + gust = newg; + } - // these are also voltage data we care about possibly - } else if (strstr(line, "BatVoltage") != NULL) { // we have a battVoltage line - char *batVoltagePos = strstr(line, "BatVoltage = "); - if (batVoltagePos != NULL) { - strcpy(batVoltage, batVoltagePos + 17); // 18 for ws 80, 17 for ws85 - batVoltageF = strtof(batVoltage, nullptr); - break; // last possible data we want so break - } - } else if (strstr(line, "CapVoltage") != NULL) { // we have a cappVoltage line - char *capVoltagePos = strstr(line, "CapVoltage = "); - if (capVoltagePos != NULL) { - strcpy(capVoltage, capVoltagePos + 17); // 18 for ws 80, 17 for ws85 - capVoltageF = strtof(capVoltage, nullptr); - } - // GXTS04Temp = 24.4 - } else if (strstr(line, "GXTS04Temp") != NULL) { // we have a temperature line - char *tempPos = strstr(line, "GXTS04Temp = "); - if (tempPos != NULL) { - strcpy(temperature, tempPos + 15); // 15 spaces for ws85 - temperatureF = strtof(temperature, nullptr); - } + // these are also voltage data we care about possibly + } else if (strstr(line, "BatVoltage") != NULL) { // we have a battVoltage line + char *batVoltagePos = strstr(line, "BatVoltage = "); + if (batVoltagePos != NULL) { + strlcpy(batVoltage, batVoltagePos + 17, sizeof(batVoltage)); // 18 for ws 80, 17 for ws85 + batVoltageF = strtof(batVoltage, nullptr); + break; // last possible data we want so break + } + } else if (strstr(line, "CapVoltage") != NULL) { // we have a cappVoltage line + char *capVoltagePos = strstr(line, "CapVoltage = "); + if (capVoltagePos != NULL) { + strlcpy(capVoltage, capVoltagePos + 17, sizeof(capVoltage)); // 18 for ws 80, 17 for ws85 + capVoltageF = strtof(capVoltage, nullptr); + } + // GXTS04Temp = 24.4 + } else if (strstr(line, "GXTS04Temp") != NULL) { // we have a temperature line + char *tempPos = strstr(line, "GXTS04Temp = "); + if (tempPos != NULL) { + strlcpy(temperature, tempPos + 15, sizeof(temperature)); // 15 spaces for ws85 + temperatureF = strtof(temperature, nullptr); + } - } else if (strstr(line, "RainIntSum") != NULL) { // we have a rainsum line - // LOG_INFO(line); - char *pos = strstr(line, "RainIntSum = "); - if (pos != NULL) { - strcpy(rainStr, pos + 17); // 17 spaces for ws85 - rainSum = int(strtof(rainStr, nullptr)); - } - - } else if (strstr(line, "Rain") != NULL) { // we have a rain line - if (strstr(line, "WaveRain") == NULL) { // skip WaveRain lines though. + } else if (strstr(line, "RainIntSum") != NULL) { // we have a rainsum line // LOG_INFO(line); - char *pos = strstr(line, "Rain = "); + char *pos = strstr(line, "RainIntSum = "); if (pos != NULL) { - strcpy(rainStr, pos + 17); // 17 spaces for ws85 - rain = strtof(rainStr, nullptr); + strlcpy(rainStr, pos + 17, sizeof(rainStr)); // 17 spaces for ws85 + rainSum = int(strtof(rainStr, nullptr)); + } + + } else if (strstr(line, "Rain") != NULL) { // we have a rain line + if (strstr(line, "WaveRain") == NULL) { // skip WaveRain lines though. + // LOG_INFO(line); + char *pos = strstr(line, "Rain = "); + if (pos != NULL) { + strlcpy(rainStr, pos + 17, sizeof(rainStr)); // 17 spaces for ws85 + rain = strtof(rainStr, nullptr); + } } } - } - // Update lineStart for the next line - lineStart = lineEnd + 1; + // Update lineStart for the next line + lineStart = lineEnd + 1; + } } } break;