Skip to content

Commit e16be73

Browse files
committed
net: Add CSubNet class for subnet matching
1 parent d864275 commit e16be73

File tree

3 files changed

+187
-3
lines changed

3 files changed

+187
-3
lines changed

src/netbase.cpp

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,22 @@ void CNetAddr::SetIP(const CNetAddr& ipIn)
546546
memcpy(ip, ipIn.ip, sizeof(ip));
547547
}
548548

549+
void CNetAddr::SetRaw(Network network, const uint8_t *ip_in)
550+
{
551+
switch(network)
552+
{
553+
case NET_IPV4:
554+
memcpy(ip, pchIPv4, 12);
555+
memcpy(ip+12, ip_in, 4);
556+
break;
557+
case NET_IPV6:
558+
memcpy(ip, ip_in, 16);
559+
break;
560+
default:
561+
assert(!"invalid network");
562+
}
563+
}
564+
549565
static const unsigned char pchOnionCat[] = {0xFD,0x87,0xD8,0x7E,0xEB,0x43};
550566

551567
bool CNetAddr::SetSpecial(const std::string &strName)
@@ -569,13 +585,12 @@ CNetAddr::CNetAddr()
569585

570586
CNetAddr::CNetAddr(const struct in_addr& ipv4Addr)
571587
{
572-
memcpy(ip, pchIPv4, 12);
573-
memcpy(ip+12, &ipv4Addr, 4);
588+
SetRaw(NET_IPV4, (const uint8_t*)&ipv4Addr);
574589
}
575590

576591
CNetAddr::CNetAddr(const struct in6_addr& ipv6Addr)
577592
{
578-
memcpy(ip, &ipv6Addr, 16);
593+
SetRaw(NET_IPV6, (const uint8_t*)&ipv6Addr);
579594
}
580595

581596
CNetAddr::CNetAddr(const char *pszIp, bool fAllowLookup)
@@ -1120,3 +1135,105 @@ void CService::SetPort(unsigned short portIn)
11201135
{
11211136
port = portIn;
11221137
}
1138+
1139+
CSubNet::CSubNet():
1140+
valid(false)
1141+
{
1142+
memset(netmask, 0, sizeof(netmask));
1143+
}
1144+
1145+
CSubNet::CSubNet(const std::string &strSubnet, bool fAllowLookup)
1146+
{
1147+
size_t slash = strSubnet.find_last_of('/');
1148+
std::vector<CNetAddr> vIP;
1149+
1150+
valid = true;
1151+
// Default to /32 (IPv4) or /128 (IPv6), i.e. match single address
1152+
memset(netmask, 255, sizeof(netmask));
1153+
1154+
std::string strAddress = strSubnet.substr(0, slash);
1155+
if (LookupHost(strAddress.c_str(), vIP, 1, fAllowLookup))
1156+
{
1157+
network = vIP[0];
1158+
if (slash != strSubnet.npos)
1159+
{
1160+
std::string strNetmask = strSubnet.substr(slash + 1);
1161+
int32_t n;
1162+
// IPv4 addresses start at offset 12, and first 12 bytes must match, so just offset n
1163+
int noffset = network.IsIPv4() ? (12 * 8) : 0;
1164+
if (ParseInt32(strNetmask, &n)) // If valid number, assume /24 symtex
1165+
{
1166+
if(n >= 0 && n <= (128 - noffset)) // Only valid if in range of bits of address
1167+
{
1168+
n += noffset;
1169+
// Clear bits [n..127]
1170+
for (; n < 128; ++n)
1171+
netmask[n>>3] &= ~(1<<(n&7));
1172+
}
1173+
else
1174+
{
1175+
valid = false;
1176+
}
1177+
}
1178+
else // If not a valid number, try full netmask syntax
1179+
{
1180+
if (LookupHost(strNetmask.c_str(), vIP, 1, false)) // Never allow lookup for netmask
1181+
{
1182+
// Remember: GetByte returns bytes in reversed order
1183+
// Copy only the *last* four bytes in case of IPv4, the rest of the mask should stay 1's as
1184+
// we don't want pchIPv4 to be part of the mask.
1185+
int asize = network.IsIPv4() ? 4 : 16;
1186+
for(int x=0; x<asize; ++x)
1187+
netmask[15-x] = vIP[0].GetByte(x);
1188+
}
1189+
else
1190+
{
1191+
valid = false;
1192+
}
1193+
}
1194+
}
1195+
}
1196+
else
1197+
{
1198+
valid = false;
1199+
}
1200+
}
1201+
1202+
bool CSubNet::Match(const CNetAddr &addr) const
1203+
{
1204+
if (!valid || !addr.IsValid())
1205+
return false;
1206+
for(int x=0; x<16; ++x)
1207+
if ((addr.GetByte(x) & netmask[15-x]) != network.GetByte(x))
1208+
return false;
1209+
return true;
1210+
}
1211+
1212+
std::string CSubNet::ToString() const
1213+
{
1214+
std::string strNetmask;
1215+
if (network.IsIPv4())
1216+
strNetmask = strprintf("%u.%u.%u.%u", netmask[12], netmask[13], netmask[14], netmask[15]);
1217+
else
1218+
strNetmask = strprintf("%x:%x:%x:%x:%x:%x:%x:%x",
1219+
netmask[0] << 8 | netmask[1], netmask[2] << 8 | netmask[3],
1220+
netmask[4] << 8 | netmask[5], netmask[6] << 8 | netmask[7],
1221+
netmask[8] << 8 | netmask[9], netmask[10] << 8 | netmask[11],
1222+
netmask[12] << 8 | netmask[13], netmask[14] << 8 | netmask[15]);
1223+
return network.ToString() + "/" + strNetmask;
1224+
}
1225+
1226+
bool CSubNet::IsValid() const
1227+
{
1228+
return valid;
1229+
}
1230+
1231+
bool operator==(const CSubNet& a, const CSubNet& b)
1232+
{
1233+
return a.valid == b.valid && a.network == b.network && !memcmp(a.netmask, b.netmask, 16);
1234+
}
1235+
1236+
bool operator!=(const CSubNet& a, const CSubNet& b)
1237+
{
1238+
return !(a==b);
1239+
}

src/netbase.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ class CNetAddr
4949
explicit CNetAddr(const std::string &strIp, bool fAllowLookup = false);
5050
void Init();
5151
void SetIP(const CNetAddr& ip);
52+
53+
/**
54+
* Set raw IPv4 or IPv6 address (in network byte order)
55+
* @note Only NET_IPV4 and NET_IPV6 are allowed for network.
56+
*/
57+
void SetRaw(Network network, const uint8_t *data);
58+
5259
bool SetSpecial(const std::string &strName); // for Tor addresses
5360
bool IsIPv4() const; // IPv4 mapped address (::FFFF:0:0/96, 0.0.0.0/0)
5461
bool IsIPv6() const; // IPv6 address (not mapped IPv4, not Tor)
@@ -90,6 +97,29 @@ class CNetAddr
9097
)
9198
};
9299

100+
class CSubNet
101+
{
102+
protected:
103+
/// Network (base) address
104+
CNetAddr network;
105+
/// Netmask, in network byte order
106+
uint8_t netmask[16];
107+
/// Is this value valid? (only used to signal parse errors)
108+
bool valid;
109+
110+
public:
111+
CSubNet();
112+
explicit CSubNet(const std::string &strSubnet, bool fAllowLookup = false);
113+
114+
bool Match(const CNetAddr &addr) const;
115+
116+
std::string ToString() const;
117+
bool IsValid() const;
118+
119+
friend bool operator==(const CSubNet& a, const CSubNet& b);
120+
friend bool operator!=(const CSubNet& a, const CSubNet& b);
121+
};
122+
93123
/** A combination of a network address (CNetAddr) and a (TCP) port */
94124
class CService : public CNetAddr
95125
{

src/test/netbase_tests.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,41 @@ BOOST_AUTO_TEST_CASE(onioncat_test)
102102
BOOST_CHECK(addr1.IsRoutable());
103103
}
104104

105+
BOOST_AUTO_TEST_CASE(subnet_test)
106+
{
107+
BOOST_CHECK(CSubNet("1.2.3.0/24") == CSubNet("1.2.3.0/255.255.255.0"));
108+
BOOST_CHECK(CSubNet("1.2.3.0/24") != CSubNet("1.2.4.0/255.255.255.0"));
109+
BOOST_CHECK(CSubNet("1.2.3.0/24").Match(CNetAddr("1.2.3.4")));
110+
BOOST_CHECK(!CSubNet("1.2.2.0/24").Match(CNetAddr("1.2.3.4")));
111+
BOOST_CHECK(CSubNet("1.2.3.4").Match(CNetAddr("1.2.3.4")));
112+
BOOST_CHECK(CSubNet("1.2.3.4/32").Match(CNetAddr("1.2.3.4")));
113+
BOOST_CHECK(!CSubNet("1.2.3.4").Match(CNetAddr("5.6.7.8")));
114+
BOOST_CHECK(!CSubNet("1.2.3.4/32").Match(CNetAddr("5.6.7.8")));
115+
BOOST_CHECK(CSubNet("::ffff:127.0.0.1").Match(CNetAddr("127.0.0.1")));
116+
BOOST_CHECK(CSubNet("1:2:3:4:5:6:7:8").Match(CNetAddr("1:2:3:4:5:6:7:8")));
117+
BOOST_CHECK(!CSubNet("1:2:3:4:5:6:7:8").Match(CNetAddr("1:2:3:4:5:6:7:9")));
118+
BOOST_CHECK(CSubNet("1:2:3:4:5:6:7:0/112").Match(CNetAddr("1:2:3:4:5:6:7:1234")));
119+
// All-Matching IPv6 Matches arbitrary IPv4 and IPv6
120+
BOOST_CHECK(CSubNet("::/0").Match(CNetAddr("1:2:3:4:5:6:7:1234")));
121+
BOOST_CHECK(CSubNet("::/0").Match(CNetAddr("1.2.3.4")));
122+
// All-Matching IPv4 does not Match IPv6
123+
BOOST_CHECK(!CSubNet("0.0.0.0/0").Match(CNetAddr("1:2:3:4:5:6:7:1234")));
124+
// Invalid subnets Match nothing (not even invalid addresses)
125+
BOOST_CHECK(!CSubNet().Match(CNetAddr("1.2.3.4")));
126+
BOOST_CHECK(!CSubNet("").Match(CNetAddr("4.5.6.7")));
127+
BOOST_CHECK(!CSubNet("bloop").Match(CNetAddr("0.0.0.0")));
128+
BOOST_CHECK(!CSubNet("bloop").Match(CNetAddr("hab")));
129+
// Check valid/invalid
130+
BOOST_CHECK(CSubNet("1.2.3.0/0").IsValid());
131+
BOOST_CHECK(!CSubNet("1.2.3.0/-1").IsValid());
132+
BOOST_CHECK(CSubNet("1.2.3.0/32").IsValid());
133+
BOOST_CHECK(!CSubNet("1.2.3.0/33").IsValid());
134+
BOOST_CHECK(CSubNet("1:2:3:4:5:6:7:8/0").IsValid());
135+
BOOST_CHECK(CSubNet("1:2:3:4:5:6:7:8/33").IsValid());
136+
BOOST_CHECK(!CSubNet("1:2:3:4:5:6:7:8/-1").IsValid());
137+
BOOST_CHECK(CSubNet("1:2:3:4:5:6:7:8/128").IsValid());
138+
BOOST_CHECK(!CSubNet("1:2:3:4:5:6:7:8/129").IsValid());
139+
BOOST_CHECK(!CSubNet("fuzzy").IsValid());
140+
}
141+
105142
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)