Skip to content

Commit e2f1a8f

Browse files
committed
evo: introduce purpose-based port restrictions
Additionally, ports defined as defaults are exempt from bad port restrictions for that purpose. In other purposes, they are still considered bad ports.
1 parent 3459c64 commit e2f1a8f

File tree

3 files changed

+56
-19
lines changed

3 files changed

+56
-19
lines changed

src/evo/netinfo.cpp

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@ bool MatchSuffix(const std::string& str, const T1& list)
4444
}
4545
return false;
4646
}
47+
48+
uint16_t GetMainnetPurposePort(const uint8_t purpose)
49+
{
50+
assert(IsValidPurpose(purpose));
51+
switch (purpose) {
52+
case Purpose::CORE_P2P:
53+
return MainParams().GetDefaultPort();
54+
case Purpose::PLATFORM_P2P:
55+
return MainParams().GetDefaultPlatformP2PPort();
56+
case Purpose::PLATFORM_HTTPS:
57+
return MainParams().GetDefaultPlatformHTTPPort();
58+
} // no default case, so the compiler can warn about missing cases
59+
assert(false);
60+
}
4761
} // anonymous namespace
4862

4963
bool NetInfoEntry::operator==(const NetInfoEntry& rhs) const
@@ -213,9 +227,8 @@ NetInfoStatus MnNetInfo::ValidateService(const CService& service)
213227
return NetInfoStatus::NotRoutable;
214228
}
215229

216-
if (IsNodeOnMainnet() != (service.GetPort() == MainParams().GetDefaultPort())) {
217-
// Must use mainnet port on mainnet.
218-
// Must NOT use mainnet port on other networks.
230+
if (IsNodeOnMainnet() != (service.GetPort() == GetMainnetPurposePort(Purpose::CORE_P2P))) {
231+
// Must use mainnet port on mainnet and any other port for other networks
219232
return NetInfoStatus::BadPort;
220233
}
221234

@@ -359,7 +372,7 @@ NetInfoStatus ExtNetInfo::ProcessCandidate(const uint8_t purpose, const NetInfoE
359372
}
360373
}
361374

362-
NetInfoStatus ExtNetInfo::ValidateService(const CService& service, bool is_primary)
375+
NetInfoStatus ExtNetInfo::ValidateService(const CService& service, const uint8_t purpose, bool is_primary)
363376
{
364377
if (!service.IsValid()) {
365378
return NetInfoStatus::BadAddress;
@@ -370,16 +383,27 @@ NetInfoStatus ExtNetInfo::ValidateService(const CService& service, bool is_prima
370383
if (Params().RequireRoutableExternalIP() && !service.IsRoutable()) {
371384
return NetInfoStatus::NotRoutable;
372385
}
373-
if (service.IsI2P() && service.GetPort() != I2P_SAM31_PORT) {
386+
387+
const uint16_t service_port{service.GetPort()};
388+
if (service.IsI2P() && service_port != I2P_SAM31_PORT) {
374389
// I2P SAM 3.1 and earlier don't support arbitrary ports
375390
return NetInfoStatus::BadPort;
376-
} else if (!service.IsI2P() && (IsBadPort(service.GetPort()) || service.GetPort() == 0)) {
391+
} else if (!service.IsI2P() &&
392+
((IsBadPort(service_port) && service_port != GetMainnetPurposePort(purpose)) || service_port == 0)) {
377393
return NetInfoStatus::BadPort;
378394
}
395+
379396
if (is_primary) {
380397
if (!service.IsIPv4()) {
381398
return NetInfoStatus::BadType;
382399
}
400+
if (IsNodeOnMainnet() && service_port != GetMainnetPurposePort(purpose)) {
401+
// On mainnet, the primary address must use the fixed port assigned to the purpose
402+
return NetInfoStatus::BadPort;
403+
} else if (!IsNodeOnMainnet() && service_port == GetMainnetPurposePort(Purpose::CORE_P2P)) {
404+
// The mainnet CORE_P2P port may not be used for any purpose outside of mainnet
405+
return NetInfoStatus::BadPort;
406+
}
383407
}
384408

385409
return NetInfoStatus::Success;
@@ -415,7 +439,7 @@ NetInfoStatus ExtNetInfo::AddEntry(const uint8_t purpose, const std::string& inp
415439
CNetAddr netaddr;
416440
if (netaddr.SetSpecial(addr)) {
417441
const CService service{netaddr, port};
418-
const auto ret{ValidateService(service, /*is_primary=*/false)};
442+
const auto ret{ValidateService(service, purpose, /*is_primary=*/false)};
419443
if (ret == NetInfoStatus::Success) {
420444
return ProcessCandidate(purpose, NetInfoEntry{service});
421445
}
@@ -428,7 +452,7 @@ NetInfoStatus ExtNetInfo::AddEntry(const uint8_t purpose, const std::string& inp
428452
// IP:port safe, try to parse it as IP:port
429453
if (auto service_opt{Lookup(addr, /*portDefault=*/port, /*fAllowLookup=*/false)}) {
430454
const auto service{MaybeFlipIPv6toCJDNS(*service_opt)};
431-
const auto ret{ValidateService(service, is_primary)};
455+
const auto ret{ValidateService(service, purpose, is_primary)};
432456
if (ret == NetInfoStatus::Success) {
433457
return ProcessCandidate(purpose, NetInfoEntry{service});
434458
}
@@ -494,7 +518,7 @@ NetInfoStatus ExtNetInfo::Validate() const
494518
return NetInfoStatus::Malformed;
495519
}
496520
if (const auto& service_opt{entry.GetAddrPort()}) {
497-
if (auto ret{ValidateService(*service_opt, /*is_primary=*/entry == *entries.begin())};
521+
if (auto ret{ValidateService(*service_opt, purpose, /*is_primary=*/entry == *entries.begin())};
498522
ret != NetInfoStatus::Success) {
499523
// Stores CService underneath but doesn't pass validation rules
500524
return ret;

src/evo/netinfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ class ExtNetInfo final : public NetInfoInterface
262262
bool HasDuplicates(const std::vector<NetInfoEntry>* entries) const;
263263
bool IsDuplicateCandidate(const NetInfoEntry& candidate, const std::vector<NetInfoEntry>* entries) const;
264264
NetInfoStatus ProcessCandidate(const uint8_t purpose, const NetInfoEntry& candidate);
265-
static NetInfoStatus ValidateService(const CService& service, bool is_primary);
265+
static NetInfoStatus ValidateService(const CService& service, const uint8_t purpose, bool is_primary);
266266

267267
private:
268268
uint8_t m_version{CURRENT_VERSION};

src/test/evo_netinfo_tests.cpp

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ static const TestVectors vals_main{
2323
// - Port should default to default P2P core with MnNetInfo
2424
// - Ports are no longer implied with ExtNetInfo
2525
{{Purpose::CORE_P2P, "1.1.1.1"}, NetInfoStatus::Success, NetInfoStatus::BadPort},
26-
// - Non-mainnet port on mainnet causes failure in MnNetInfo
27-
// - ExtNetInfo is indifferent to choice of port unless it's a bad port which 9998 isn't
28-
{{Purpose::CORE_P2P, "1.1.1.1:9998"}, NetInfoStatus::BadPort, NetInfoStatus::Success},
26+
// Non-mainnet port on mainnet not allowed
27+
{{Purpose::CORE_P2P, "1.1.1.1:9998"}, NetInfoStatus::BadPort, NetInfoStatus::BadPort},
2928
// Internal addresses not allowed on mainnet
3029
{{Purpose::CORE_P2P, "127.0.0.1:9999"}, NetInfoStatus::NotRoutable, NetInfoStatus::NotRoutable},
3130
// Valid IPv4 formatting but invalid IPv4 address
@@ -44,9 +43,14 @@ static const TestVectors vals_main{
4443
{{Purpose::CORE_P2P, ":9999"}, NetInfoStatus::BadInput, NetInfoStatus::BadInput},
4544
// Bad purpose code
4645
{{64, "1.1.1.1:9999"}, NetInfoStatus::MaxLimit, NetInfoStatus::MaxLimit},
47-
// - MnNetInfo doesn't allow storing anything except a Core P2P address
48-
// - ExtNetInfo allows storing Platform P2P addresses
49-
{{Purpose::PLATFORM_P2P, "1.1.1.1:9999"}, NetInfoStatus::MaxLimit, NetInfoStatus::Success},
46+
// - MnNetInfo only supports CORE_P2P and cannot store PLATFORM_HTTPS
47+
// - ExtNetInfo can store PLATFORM_HTTPS but non-default ports are not allowed on mainnet
48+
{{Purpose::PLATFORM_HTTPS, "1.1.1.1:443"}, NetInfoStatus::MaxLimit, NetInfoStatus::Success},
49+
{{Purpose::PLATFORM_HTTPS, "1.1.1.1:1445"}, NetInfoStatus::MaxLimit, NetInfoStatus::BadPort},
50+
// - MnNetInfo only supports CORE_P2P and cannot store PLATFORM_HTTPS
51+
// - ExtNetInfo can store PLATFORM_P2P but non-default ports are not allowed on mainnet
52+
{{Purpose::PLATFORM_P2P, "1.1.1.1:26656"}, NetInfoStatus::MaxLimit, NetInfoStatus::Success},
53+
{{Purpose::PLATFORM_P2P, "1.1.1.1:26657"}, NetInfoStatus::MaxLimit, NetInfoStatus::BadPort},
5054
};
5155

5256
void ValidateGetEntries(const NetInfoList& entries, const size_t expected_size)
@@ -125,12 +129,21 @@ static const TestVectors vals_reg{
125129
// - MnNetInfo doesn't mind using port 0
126130
// - ExtNetInfo prohibits non-zero ports
127131
{{Purpose::CORE_P2P, "1.1.1.1:0"}, NetInfoStatus::Success, NetInfoStatus::BadPort},
128-
// - Mainnet P2P port on non-mainnet cause failure in MnNetInfo
129-
// - ExtNetInfo is indifferent to choice of port unless it's a bad port which 9999 isn't
130-
{{Purpose::CORE_P2P, "1.1.1.1:9999"}, NetInfoStatus::BadPort, NetInfoStatus::Success},
132+
// Mainnet P2P port on non-mainnet not allowed
133+
{{Purpose::CORE_P2P, "1.1.1.1:9999"}, NetInfoStatus::BadPort, NetInfoStatus::BadPort},
131134
// - Non-mainnet P2P port is allowed in MnNetInfo regardless of bad port status
132135
// - Port 22 (SSH) is below the privileged ports threshold (1023) and is therefore a bad port, disallowed in ExtNetInfo
133136
{{Purpose::CORE_P2P, "1.1.1.1:22"}, NetInfoStatus::Success, NetInfoStatus::BadPort},
137+
// - MnNetInfo only supports CORE_P2P and cannot store PLATFORM_HTTPS
138+
// - ExtNetInfo can store PLATFORM_HTTPS but default and non-default ports are allowed on non-mainnet
139+
{{Purpose::PLATFORM_HTTPS, "1.1.1.1:443"}, NetInfoStatus::MaxLimit, NetInfoStatus::Success},
140+
{{Purpose::PLATFORM_HTTPS, "1.1.1.1:1445"}, NetInfoStatus::MaxLimit, NetInfoStatus::Success},
141+
// The port 443 bad ports carve-out doesn't apply outside PLATFORM_HTTPS (where it is default)
142+
{{Purpose::PLATFORM_P2P, "1.1.1.1:443"}, NetInfoStatus::MaxLimit, NetInfoStatus::BadPort},
143+
// - MnNetInfo only supports CORE_P2P and cannot store PLATFORM_P2P
144+
// - ExtNetInfo can store PLATFORM_P2P but default and non-default ports are allowed on non-mainnet
145+
{{Purpose::PLATFORM_P2P, "1.1.1.1:26656"}, NetInfoStatus::MaxLimit, NetInfoStatus::Success},
146+
{{Purpose::PLATFORM_P2P, "1.1.1.1:26657"}, NetInfoStatus::MaxLimit, NetInfoStatus::Success},
134147
};
135148

136149
enum class ExpectedType : uint8_t {

0 commit comments

Comments
 (0)