net: don't accept non-left-contiguous netmasks

A netmask that contains 1-bits after 0-bits (the 1-bits are not
contiguous on the left side) is invalid [1] [2].

The code before this PR used to parse and accept such
non-left-contiguous netmasks. However, a coming change that will alter
`CNetAddr::ip` to have flexible size would make juggling with such
netmasks more difficult, thus drop support for those.

[1] https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#Subnet_masks
[2] https://tools.ietf.org/html/rfc4632#section-5.1
This commit is contained in:
Vasil Dimov committed 2020-08-24 21:50:59 +02:00
1 parent cb1ee1551c
commit 1ea57ad674
3 files changed
+48 -54

No files matched your search

+4
View File
@@ -135,6 +135,10 @@ Updated settings
in future releases. Refer to the help of the affected settings `-whitebind` in future releases. Refer to the help of the affected settings `-whitebind`
and `-whitelist` for more details. (#19191) and `-whitelist` for more details. (#19191)
- Netmasks that contain 1-bits after 0-bits (the 1-bits are not contiguous on
the left side, e.g. 255.0.255.255) are no longer accepted. They are invalid
according to RFC 4632.
Changes to Wallet or GUI related settings can be found in the GUI or Wallet section below. Changes to Wallet or GUI related settings can be found in the GUI or Wallet section below.
Tools and Utilities Tools and Utilities
+39 -51
View File
@@ -789,9 +789,41 @@ CSubNet::CSubNet(const CNetAddr &addr, int32_t mask)
network.ip[x] &= netmask[x]; network.ip[x] &= netmask[x];
} }
/**
* @returns The number of 1-bits in the prefix of the specified subnet mask. If
* the specified subnet mask is not a valid one, -1.
*/
static inline int NetmaskBits(uint8_t x)
{
switch(x) {
case 0x00: return 0;
case 0x80: return 1;
case 0xc0: return 2;
case 0xe0: return 3;
case 0xf0: return 4;
case 0xf8: return 5;
case 0xfc: return 6;
case 0xfe: return 7;
case 0xff: return 8;
default: return -1;
}
}
CSubNet::CSubNet(const CNetAddr &addr, const CNetAddr &mask) CSubNet::CSubNet(const CNetAddr &addr, const CNetAddr &mask)
{ {
valid = true; valid = true;
// Check if `mask` contains 1-bits after 0-bits (which is an invalid netmask).
bool zeros_found = false;
for (size_t i = mask.IsIPv4() ? 12 : 0; i < sizeof(mask.ip); ++i) {
const int num_bits = NetmaskBits(mask.ip[i]);
if (num_bits == -1 || (zeros_found && num_bits != 0)) {
valid = false;
return;
}
if (num_bits < 8) {
zeros_found = true;
}
}
network = addr; network = addr;
// Default to /32 (IPv4) or /128 (IPv6), i.e. match single address // Default to /32 (IPv4) or /128 (IPv6), i.e. match single address
memset(netmask, 255, sizeof(netmask)); memset(netmask, 255, sizeof(netmask));
@@ -828,62 +860,18 @@ bool CSubNet::Match(const CNetAddr &addr) const
return true; return true;
} }
/**
* @returns The number of 1-bits in the prefix of the specified subnet mask. If
* the specified subnet mask is not a valid one, -1.
*/
static inline int NetmaskBits(uint8_t x)
{
switch(x) {
case 0x00: return 0;
case 0x80: return 1;
case 0xc0: return 2;
case 0xe0: return 3;
case 0xf0: return 4;
case 0xf8: return 5;
case 0xfc: return 6;
case 0xfe: return 7;
case 0xff: return 8;
default: return -1;
}
}
std::string CSubNet::ToString() const std::string CSubNet::ToString() const
{ {
/* Parse binary 1{n}0{N-n} to see if mask can be represented as /n */ uint8_t cidr = 0;
int cidr = 0;
bool valid_cidr = true;
int n = network.IsIPv4() ? 12 : 0;
for (; n < 16 && netmask[n] == 0xff; ++n)
cidr += 8;
if (n < 16) {
int bits = NetmaskBits(netmask[n]);
if (bits < 0)
valid_cidr = false;
else
cidr += bits;
++n;
}
for (; n < 16 && valid_cidr; ++n)
if (netmask[n] != 0x00)
valid_cidr = false;
/* Format output */ for (size_t i = network.IsIPv4() ? 12 : 0; i < sizeof(netmask); ++i) {
std::string strNetmask; if (netmask[i] == 0x00) {
if (valid_cidr) { break;
strNetmask = strprintf("%u", cidr); }
} else { cidr += NetmaskBits(netmask[i]);
if (network.IsIPv4())
strNetmask = strprintf("%u.%u.%u.%u", netmask[12], netmask[13], netmask[14], netmask[15]);
else
strNetmask = strprintf("%x:%x:%x:%x:%x:%x:%x:%x",
netmask[0] << 8 | netmask[1], netmask[2] << 8 | netmask[3],
netmask[4] << 8 | netmask[5], netmask[6] << 8 | netmask[7],
netmask[8] << 8 | netmask[9], netmask[10] << 8 | netmask[11],
netmask[12] << 8 | netmask[13], netmask[14] << 8 | netmask[15]);
} }
return network.ToString() + "/" + strNetmask; return network.ToString() + strprintf("/%u", cidr);
} }
bool CSubNet::IsValid() const bool CSubNet::IsValid() const
+5 -3
View File
@@ -290,11 +290,13 @@ BOOST_AUTO_TEST_CASE(subnet_test)
BOOST_CHECK_EQUAL(subnet.ToString(), "1::/16"); BOOST_CHECK_EQUAL(subnet.ToString(), "1::/16");
subnet = ResolveSubNet("1:2:3:4:5:6:7:8/0000:0000:0000:0000:0000:0000:0000:0000"); subnet = ResolveSubNet("1:2:3:4:5:6:7:8/0000:0000:0000:0000:0000:0000:0000:0000");
BOOST_CHECK_EQUAL(subnet.ToString(), "::/0"); BOOST_CHECK_EQUAL(subnet.ToString(), "::/0");
// Invalid netmasks (with 1-bits after 0-bits)
subnet = ResolveSubNet("1.2.3.4/255.255.232.0"); subnet = ResolveSubNet("1.2.3.4/255.255.232.0");
BOOST_CHECK_EQUAL(subnet.ToString(), "1.2.0.0/255.255.232.0"); BOOST_CHECK(!subnet.IsValid());
subnet = ResolveSubNet("1.2.3.4/255.0.255.255");
BOOST_CHECK(!subnet.IsValid());
subnet = ResolveSubNet("1:2:3:4:5:6:7:8/ffff:ffff:ffff:fffe:ffff:ffff:ffff:ff0f"); subnet = ResolveSubNet("1:2:3:4:5:6:7:8/ffff:ffff:ffff:fffe:ffff:ffff:ffff:ff0f");
BOOST_CHECK_EQUAL(subnet.ToString(), "1:2:3:4:5:6:7:8/ffff:ffff:ffff:fffe:ffff:ffff:ffff:ff0f"); BOOST_CHECK(!subnet.IsValid());
} }
BOOST_AUTO_TEST_CASE(netbase_getgroup) BOOST_AUTO_TEST_CASE(netbase_getgroup)