[AccessControl] Fix checking for allowed IP access of webinterface

This commit is contained in:
TD-er
2026-06-07 17:06:06 +02:00
parent b95cda55ad
commit 9bfc82bb70
15 changed files with 160 additions and 98 deletions
+6 -14
View File
@@ -77,24 +77,16 @@ IPAddress NetworkLocalIP() { return WiFi.localIP(); }
IPAddress NetworkID()
{
const IPAddress subnet = NetworkSubnetMask();
IPAddress networkID = ESPEasy::net::NetworkLocalIP();
for (uint8_t i = 0; i < 4; ++i) {
networkID[i] &= subnet[i];
}
return networkID;
return NWPlugin::getNetworkID(
ESPEasy::net::NetworkLocalIP(),
NetworkSubnetMask());
}
IPAddress NetworkBroadcast()
{
const IPAddress subnet = NetworkSubnetMask();
IPAddress broadcast = ESPEasy::net::NetworkLocalIP();
for (uint8_t i = 0; i < 4; ++i) {
broadcast[i] |= ~subnet[i];
}
return broadcast;
return NWPlugin::getNetworkBroadcast(
ESPEasy::net::NetworkLocalIP(),
NetworkSubnetMask());
}
IPAddress NetworkSubnetMask() { return WiFi.subnetMask(); }
+25 -8
View File
@@ -472,21 +472,38 @@ bool NWPluginCall(NWPlugin::Function Function, EventStruct *event, String& str)
IPAddress client_ip;
client_ip.fromString(str);
success = true;
if (SecuritySettings.IPblockLevel == LOCAL_SUBNET_ALLOWED) {
success = NWPlugin::IP_in_subnet(client_ip, event->networkInterface);
const NWPlugin::IP_type ip_type = NWPlugin::get_IP_type(client_ip);
IPAddress networkID;
IPAddress broadcast;
if (NWPlugin::get_subnet(ip_type, event->networkInterface, networkID, broadcast)) {
# if FEATURE_USE_IPV6
const bool includeZone = ip_type == NWPlugin::IP_type::ipv6_link_local;
event->String1 = formatIP(networkID, includeZone);
event->String2 = formatIP(broadcast, includeZone);
# else // if FEATURE_USE_IPV6
event->String1 = formatIP(networkID);
event->String2 = formatIP(broadcast);
# endif // if FEATURE_USE_IPV6
}
} else if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) {
const IPAddress low(SecuritySettings.AllowedIPrangeLow);
const IPAddress high(SecuritySettings.AllowedIPrangeHigh);
if (IPAddressSet(low) && IPAddressSet(high))
if (IPAddressSet(low) || IPAddressSet(high))
{
success =
NWPlugin::ipInRange(client_ip, low, high) &&
NWPlugin::IP_in_subnet(low, event->networkInterface) &&
NWPlugin::IP_in_subnet(high, event->networkInterface);
} else { success = true; }
} else {
success = true;
// Use || as for example 0.0.0.0 ... 123.0.0.0 might also be a possible range.
// Though I have no idea for what practical use case...
success = NWPlugin::ipInRange(client_ip, low, high);
event->String1 = formatIP(low);
event->String2 = formatIP(high);
}
}
}
break;
+22 -34
View File
@@ -30,43 +30,31 @@ bool ipInAllowedSubnet(const IPAddress& ip)
}
String describeAllowedIPrange() {
String reply;
switch (SecuritySettings.IPblockLevel)
{
case ALL_ALLOWED:
reply += F("All Allowed");
break;
default:
{
IPAddress low, high;
getIPallowedRange(low, high);
reply += formatIP(low);
reply += F(" - ");
reply += formatIP(high);
}
}
return reply;
return describeAllowedIPrange(ESPEasy::net::NetworkLocalIP());
}
bool getIPallowedRange(IPAddress& low, IPAddress& high)
{
switch (SecuritySettings.IPblockLevel)
{
case LOCAL_SUBNET_ALLOWED:
low = NetworkID();
high = NetworkBroadcast();
return true;
case ONLY_IP_RANGE_ALLOWED:
low = IPAddress(SecuritySettings.AllowedIPrangeLow);
high = IPAddress(SecuritySettings.AllowedIPrangeHigh);
break;
default:
low = IPAddress(0, 0, 0, 0);
high = IPAddress(255, 255, 255, 255);
return false;
String describeAllowedIPrange(const IPAddress& ip) {
if (SecuritySettings.IPblockLevel == ALL_ALLOWED) {
return F("All Allowed");
}
return true;
String allowedRange;
String ip_str = ip.toString();
for (networkIndex_t x = 0; x < NETWORK_MAX; x++) {
EventStruct tempEvent;
tempEvent.NetworkIndex = x;
if (NWPluginCall(NWPlugin::Function::NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED, &tempEvent, ip_str)) {
if (!tempEvent.String1.isEmpty() && !tempEvent.String2.isEmpty()) {
if (!allowedRange.isEmpty()) { allowedRange += F(", "); }
allowedRange += tempEvent.String1;
allowedRange += F(" - ");
allowedRange += tempEvent.String2;
}
}
}
return allowedRange;
}
} // namespace net
+1 -2
View File
@@ -15,8 +15,7 @@ namespace net {
bool ipInAllowedSubnet(const IPAddress& ip);
String describeAllowedIPrange();
bool getIPallowedRange(IPAddress& low, IPAddress& high);
String describeAllowedIPrange(const IPAddress& ip);
} // namespace net
+18 -14
View File
@@ -297,21 +297,25 @@ bool NWPlugin_001(NWPlugin::Function function, EventStruct *event, String& strin
case NWPlugin::Function::NWPLUGIN_CLIENT_IP_WEB_ACCESS_ALLOWED:
{
IPAddress client_ip;
client_ip.fromString(string);
if ((SecuritySettings.IPblockLevel == LOCAL_SUBNET_ALLOWED) &&
!Settings.getNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex)) {
success = NWPlugin::ipInRange(client_ip, NetworkID(), NetworkBroadcast());
} else if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) {
const IPAddress low(SecuritySettings.AllowedIPrangeLow);
const IPAddress high(SecuritySettings.AllowedIPrangeHigh);
success =
!IPAddressSet(low) ||
!IPAddressSet(high) ||
NWPlugin::ipInRange(client_ip, low, high);
} else {
if (!Settings.getNetworkInterfaceSubnetBlockClientIP(event->NetworkIndex)) {
IPAddress client_ip;
client_ip.fromString(string);
success = true;
if (SecuritySettings.IPblockLevel != ALL_ALLOWED) {
IPAddress low, high;
if (SecuritySettings.IPblockLevel == LOCAL_SUBNET_ALLOWED) {
low = NetworkID();
high = NetworkBroadcast();
} else if (SecuritySettings.IPblockLevel == ONLY_IP_RANGE_ALLOWED) {
low = IPAddress(SecuritySettings.AllowedIPrangeLow);
high = IPAddress(SecuritySettings.AllowedIPrangeHigh);
}
if (IPAddressSet(low) || IPAddressSet(high)) {
success = NWPlugin::ipInRange(client_ip, low, high);
event->String1 = formatIP(low);
event->String2 = formatIP(high);
}
}
}
break;
}
+1 -8
View File
@@ -230,14 +230,7 @@ bool NWPlugin_002(NWPlugin::Function function, EventStruct *event, String& strin
// FIXME TD-er: Do we allow to set the subnetmask for AP to anything else?
const IPAddress subnet(255, 255, 255, 0);
const IPAddress localIP = WiFi.softAPIP();
bool success = true;
for (uint8_t i = 0; success && i < 4; ++i) {
if ((localIP[i] & subnet[i]) != (client_ip[i] & subnet[i])) {
success = false;
}
}
success = NWPlugin::IP_in_subnet(WiFi.softAPIP(), client_ip, subnet);
}
break;
}
+71 -11
View File
@@ -185,21 +185,22 @@ bool NWPlugin::get_subnet(NWPlugin::IP_type ip_type, NetworkInterface*networkInt
broadcast = networkInterface->broadcastIP();
return true;
}
#endif
#endif // ifdef ESP32
bool NWPlugin::ipLessEqual(const IPAddress& ip, const IPAddress& high)
{
// FIXME TD-er: Must check whether both are of same type and check full range IPv6
int nrOctets = 4;
# if FEATURE_USE_IPV6
#if FEATURE_USE_IPV6
if (ip.type() != high.type()) { return false; }
if (ip.type() == IPv6) {
nrOctets = 16;
}
# endif // if FEATURE_USE_IPV6
#endif // if FEATURE_USE_IPV6
for (int i = 0; i < nrOctets; ++i) {
if (ip[i] != high[i]) {
@@ -211,11 +212,69 @@ bool NWPlugin::ipLessEqual(const IPAddress& ip, const IPAddress& high)
return true;
}
bool NWPlugin::ipInRange(const IPAddress& ip, const IPAddress& low, const IPAddress& high) {
return ipLessEqual(low, ip) && ipLessEqual(ip, high);
bool NWPlugin::ipInRange(const IPAddress& ip, const IPAddress& low, const IPAddress& high) {
return ipLessEqual(low, ip) && ipLessEqual(ip, high);
}
bool NWPlugin::IP_in_subnet(const IPAddress& localIP,
const IPAddress& client_ip,
const IPAddress& subnet)
{
#if FEATURE_USE_IPV6
if ((localIP.type() != client_ip.type()) ||
(localIP.type() != subnet.type())) { return false; }
#endif // if FEATURE_USE_IPV6
return ipInRange(
client_ip,
getNetworkID(localIP, subnet),
getNetworkBroadcast(localIP, subnet));
}
IPAddress NWPlugin::getNetworkID(const IPAddress& localIP, const IPAddress& subnet)
{
IPAddress networkID = localIP;
int nrOctets = 4;
#if FEATURE_USE_IPV6
if (localIP.type() != subnet.type()) { return IPAddress(); }
if (localIP.type() == IPv6) {
nrOctets = 16;
}
#endif // if FEATURE_USE_IPV6
for (uint8_t i = 0; i < nrOctets; ++i) {
networkID[i] &= subnet[i];
}
return networkID;
}
IPAddress NWPlugin::getNetworkBroadcast(const IPAddress& localIP, const IPAddress& subnet)
{
IPAddress broadcast = localIP;
int nrOctets = 4;
#if FEATURE_USE_IPV6
if (localIP.type() != subnet.type()) { return IPAddress(); }
if (localIP.type() == IPv6) {
nrOctets = 16;
}
#endif // if FEATURE_USE_IPV6
for (uint8_t i = 0; i < nrOctets; ++i) {
broadcast[i] |= ~subnet[i];
}
return broadcast;
}
#ifdef ESP32
bool NWPlugin::IP_in_subnet(const IPAddress & ip,
NetworkInterface *networkInterface)
{
@@ -223,17 +282,18 @@ bool NWPlugin::IP_in_subnet(const IPAddress & ip,
IPAddress networkID;
IPAddress broadcast;
if (!get_subnet(ip_type, networkInterface, networkID, broadcast)) {
return false;
if (!get_subnet(ip_type, networkInterface, networkID, broadcast)) {
return false;
}
#if FEATURE_USE_IPV6
# if FEATURE_USE_IPV6
if (ip_type == NWPlugin::IP_type::ipv6_link_local) {
// Must match zone, or else it will always match.
return (ip.zone() == networkID.zone());
if (ip.zone() != networkID.zone()) return false;
}
#endif
# endif // if FEATURE_USE_IPV6
return ipLessEqual(networkID, ip) && ipLessEqual(ip, broadcast);
return ipInRange(ip, networkID, broadcast);
}
NWPlugin::IP_type NWPlugin::get_IP_type(const IPAddress& ip)
@@ -255,6 +255,14 @@ public:
const IPAddress& low,
const IPAddress& high);
static bool IP_in_subnet(const IPAddress& localIP,
const IPAddress& client_ip,
const IPAddress& subnet);
static IPAddress getNetworkID(const IPAddress& localIP, const IPAddress& subnet);
static IPAddress getNetworkBroadcast(const IPAddress& localIP, const IPAddress& subnet);
#ifdef ESP32
static bool IP_in_subnet(const IPAddress & ip,
NetworkInterface *networkInterface);
+1 -1
View File
@@ -212,7 +212,7 @@ int SettingsType::getFileSize(Enum settingsType) {
return max_file_pos;
}
#ifndef BUILD_NO_DEBUG
#if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG)
unsigned int SettingsType::getSVGcolor(Enum settingsType) {
switch (settingsType) {
case Enum::BasicSettings_Type:
+1 -1
View File
@@ -54,7 +54,7 @@ public:
static int getMaxFilePos(Enum settingsType);
static int getFileSize(Enum settingsType);
#ifndef BUILD_NO_DEBUG
#if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG)
static unsigned int getSVGcolor(Enum settingsType);
#endif
+2 -1
View File
@@ -615,7 +615,7 @@ void logMemUsageAfter(const __FlashStringHelper *function, int value) {
// The recorded used memory is not an exact value, as background (or interrupt) tasks may also allocate or free heap memory.
static int last_freemem = ESP.getFreeHeap();
const int freemem_end = ESP.getFreeHeap();
#ifndef BUILD_NO_DEBUG
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
String log;
@@ -639,6 +639,7 @@ void logMemUsageAfter(const __FlashStringHelper *function, int value) {
addLogMove(LOG_LEVEL_DEBUG, log);
}
}
#endif
last_freemem = freemem_end;
}
+1 -1
View File
@@ -46,7 +46,7 @@ bool clientIPallowed()
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
response += F(" Allowed: ");
response += ESPEasy::net::describeAllowedIPrange();
response += ESPEasy::net::describeAllowedIPrange(remoteIP);
addLogMove(LOG_LEVEL_ERROR, response);
}
return false;
+1 -1
View File
@@ -1091,7 +1091,7 @@ void getWiFi_RSSI_icon(int rssi, int width_pixels)
addHtml(F("</svg>\n"));
}
#if FEATURE_CHART_STORAGE_LAYOUT
#if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG)
void getConfig_dat_file_layout() {
const int shiftY = 2;
float yOffset = shiftY;
+1 -1
View File
@@ -193,7 +193,7 @@ void write_SVG_image_header(int width,
void getWiFi_RSSI_icon(int rssi,
int width_pixels);
#if FEATURE_CHART_STORAGE_LAYOUT
#if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG)
void getConfig_dat_file_layout();
void getStorageTableSVG(SettingsType::Enum settingsType);
+1 -1
View File
@@ -668,7 +668,7 @@ void handle_sysinfo_Storage() {
}
# endif // ifndef LIMIT_BUILD_SIZE
# if FEATURE_CHART_STORAGE_LAYOUT
#if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG)
if (showSettingsFileLayout) {
addTableSeparator(F("Settings Files"), 2, 3);