From 58d80b8557a849b7d3331fb3318d521a0c6cbcab Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Fri, 20 Dec 2024 16:21:27 -0800 Subject: [PATCH] Use IPAddress.fromString for parsing private IPs (#5621) --- src/mqtt/MQTT.cpp | 103 +++++++++++++++------------------------------- src/mqtt/MQTT.h | 4 -- 2 files changed, 33 insertions(+), 74 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index e405786801..ac4e9e786f 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -26,6 +26,14 @@ #include #include +#include +#if defined(ARCH_PORTDUINO) +#include +#elif !defined(ntohl) +#include +#define ntohl __ntohl +#endif + MQTT *mqtt; namespace @@ -196,6 +204,29 @@ inline void onReceiveJson(byte *payload, size_t length) LOG_DEBUG("JSON ignore downlink message with unsupported type"); } } + +/// Determines if the given IPAddress is a private IPv4 address, i.e. not routable on the public internet. +bool isPrivateIpAddress(const IPAddress &ip) +{ + constexpr struct { + uint32_t network; + uint32_t mask; + } privateCidrRanges[] = { + {.network = 192u << 24 | 168 << 16, .mask = 0xffff0000}, // 192.168.0.0/16 + {.network = 172u << 24 | 16 << 16, .mask = 0xfff00000}, // 172.16.0.0/12 + {.network = 169u << 24 | 254 << 16, .mask = 0xffff0000}, // 169.254.0.0/16 + {.network = 10u << 24, .mask = 0xff000000}, // 10.0.0.0/8 + {.network = 127u << 24 | 1, .mask = 0xffffffff}, // 127.0.0.1/32 + }; + const uint32_t addr = ntohl(ip); + for (const auto &cidrRange : privateCidrRanges) { + if (cidrRange.network == (addr & cidrRange.mask)) { + LOG_INFO("MQTT server on a private IP"); + return true; + } + } + return false; +} } // namespace void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length) @@ -270,10 +301,8 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs); } - isMqttServerAddressPrivate = isPrivateIpAddress(moduleConfig.mqtt.address); - if (isMqttServerAddressPrivate) { - LOG_INFO("MQTT server on a private IP"); - } + IPAddress ip; + isMqttServerAddressPrivate = ip.fromString(moduleConfig.mqtt.address) && isPrivateIpAddress(ip); #if HAS_NETWORKING if (!moduleConfig.mqtt.proxy_to_client_enabled) @@ -717,69 +746,3 @@ void MQTT::perhapsReportToMap() // Update the last report time last_report_to_map = millis(); } - -bool MQTT::isPrivateIpAddress(const char address[]) -{ - // Min. length like 10.0.0.0 (8), max like 192.168.255.255:65535 (21) - size_t length = strlen(address); - if (length < 8 || length > 21) { - return false; - } - - // Ensure the address contains only digits and dots and maybe a colon. - // Some limited validation is done. - // Even if it's not a valid IP address, we will know it's not a domain. - bool hasColon = false; - int numDots = 0; - for (size_t i = 0; i < length; i++) { - if (!isdigit(address[i]) && address[i] != '.' && address[i] != ':') { - return false; - } - - // Dots can't be the first character, immediately follow another dot, - // occur more than 3 times, or occur after a colon. - if (address[i] == '.') { - if (++numDots > 3 || i == 0 || address[i - 1] == '.' || hasColon) { - return false; - } - } - // There can only be a single colon, and it can only occur after 3 dots - else if (address[i] == ':') { - if (hasColon || numDots < 3) { - return false; - } - - hasColon = true; - } - } - - // Final validation for IPv4 address and port format. - // Note that the values of octets haven't been tested, only the address format. - if (numDots != 3) { - return false; - } - - // Check the easy ones first. - if (strcmp(address, "127.0.0.1") == 0 || strncmp(address, "10.", 3) == 0 || strncmp(address, "192.168", 7) == 0 || - strncmp(address, "169.254", 7) == 0) { - return true; - } - - // See if it's definitely not a 172 address. - if (strncmp(address, "172", 3) != 0) { - return false; - } - - // We know it's a 172 address, now see if the second octet is 2 digits. - if (address[6] != '.') { - return false; - } - - // Copy the second octet into a secondary buffer we can null-terminate and parse. - char octet2[3]; - strncpy(octet2, address + 4, 2); - octet2[2] = 0; - - int octet2Num = atoi(octet2); - return octet2Num >= 16 && octet2Num <= 31; -} diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index 9db54ea4be..11621c55f3 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -121,10 +121,6 @@ class MQTT : private concurrency::OSThread // Check if we should report unencrypted information about our node for consumption by a map void perhapsReportToMap(); - /// Determines if the given address is a private IPv4 address, i.e. not routable on the public internet. - /// These are the ranges: 127.0.0.1, 10.0.0.0-10.255.255.255, 172.16.0.0-172.31.255.255, 192.168.0.0-192.168.255.255. - bool isPrivateIpAddress(const char address[]); - /// Return 0 if sleep is okay, veto sleep if we are connected to pubsub server // int preflightSleepCb(void *unused = NULL) { return pubSub.connected() ? 1 : 0; } };