Skip to content

Commit b5f0ebb

Browse files
Crypt-iQRoasbeef
authored andcommitted
v2transport+server+peer: add v2->v1 downgrade error if EOF during handshake
1 parent 6588e2e commit b5f0ebb

File tree

3 files changed

+75
-19
lines changed

3 files changed

+75
-19
lines changed

peer/peer.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ type Config struct {
293293

294294
// UsingV2Conn is defined if and only if we accept and attempt to make
295295
// v2 connections.
296+
// TODO: Modify this so that we can downgrade certain peers.
296297
UsingV2Conn bool
297298
}
298299

@@ -468,7 +469,8 @@ type Peer struct {
468469
witnessEnabled bool
469470
sendAddrV2 bool
470471

471-
V2Transport *v2transport.Peer
472+
V2Transport *v2transport.Peer
473+
shouldDowngradeToV1 atomic.Bool
472474

473475
wireEncoding wire.MessageEncoding
474476

@@ -2345,13 +2347,19 @@ func (p *Peer) negotiateOutboundProtocol() error {
23452347
// and reconnect using a v1 connection. This is the logic that
23462348
// bitcoind uses.
23472349
// TODO: random value for garbage len?
2348-
if err := p.V2Transport.InitiateV2Handshake(1); err != nil {
2350+
if err := p.V2Transport.InitiateV2Handshake(0); err != nil {
23492351
return err
23502352
}
23512353

2352-
if err := p.V2Transport.CompleteHandshake(
2354+
err := p.V2Transport.CompleteHandshake(
23532355
true, nil, p.cfg.ChainParams.Net,
2354-
); err != nil {
2356+
)
2357+
if errors.Is(err, v2transport.ErrShouldDowngradeToV1) {
2358+
// If we should downgrade, mark Peer and then return an error to
2359+
// trigger a Disconnect call.
2360+
p.shouldDowngradeToV1.Store(true)
2361+
return err
2362+
} else if err != nil {
23552363
return err
23562364
}
23572365
}
@@ -2470,6 +2478,13 @@ func (p *Peer) WaitForDisconnect() {
24702478
<-p.quit
24712479
}
24722480

2481+
// ShouldDowngradeToV1 is called when we try to connect to a peer via v2 BIP324
2482+
// transport and they hang up. In this case, we should reconnect with the
2483+
// legacy transport.
2484+
func (p *Peer) ShouldDowngradeToV1() bool {
2485+
return p.shouldDowngradeToV1.Load()
2486+
}
2487+
24732488
// newPeerBase returns a new base bitcoin peer based on the inbound flag. This
24742489
// is used by the NewInboundPeer and NewOutboundPeer functions to perform base
24752490
// setup needed by both types of peers.

server.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2209,6 +2209,19 @@ func (s *server) outboundPeerConnected(c *connmgr.ConnReq, conn net.Conn) {
22092209
// done along with other performing other desirable cleanup.
22102210
func (s *server) peerDoneHandler(sp *serverPeer) {
22112211
sp.WaitForDisconnect()
2212+
2213+
// If this is an outbound peer and the shouldDowngradeToV1 bool is set on
2214+
// the underlying Peer, trigger a reconnect using the OG v1 connection
2215+
// scheme.
2216+
if !sp.Inbound() && sp.Peer.ShouldDowngradeToV1() {
2217+
// TODO: Determine _how_ to trigger a reconnect that does not use v2
2218+
// transport. If it goes to donePeers, the Disconnect call for
2219+
// persistent peers will trigger a reconnect. For non-persistent
2220+
// peers, we will disconnect them and then find a new peer to
2221+
// connect to.
2222+
}
2223+
2224+
// This is sent to a buffered channel, so it may not execute immediately.
22122225
s.donePeers <- sp
22132226

22142227
// Only tell sync manager we are gone if we ever told it we existed.

v2transport/transport.go

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ var (
8888
// errGarbageTooLarge is returned if a caller attempts to send garbage
8989
// larger than normal.
9090
errGarbageTooLarge = fmt.Errorf("garbage too large")
91+
92+
// ErrShouldDowngradeToV1 is returned when we send the peer our
93+
// ellswift key and they immediately hang up. This indicates that they
94+
// don't understand v2 transport and interpreted the 64-byte key as a
95+
// v1 message header + message. This will (always?) decode to an
96+
// invalid command and checksum. The caller should try connecting to
97+
// the peer with the OG v1 transport.
98+
ErrShouldDowngradeToV1 = fmt.Errorf("should downgrade to v1")
9199
)
92100

93101
// Peer defines the components necessary for sending/receiving data over the v2
@@ -376,7 +384,7 @@ func (p *Peer) RespondV2Handshake(garbageLen int, net wire.BitcoinNet) error {
376384
len(p.receivedPrefix), len(v1Prefix))
377385

378386
var receiveBytes []byte
379-
receiveBytes, err = p.Receive(1)
387+
receiveBytes, _, err = p.Receive(1)
380388
if err != nil {
381389
log.Errorf("Failed to receive byte for v1 prefix "+
382390
"check: %v", err)
@@ -473,11 +481,11 @@ func createV1Prefix(net wire.BitcoinNet) []byte {
473481
// CompleteHandshake finishes the v2 protocol negotiation and optionally sends
474482
// decoy packets after sending the garbage terminator.
475483
func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
476-
net wire.BitcoinNet) error {
484+
btcnet wire.BitcoinNet) error {
477485

478486
log.Debugf("Completing v2 handshake (initiating=%v, "+
479487
"num_decoys=%d, net=%v)", initiating, len(decoyContentLens),
480-
net)
488+
btcnet)
481489

482490
var receivedPrefix []byte
483491
if initiating {
@@ -498,8 +506,28 @@ func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
498506
len(receivedPrefix), 64-len(receivedPrefix))
499507
}
500508

501-
recvData, err := p.Receive(64 - len(receivedPrefix))
509+
recvData, numRead, err := p.Receive(64 - len(receivedPrefix))
502510
if err != nil {
511+
// If we receive an error when reading off the wire and we read
512+
// zero bytes, then we will reconnect to the peer using v1.
513+
// There are several different errors that Receive can return
514+
// that indicate we should reconnect. Instead of special-casing
515+
// them all, just perform these checks if any error was
516+
// returned.
517+
if numRead == 0 && initiating {
518+
// The peer most likely attempted to parse our 64-byte
519+
// elligator-swift key as a version message and failed
520+
// when trying to parse the message header into
521+
// something valid. In this case, return a special
522+
// error that signals to the server that we can
523+
// reconnect with the OG v1 scheme.
524+
log.Debugf("Received transport error during " +
525+
"v2 handshake, retying downgraded v1 " +
526+
"connection.")
527+
return ErrShouldDowngradeToV1
528+
}
529+
530+
// If we are the recipient, we can fail.
503531
log.Errorf("Failed to receive peer's ellswift key data: %v",
504532
err)
505533
return err
@@ -533,7 +561,7 @@ func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
533561

534562
// Calculate the v1 protocol's message prefix and see if the bytes read
535563
// read into ellswiftTheirs matches it.
536-
v1Prefix := createV1Prefix(net)
564+
v1Prefix := createV1Prefix(btcnet)
537565

538566
// ellswiftTheirs should be at least 16 bytes if receive succeeded, but
539567
// just in case, check the size.
@@ -546,7 +574,7 @@ func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
546574

547575
if !initiating && bytes.Equal(ellswiftTheirs[4:16], v1Prefix[4:16]) {
548576
log.Warnf("Peer sent v1 version message for wrong network "+
549-
"(expected %v)", net)
577+
"(expected %v)", btcnet)
550578
return errWrongNetV1Peer
551579
}
552580

@@ -563,7 +591,7 @@ func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
563591

564592
log.Tracef("Calculated ECDH shared secret: %x", ecdhSecret)
565593

566-
err = p.createV2Ciphers(ecdhSecret[:], initiating, net)
594+
err = p.createV2Ciphers(ecdhSecret[:], initiating, btcnet)
567595
if err != nil {
568596
// createV2Ciphers logs its own errors
569597
return err
@@ -606,7 +634,7 @@ func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
606634
p.recvGarbageTerm)
607635

608636
// Skip garbage until encountering garbage terminator.
609-
recvGarbage, err := p.Receive(16)
637+
recvGarbage, _, err := p.Receive(16)
610638
if err != nil {
611639
log.Errorf("Failed to receive initial 16 bytes of "+
612640
"garbage: %v", err)
@@ -645,7 +673,7 @@ func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
645673
log.Tracef("Garbage terminator not found, receiving 1 more "+
646674
"byte (total_received=%d)", recvGarbageLen)
647675

648-
recvData, err := p.Receive(1)
676+
recvData, _, err := p.Receive(1)
649677
if err != nil {
650678
log.Errorf("Failed to receive garbage "+
651679
"byte %d: %v", recvGarbageLen+1, err)
@@ -746,7 +774,7 @@ func (p *Peer) V2ReceivePacket(aad []byte) ([]byte, error) {
746774
lengthFieldLen)
747775

748776
// Decrypt the length field so we know how many more bytes to receive.
749-
encContentsLen, err := p.Receive(lengthFieldLen)
777+
encContentsLen, _, err := p.Receive(lengthFieldLen)
750778
if err != nil {
751779
log.Errorf("Failed to receive encrypted length: %v",
752780
err)
@@ -783,7 +811,7 @@ func (p *Peer) V2ReceivePacket(aad []byte) ([]byte, error) {
783811
log.Tracef("Receiving %d bytes for encrypted packet body",
784812
numBytes)
785813

786-
aeadCiphertext, err := p.Receive(numBytes)
814+
aeadCiphertext, _, err := p.Receive(numBytes)
787815
if err != nil {
788816
log.Errorf("Failed to receive encrypted "+
789817
"packet body: %v", err)
@@ -844,7 +872,7 @@ func (p *Peer) Send(data []byte) (int, error) {
844872
}
845873

846874
// Receive receives numBytes bytes from the underlying connection.
847-
func (p *Peer) Receive(numBytes int) ([]byte, error) {
875+
func (p *Peer) Receive(numBytes int) ([]byte, int, error) {
848876
b := make([]byte, numBytes)
849877
index := 0
850878
total := 0
@@ -858,12 +886,12 @@ func (p *Peer) Receive(numBytes int) ([]byte, error) {
858886
// used implicitly by the loop structure.
859887
log.Criticalf("Receive logic error: total=%d > "+
860888
"numBytes=%d", total, numBytes)
861-
return nil, errFailedToRecv
889+
return nil, total, errFailedToRecv
862890
}
863891

864892
if total == numBytes {
865893
log.Tracef("Successfully received %d bytes", total)
866-
return b, nil
894+
return b, total, nil
867895
}
868896

869897
log.Tracef("Calling Read (need %d bytes, have "+
@@ -873,7 +901,7 @@ func (p *Peer) Receive(numBytes int) ([]byte, error) {
873901
if err != nil {
874902
log.Errorf("Receive failed after reading %d bytes "+
875903
"(target %d): %v", total+n, numBytes, err)
876-
return nil, err
904+
return nil, total, err
877905
}
878906

879907
log.Tracef("Read returned %d bytes", n)

0 commit comments

Comments
 (0)