Skip to content

Commit 8da8642

Browse files
committed
net: make V2Transport auto-detect incoming V1 and fall back to it
1 parent 13a7f01 commit 8da8642

File tree

3 files changed

+158
-20
lines changed

3 files changed

+158
-20
lines changed

src/net.cpp

+93-7
Original file line numberDiff line numberDiff line change
@@ -915,9 +915,9 @@ size_t V1Transport::GetSendMemoryUsage() const noexcept
915915

916916
V2Transport::V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in) noexcept :
917917
m_cipher{}, m_initiating{initiating}, m_nodeid{nodeid},
918-
m_recv_type{type_in}, m_recv_version{version_in},
919-
m_recv_state{RecvState::KEY},
920-
m_send_state{SendState::AWAITING_KEY}
918+
m_v1_fallback{nodeid, type_in, version_in}, m_recv_type{type_in}, m_recv_version{version_in},
919+
m_recv_state{initiating ? RecvState::KEY : RecvState::KEY_MAYBE_V1},
920+
m_send_state{initiating ? SendState::AWAITING_KEY : SendState::MAYBE_V1}
921921
{
922922
// Initialize the send buffer with ellswift pubkey.
923923
m_send_buffer.resize(EllSwiftPubKey::size());
@@ -926,9 +926,9 @@ V2Transport::V2Transport(NodeId nodeid, bool initiating, int type_in, int versio
926926

927927
V2Transport::V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in, const CKey& key, Span<const std::byte> ent32) noexcept :
928928
m_cipher{key, ent32}, m_initiating{initiating}, m_nodeid{nodeid},
929-
m_recv_type{type_in}, m_recv_version{version_in},
930-
m_recv_state{RecvState::KEY},
931-
m_send_state{SendState::AWAITING_KEY}
929+
m_v1_fallback{nodeid, type_in, version_in}, m_recv_type{type_in}, m_recv_version{version_in},
930+
m_recv_state{initiating ? RecvState::KEY : RecvState::KEY_MAYBE_V1},
931+
m_send_state{initiating ? SendState::AWAITING_KEY : SendState::MAYBE_V1}
932932
{
933933
// Initialize the send buffer with ellswift pubkey.
934934
m_send_buffer.resize(EllSwiftPubKey::size());
@@ -940,6 +940,9 @@ void V2Transport::SetReceiveState(RecvState recv_state) noexcept
940940
AssertLockHeld(m_recv_mutex);
941941
// Enforce allowed state transitions.
942942
switch (m_recv_state) {
943+
case RecvState::KEY_MAYBE_V1:
944+
Assume(recv_state == RecvState::KEY || recv_state == RecvState::V1);
945+
break;
943946
case RecvState::KEY:
944947
Assume(recv_state == RecvState::GARB_GARBTERM);
945948
break;
@@ -958,6 +961,9 @@ void V2Transport::SetReceiveState(RecvState recv_state) noexcept
958961
case RecvState::APP_READY:
959962
Assume(recv_state == RecvState::APP);
960963
break;
964+
case RecvState::V1:
965+
Assume(false); // V1 state cannot be left
966+
break;
961967
}
962968
// Change state.
963969
m_recv_state = recv_state;
@@ -968,11 +974,15 @@ void V2Transport::SetSendState(SendState send_state) noexcept
968974
AssertLockHeld(m_send_mutex);
969975
// Enforce allowed state transitions.
970976
switch (m_send_state) {
977+
case SendState::MAYBE_V1:
978+
Assume(send_state == SendState::V1 || send_state == SendState::AWAITING_KEY);
979+
break;
971980
case SendState::AWAITING_KEY:
972981
Assume(send_state == SendState::READY);
973982
break;
974983
case SendState::READY:
975-
Assume(false); // Final state
984+
case SendState::V1:
985+
Assume(false); // Final states
976986
break;
977987
}
978988
// Change state.
@@ -983,9 +993,48 @@ bool V2Transport::ReceivedMessageComplete() const noexcept
983993
{
984994
AssertLockNotHeld(m_recv_mutex);
985995
LOCK(m_recv_mutex);
996+
if (m_recv_state == RecvState::V1) return m_v1_fallback.ReceivedMessageComplete();
997+
986998
return m_recv_state == RecvState::APP_READY;
987999
}
9881000

1001+
void V2Transport::ProcessReceivedMaybeV1Bytes() noexcept
1002+
{
1003+
AssertLockHeld(m_recv_mutex);
1004+
AssertLockNotHeld(m_send_mutex);
1005+
Assume(m_recv_state == RecvState::KEY_MAYBE_V1);
1006+
// We still have to determine if this is a v1 or v2 connection. The bytes being received could
1007+
// be the beginning of either a v1 packet (network magic + "version\x00"), or of a v2 public
1008+
// key. BIP324 specifies that a mismatch with this 12-byte string should trigger sending of the
1009+
// key.
1010+
std::array<uint8_t, V1_PREFIX_LEN> v1_prefix = {0, 0, 0, 0, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0};
1011+
std::copy(std::begin(Params().MessageStart()), std::end(Params().MessageStart()), v1_prefix.begin());
1012+
Assume(m_recv_buffer.size() <= v1_prefix.size());
1013+
if (!std::equal(m_recv_buffer.begin(), m_recv_buffer.end(), v1_prefix.begin())) {
1014+
// Mismatch with v1 prefix, so we can assume a v2 connection.
1015+
SetReceiveState(RecvState::KEY); // Convert to KEY state, leaving received bytes around.
1016+
// Transition the sender to AWAITING_KEY state (if not already).
1017+
LOCK(m_send_mutex);
1018+
SetSendState(SendState::AWAITING_KEY);
1019+
} else if (m_recv_buffer.size() == v1_prefix.size()) {
1020+
// Full match with the v1 prefix, so fall back to v1 behavior.
1021+
LOCK(m_send_mutex);
1022+
Span<const uint8_t> feedback{m_recv_buffer};
1023+
// Feed already received bytes to v1 transport. It should always accept these, because it's
1024+
// less than the size of a v1 header, and these are the first bytes fed to m_v1_fallback.
1025+
bool ret = m_v1_fallback.ReceivedBytes(feedback);
1026+
Assume(feedback.empty());
1027+
Assume(ret);
1028+
SetReceiveState(RecvState::V1);
1029+
SetSendState(SendState::V1);
1030+
// Reset v2 transport buffers to save memory.
1031+
m_recv_buffer = {};
1032+
m_send_buffer = {};
1033+
} else {
1034+
// We have not received enough to distinguish v1 from v2 yet. Wait until more bytes come.
1035+
}
1036+
}
1037+
9891038
void V2Transport::ProcessReceivedKeyBytes() noexcept
9901039
{
9911040
AssertLockHeld(m_recv_mutex);
@@ -1143,6 +1192,15 @@ size_t V2Transport::GetMaxBytesToProcess() noexcept
11431192
{
11441193
AssertLockHeld(m_recv_mutex);
11451194
switch (m_recv_state) {
1195+
case RecvState::KEY_MAYBE_V1:
1196+
// During the KEY_MAYBE_V1 state we do not allow more than the length of v1 prefix into the
1197+
// receive buffer.
1198+
Assume(m_recv_buffer.size() <= V1_PREFIX_LEN);
1199+
// As long as we're not sure if this is a v1 or v2 connection, don't receive more than what
1200+
// is strictly necessary to distinguish the two (12 bytes). If we permitted more than
1201+
// the v1 header size (24 bytes), we may not be able to feed the already-received bytes
1202+
// back into the m_v1_fallback V1 transport.
1203+
return V1_PREFIX_LEN - m_recv_buffer.size();
11461204
case RecvState::KEY:
11471205
// During the KEY state, we only allow the 64-byte key into the receive buffer.
11481206
Assume(m_recv_buffer.size() <= EllSwiftPubKey::size());
@@ -1171,6 +1229,10 @@ size_t V2Transport::GetMaxBytesToProcess() noexcept
11711229
case RecvState::APP_READY:
11721230
// No bytes can be processed until GetMessage() is called.
11731231
return 0;
1232+
case RecvState::V1:
1233+
// Not allowed (must be dealt with by the caller).
1234+
Assume(false);
1235+
return 0;
11741236
}
11751237
Assume(false); // unreachable
11761238
return 0;
@@ -1180,6 +1242,8 @@ bool V2Transport::ReceivedBytes(Span<const uint8_t>& msg_bytes) noexcept
11801242
{
11811243
AssertLockNotHeld(m_recv_mutex);
11821244
LOCK(m_recv_mutex);
1245+
if (m_recv_state == RecvState::V1) return m_v1_fallback.ReceivedBytes(msg_bytes);
1246+
11831247
// Process the provided bytes in msg_bytes in a loop. In each iteration a nonzero number of
11841248
// bytes (decided by GetMaxBytesToProcess) are taken from the beginning om msg_bytes, and
11851249
// appended to m_recv_buffer. Then, depending on the receiver state, one of the
@@ -1195,6 +1259,11 @@ bool V2Transport::ReceivedBytes(Span<const uint8_t>& msg_bytes) noexcept
11951259

11961260
// Process data in the buffer.
11971261
switch (m_recv_state) {
1262+
case RecvState::KEY_MAYBE_V1:
1263+
ProcessReceivedMaybeV1Bytes();
1264+
if (m_recv_state == RecvState::V1) return true;
1265+
break;
1266+
11981267
case RecvState::KEY:
11991268
ProcessReceivedKeyBytes();
12001269
break;
@@ -1211,6 +1280,11 @@ bool V2Transport::ReceivedBytes(Span<const uint8_t>& msg_bytes) noexcept
12111280

12121281
case RecvState::APP_READY:
12131282
return true;
1283+
1284+
case RecvState::V1:
1285+
// We should have bailed out before.
1286+
Assume(false);
1287+
break;
12141288
}
12151289
// Make sure we have made progress before continuing.
12161290
Assume(max_read > 0);
@@ -1254,6 +1328,8 @@ CNetMessage V2Transport::GetReceivedMessage(std::chrono::microseconds time, bool
12541328
{
12551329
AssertLockNotHeld(m_recv_mutex);
12561330
LOCK(m_recv_mutex);
1331+
if (m_recv_state == RecvState::V1) return m_v1_fallback.GetReceivedMessage(time, reject_message);
1332+
12571333
Assume(m_recv_state == RecvState::APP_READY);
12581334
Span<const uint8_t> contents{m_recv_decode_buffer};
12591335
auto msg_type = GetMessageType(contents);
@@ -1282,6 +1358,7 @@ bool V2Transport::SetMessageToSend(CSerializedNetMsg& msg) noexcept
12821358
{
12831359
AssertLockNotHeld(m_send_mutex);
12841360
LOCK(m_send_mutex);
1361+
if (m_send_state == SendState::V1) return m_v1_fallback.SetMessageToSend(msg);
12851362
// We only allow adding a new message to be sent when in the READY state (so the packet cipher
12861363
// is available) and the send buffer is empty. This limits the number of messages in the send
12871364
// buffer to just one, and leaves the responsibility for queueing them up to the caller.
@@ -1305,6 +1382,11 @@ Transport::BytesToSend V2Transport::GetBytesToSend(bool have_next_message) const
13051382
{
13061383
AssertLockNotHeld(m_send_mutex);
13071384
LOCK(m_send_mutex);
1385+
if (m_send_state == SendState::V1) return m_v1_fallback.GetBytesToSend(have_next_message);
1386+
1387+
// We do not send anything in MAYBE_V1 state (as we don't know if the peer is v1 or v2),
1388+
// despite there being data in the send buffer in that state.
1389+
if (m_send_state == SendState::MAYBE_V1) return {{}, false, m_send_type};
13081390
Assume(m_send_pos <= m_send_buffer.size());
13091391
return {
13101392
Span{m_send_buffer}.subspan(m_send_pos),
@@ -1319,6 +1401,8 @@ void V2Transport::MarkBytesSent(size_t bytes_sent) noexcept
13191401
{
13201402
AssertLockNotHeld(m_send_mutex);
13211403
LOCK(m_send_mutex);
1404+
if (m_send_state == SendState::V1) return m_v1_fallback.MarkBytesSent(bytes_sent);
1405+
13221406
m_send_pos += bytes_sent;
13231407
Assume(m_send_pos <= m_send_buffer.size());
13241408
if (m_send_pos == m_send_buffer.size()) {
@@ -1331,6 +1415,8 @@ size_t V2Transport::GetSendMemoryUsage() const noexcept
13311415
{
13321416
AssertLockNotHeld(m_send_mutex);
13331417
LOCK(m_send_mutex);
1418+
if (m_send_state == SendState::V1) return m_v1_fallback.GetSendMemoryUsage();
1419+
13341420
return sizeof(m_send_buffer) + memusage::DynamicUsage(m_send_buffer);
13351421
}
13361422

src/net.h

+54-13
Original file line numberDiff line numberDiff line change
@@ -444,25 +444,40 @@ class V2Transport final : public Transport
444444
* an empty version packet contents is interpreted as no extensions supported. */
445445
static constexpr std::array<std::byte, 0> VERSION_CONTENTS = {};
446446

447+
/** The length of the V1 prefix to match bytes initially received by responders with to
448+
* determine if their peer is speaking V1 or V2. */
449+
static constexpr size_t V1_PREFIX_LEN = 12;
450+
447451
// The sender side and receiver side of V2Transport are state machines that are transitioned
448452
// through, based on what has been received. The receive state corresponds to the contents of,
449453
// and bytes received to, the receive buffer. The send state controls what can be appended to
450-
// the send buffer.
454+
// the send buffer and what can be sent from it.
451455

452456
/** State type that defines the current contents of the receive buffer and/or how the next
453457
* received bytes added to it will be interpreted.
454458
*
455459
* Diagram:
456460
*
457-
* start /---------\
458-
* | | |
459-
* v v |
460-
* KEY -> GARB_GARBTERM -> GARBAUTH -> VERSION -> APP -> APP_READY
461+
* start(responder)
462+
* |
463+
* | start(initiator) /---------\
464+
* | | | |
465+
* v v v |
466+
* KEY_MAYBE_V1 -> KEY -> GARB_GARBTERM -> GARBAUTH -> VERSION -> APP -> APP_READY
467+
* |
468+
* \-------> V1
461469
*/
462470
enum class RecvState : uint8_t {
471+
/** (Responder only) either v2 public key or v1 header.
472+
*
473+
* This is the initial state for responders, before data has been received to distinguish
474+
* v1 from v2 connections. When that happens, the state becomes either KEY (for v2) or V1
475+
* (for v1). */
476+
KEY_MAYBE_V1,
477+
463478
/** Public key.
464479
*
465-
* This is the initial state, during which the other side's public key is
480+
* This is the initial state for initiators, during which the other side's public key is
466481
* received. When that information arrives, the ciphers get initialized and the state
467482
* becomes GARB_GARBTERM. */
468483
KEY,
@@ -502,23 +517,40 @@ class V2Transport final : public Transport
502517
* Nothing can be received in this state. When the message is retrieved by GetMessage,
503518
* the state becomes APP again. */
504519
APP_READY,
520+
521+
/** Nothing (this transport is using v1 fallback).
522+
*
523+
* All receive operations are redirected to m_v1_fallback. */
524+
V1,
505525
};
506526

507527
/** State type that controls the sender side.
508528
*
509529
* Diagram:
510530
*
511-
* start
512-
* |
513-
* v
514-
* AWAITING_KEY -> READY
531+
* start(responder)
532+
* |
533+
* | start(initiator)
534+
* | |
535+
* v v
536+
* MAYBE_V1 -> AWAITING_KEY -> READY
537+
* |
538+
* \-----> V1
515539
*/
516540
enum class SendState : uint8_t {
541+
/** (Responder only) Not sending until v1 or v2 is detected.
542+
*
543+
* This is the initial state for responders. The send buffer contains the public key to
544+
* send, but nothing is sent in this state yet. When the receiver determines whether this
545+
* is a V1 or V2 connection, the sender state becomes AWAITING_KEY (for v2) or V1 (for v1).
546+
*/
547+
MAYBE_V1,
548+
517549
/** Waiting for the other side's public key.
518550
*
519-
* This is the initial state. The public key is sent out. When the receiver receives the
520-
* other side's public key and transitions to GARB_GARBTERM, the sender state becomes
521-
* READY. */
551+
* This is the initial state for initiators. The public key is sent out. When the receiver
552+
* receives the other side's public key and transitions to GARB_GARBTERM, the sender state
553+
* becomes READY. */
522554
AWAITING_KEY,
523555

524556
/** Normal sending state.
@@ -528,6 +560,11 @@ class V2Transport final : public Transport
528560
* appended to the send buffer (in addition to the key which may still be there). In this
529561
* state a message can be provided if the send buffer is empty. */
530562
READY,
563+
564+
/** This transport is using v1 fallback.
565+
*
566+
* All send operations are redirected to m_v1_fallback. */
567+
V1,
531568
};
532569

533570
/** Cipher state. */
@@ -536,6 +573,8 @@ class V2Transport final : public Transport
536573
const bool m_initiating;
537574
/** NodeId (for debug logging). */
538575
const NodeId m_nodeid;
576+
/** Encapsulate a V1Transport to fall back to. */
577+
V1Transport m_v1_fallback;
539578

540579
/** Lock for receiver-side fields. */
541580
mutable Mutex m_recv_mutex ACQUIRED_BEFORE(m_send_mutex);
@@ -575,6 +614,8 @@ class V2Transport final : public Transport
575614
static std::optional<std::string> GetMessageType(Span<const uint8_t>& contents) noexcept;
576615
/** Determine how many received bytes can be processed in one go (not allowed in V1 state). */
577616
size_t GetMaxBytesToProcess() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex);
617+
/** Process bytes in m_recv_buffer, while in KEY_MAYBE_V1 state. */
618+
void ProcessReceivedMaybeV1Bytes() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex, !m_send_mutex);
578619
/** Process bytes in m_recv_buffer, while in KEY state. */
579620
void ProcessReceivedKeyBytes() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex, !m_send_mutex);
580621
/** Process bytes in m_recv_buffer, while in GARB_GARBTERM state. */

src/test/fuzz/p2p_transport_serialization.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,14 @@ FUZZ_TARGET(p2p_transport_bidirectional_v2, .init = initialize_p2p_transport_ser
371371
if (!t1 || !t2) return;
372372
SimulationTest(*t1, *t2, rng, provider);
373373
}
374+
375+
FUZZ_TARGET(p2p_transport_bidirectional_v1v2, .init = initialize_p2p_transport_serialization)
376+
{
377+
// Test with a V1 initiator talking to a V2 responder.
378+
FuzzedDataProvider provider{buffer.data(), buffer.size()};
379+
XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral<uint64_t>());
380+
auto t1 = MakeV1Transport(NodeId{0});
381+
auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider);
382+
if (!t1 || !t2) return;
383+
SimulationTest(*t1, *t2, rng, provider);
384+
}

0 commit comments

Comments
 (0)