Skip to content

Commit

Permalink
Use IPAddress.fromString for parsing private IPs (meshtastic#5621)
Browse files Browse the repository at this point in the history
  • Loading branch information
esev authored Dec 21, 2024
1 parent 960626e commit 58d80b8
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 74 deletions.
103 changes: 33 additions & 70 deletions src/mqtt/MQTT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
#include <pb_decode.h>
#include <utility>

#include <IPAddress.h>
#if defined(ARCH_PORTDUINO)
#include <netinet/in.h>
#elif !defined(ntohl)
#include <machine/endian.h>
#define ntohl __ntohl
#endif

MQTT *mqtt;

namespace
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}
4 changes: 0 additions & 4 deletions src/mqtt/MQTT.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
};
Expand Down

0 comments on commit 58d80b8

Please sign in to comment.