Skip to content

Commit 605d5b5

Browse files
committed
Merge pull request #4102
21bf3d2 Add tests for BoostAsioToCNetAddr (Wladimir J. van der Laan) fdbd707 Remove unused function WildcardMatch (Wladimir J. van der Laan) ee21912 rpc: Use netmasks instead of wildcards for IP address matching (Wladimir J. van der Laan) e16be73 net: Add CSubNet class for subnet matching (Wladimir J. van der Laan) d864275 Use new function parseint32 in SplitHostPort (Wladimir J. van der Laan) 0d4ea1c util: add parseint32 function with strict error reporting (Wladimir J. van der Laan)
2 parents 8bcfccb + 21bf3d2 commit 605d5b5

File tree

9 files changed

+301
-79
lines changed

9 files changed

+301
-79
lines changed

src/netbase.cpp

Lines changed: 123 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,10 @@ void SplitHostPort(std::string in, int &portOut, std::string &hostOut) {
4747
bool fBracketed = fHaveColon && (in[0]=='[' && in[colon-1]==']'); // if there is a colon, and in[0]=='[', colon is not 0, so in[colon-1] is safe
4848
bool fMultiColon = fHaveColon && (in.find_last_of(':',colon-1) != in.npos);
4949
if (fHaveColon && (colon==0 || fBracketed || !fMultiColon)) {
50-
char *endp = NULL;
51-
int n = strtol(in.c_str() + colon + 1, &endp, 10);
52-
if (endp && *endp == 0 && n >= 0) {
50+
int32_t n;
51+
if (ParseInt32(in.substr(colon + 1), &n) && n > 0 && n < 0x10000) {
5352
in = in.substr(0, colon);
54-
if (n > 0 && n < 0x10000)
55-
portOut = n;
53+
portOut = n;
5654
}
5755
}
5856
if (in.size()>0 && in[0] == '[' && in[in.size()-1] == ']')
@@ -548,6 +546,22 @@ void CNetAddr::SetIP(const CNetAddr& ipIn)
548546
memcpy(ip, ipIn.ip, sizeof(ip));
549547
}
550548

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+
551565
static const unsigned char pchOnionCat[] = {0xFD,0x87,0xD8,0x7E,0xEB,0x43};
552566

553567
bool CNetAddr::SetSpecial(const std::string &strName)
@@ -571,13 +585,12 @@ CNetAddr::CNetAddr()
571585

572586
CNetAddr::CNetAddr(const struct in_addr& ipv4Addr)
573587
{
574-
memcpy(ip, pchIPv4, 12);
575-
memcpy(ip+12, &ipv4Addr, 4);
588+
SetRaw(NET_IPV4, (const uint8_t*)&ipv4Addr);
576589
}
577590

578591
CNetAddr::CNetAddr(const struct in6_addr& ipv6Addr)
579592
{
580-
memcpy(ip, &ipv6Addr, 16);
593+
SetRaw(NET_IPV6, (const uint8_t*)&ipv6Addr);
581594
}
582595

583596
CNetAddr::CNetAddr(const char *pszIp, bool fAllowLookup)
@@ -1122,3 +1135,105 @@ void CService::SetPort(unsigned short portIn)
11221135
{
11231136
port = portIn;
11241137
}
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/rpcserver.cpp

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ static map<string, boost::shared_ptr<deadline_timer> > deadlineTimers;
3838
static ssl::context* rpc_ssl_context = NULL;
3939
static boost::thread_group* rpc_worker_group = NULL;
4040
static boost::asio::io_service::work *rpc_dummy_work = NULL;
41+
static std::vector<CSubNet> rpc_allow_subnets; //!< List of subnets to allow RPC connections from
4142

4243
void RPCTypeCheck(const Array& params,
4344
const list<Value_type>& typesExpected,
@@ -358,25 +359,33 @@ void ErrorReply(std::ostream& stream, const Object& objError, const Value& id)
358359
stream << HTTPReply(nStatus, strReply, false) << std::flush;
359360
}
360361

361-
bool ClientAllowed(const boost::asio::ip::address& address)
362+
CNetAddr BoostAsioToCNetAddr(boost::asio::ip::address address)
362363
{
364+
CNetAddr netaddr;
363365
// Make sure that IPv4-compatible and IPv4-mapped IPv6 addresses are treated as IPv4 addresses
364366
if (address.is_v6()
365367
&& (address.to_v6().is_v4_compatible()
366368
|| address.to_v6().is_v4_mapped()))
367-
return ClientAllowed(address.to_v6().to_v4());
368-
369-
if (address == asio::ip::address_v4::loopback()
370-
|| address == asio::ip::address_v6::loopback()
371-
|| (address.is_v4()
372-
// Check whether IPv4 addresses match 127.0.0.0/8 (loopback subnet)
373-
&& (address.to_v4().to_ulong() & 0xff000000) == 0x7f000000))
374-
return true;
375-
376-
const string strAddress = address.to_string();
377-
const vector<string>& vAllow = mapMultiArgs["-rpcallowip"];
378-
BOOST_FOREACH(string strAllow, vAllow)
379-
if (WildcardMatch(strAddress, strAllow))
369+
address = address.to_v6().to_v4();
370+
371+
if(address.is_v4())
372+
{
373+
boost::asio::ip::address_v4::bytes_type bytes = address.to_v4().to_bytes();
374+
netaddr.SetRaw(NET_IPV4, &bytes[0]);
375+
}
376+
else
377+
{
378+
boost::asio::ip::address_v6::bytes_type bytes = address.to_v6().to_bytes();
379+
netaddr.SetRaw(NET_IPV6, &bytes[0]);
380+
}
381+
return netaddr;
382+
}
383+
384+
bool ClientAllowed(const boost::asio::ip::address& address)
385+
{
386+
CNetAddr netaddr = BoostAsioToCNetAddr(address);
387+
BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets)
388+
if (subnet.Match(netaddr))
380389
return true;
381390
return false;
382391
}
@@ -502,6 +511,31 @@ static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol,
502511

503512
void StartRPCThreads()
504513
{
514+
rpc_allow_subnets.clear();
515+
rpc_allow_subnets.push_back(CSubNet("127.0.0.0/8")); // always allow IPv4 local subnet
516+
rpc_allow_subnets.push_back(CSubNet("::1")); // always allow IPv6 localhost
517+
if (mapMultiArgs.count("-rpcallowip"))
518+
{
519+
const vector<string>& vAllow = mapMultiArgs["-rpcallowip"];
520+
BOOST_FOREACH(string strAllow, vAllow)
521+
{
522+
CSubNet subnet(strAllow);
523+
if(!subnet.IsValid())
524+
{
525+
uiInterface.ThreadSafeMessageBox(
526+
strprintf("Invalid -rpcallowip subnet specification: %s", strAllow),
527+
"", CClientUIInterface::MSG_ERROR);
528+
StartShutdown();
529+
return;
530+
}
531+
rpc_allow_subnets.push_back(subnet);
532+
}
533+
}
534+
std::string strAllowed;
535+
BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets)
536+
strAllowed += subnet.ToString() + " ";
537+
LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed);
538+
505539
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
506540
if (((mapArgs["-rpcpassword"] == "") ||
507541
(mapArgs["-rpcuser"] == mapArgs["-rpcpassword"])) && Params().RequireRPCPassword())

src/rpcserver.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "json/json_spirit_writer_template.h"
2020

2121
class CBlockIndex;
22+
class CNetAddr;
2223

2324
/* Start RPC threads */
2425
void StartRPCThreads();
@@ -50,6 +51,9 @@ void RPCTypeCheck(const json_spirit::Object& o,
5051
*/
5152
void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds);
5253

54+
//! Convert boost::asio address to CNetAddr
55+
extern CNetAddr BoostAsioToCNetAddr(boost::asio::ip::address address);
56+
5357
typedef json_spirit::Value(*rpcfn_type)(const json_spirit::Array& params, bool fHelp);
5458

5559
class CRPCCommand

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()

src/test/rpc_tests.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "rpcclient.h"
77

88
#include "base58.h"
9+
#include "netbase.h"
910

1011
#include <boost/algorithm/string.hpp>
1112
#include <boost/test/unit_test.hpp>
@@ -138,4 +139,19 @@ BOOST_AUTO_TEST_CASE(rpc_parse_monetary_values)
138139
BOOST_CHECK(AmountFromValue(ValueFromString("20999999.99999999")) == 2099999999999999LL);
139140
}
140141

142+
BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr)
143+
{
144+
// Check IPv4 addresses
145+
BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("1.2.3.4")).ToString(), "1.2.3.4");
146+
BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("127.0.0.1")).ToString(), "127.0.0.1");
147+
// Check IPv6 addresses
148+
BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::1")).ToString(), "::1");
149+
BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("123:4567:89ab:cdef:123:4567:89ab:cdef")).ToString(),
150+
"123:4567:89ab:cdef:123:4567:89ab:cdef");
151+
// v4 compatible must be interpreted as IPv4
152+
BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::0:127.0.0.1")).ToString(), "127.0.0.1");
153+
// v4 mapped must be interpreted as IPv4
154+
BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::ffff:127.0.0.1")).ToString(), "127.0.0.1");
155+
}
156+
141157
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)