mirror of
https://github.com/btcsuite/btcd.git
synced 2026-08-18 13:08:09 +02:00
v2transport+server+peer: add v2->v1 downgrade error if EOF during handshake
This commit is contained in:
parent
6588e2e829
commit
b5f0ebb132
3 changed files with 75 additions and 19 deletions
23
peer/peer.go
23
peer/peer.go
|
|
@ -293,6 +293,7 @@ type Config struct {
|
|||
|
||||
// UsingV2Conn is defined if and only if we accept and attempt to make
|
||||
// v2 connections.
|
||||
// TODO: Modify this so that we can downgrade certain peers.
|
||||
UsingV2Conn bool
|
||||
}
|
||||
|
||||
|
|
@ -468,7 +469,8 @@ type Peer struct {
|
|||
witnessEnabled bool
|
||||
sendAddrV2 bool
|
||||
|
||||
V2Transport *v2transport.Peer
|
||||
V2Transport *v2transport.Peer
|
||||
shouldDowngradeToV1 atomic.Bool
|
||||
|
||||
wireEncoding wire.MessageEncoding
|
||||
|
||||
|
|
@ -2345,13 +2347,19 @@ func (p *Peer) negotiateOutboundProtocol() error {
|
|||
// and reconnect using a v1 connection. This is the logic that
|
||||
// bitcoind uses.
|
||||
// TODO: random value for garbage len?
|
||||
if err := p.V2Transport.InitiateV2Handshake(1); err != nil {
|
||||
if err := p.V2Transport.InitiateV2Handshake(0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.V2Transport.CompleteHandshake(
|
||||
err := p.V2Transport.CompleteHandshake(
|
||||
true, nil, p.cfg.ChainParams.Net,
|
||||
); err != nil {
|
||||
)
|
||||
if errors.Is(err, v2transport.ErrShouldDowngradeToV1) {
|
||||
// If we should downgrade, mark Peer and then return an error to
|
||||
// trigger a Disconnect call.
|
||||
p.shouldDowngradeToV1.Store(true)
|
||||
return err
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -2470,6 +2478,13 @@ func (p *Peer) WaitForDisconnect() {
|
|||
<-p.quit
|
||||
}
|
||||
|
||||
// ShouldDowngradeToV1 is called when we try to connect to a peer via v2 BIP324
|
||||
// transport and they hang up. In this case, we should reconnect with the
|
||||
// legacy transport.
|
||||
func (p *Peer) ShouldDowngradeToV1() bool {
|
||||
return p.shouldDowngradeToV1.Load()
|
||||
}
|
||||
|
||||
// newPeerBase returns a new base bitcoin peer based on the inbound flag. This
|
||||
// is used by the NewInboundPeer and NewOutboundPeer functions to perform base
|
||||
// setup needed by both types of peers.
|
||||
|
|
|
|||
13
server.go
13
server.go
|
|
@ -2209,6 +2209,19 @@ func (s *server) outboundPeerConnected(c *connmgr.ConnReq, conn net.Conn) {
|
|||
// done along with other performing other desirable cleanup.
|
||||
func (s *server) peerDoneHandler(sp *serverPeer) {
|
||||
sp.WaitForDisconnect()
|
||||
|
||||
// If this is an outbound peer and the shouldDowngradeToV1 bool is set on
|
||||
// the underlying Peer, trigger a reconnect using the OG v1 connection
|
||||
// scheme.
|
||||
if !sp.Inbound() && sp.Peer.ShouldDowngradeToV1() {
|
||||
// TODO: Determine _how_ to trigger a reconnect that does not use v2
|
||||
// transport. If it goes to donePeers, the Disconnect call for
|
||||
// persistent peers will trigger a reconnect. For non-persistent
|
||||
// peers, we will disconnect them and then find a new peer to
|
||||
// connect to.
|
||||
}
|
||||
|
||||
// This is sent to a buffered channel, so it may not execute immediately.
|
||||
s.donePeers <- sp
|
||||
|
||||
// Only tell sync manager we are gone if we ever told it we existed.
|
||||
|
|
|
|||
|
|
@ -88,6 +88,14 @@ var (
|
|||
// errGarbageTooLarge is returned if a caller attempts to send garbage
|
||||
// larger than normal.
|
||||
errGarbageTooLarge = fmt.Errorf("garbage too large")
|
||||
|
||||
// ErrShouldDowngradeToV1 is returned when we send the peer our
|
||||
// ellswift key and they immediately hang up. This indicates that they
|
||||
// don't understand v2 transport and interpreted the 64-byte key as a
|
||||
// v1 message header + message. This will (always?) decode to an
|
||||
// invalid command and checksum. The caller should try connecting to
|
||||
// the peer with the OG v1 transport.
|
||||
ErrShouldDowngradeToV1 = fmt.Errorf("should downgrade to v1")
|
||||
)
|
||||
|
||||
// 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 {
|
|||
len(p.receivedPrefix), len(v1Prefix))
|
||||
|
||||
var receiveBytes []byte
|
||||
receiveBytes, err = p.Receive(1)
|
||||
receiveBytes, _, err = p.Receive(1)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to receive byte for v1 prefix "+
|
||||
"check: %v", err)
|
||||
|
|
@ -473,11 +481,11 @@ func createV1Prefix(net wire.BitcoinNet) []byte {
|
|||
// CompleteHandshake finishes the v2 protocol negotiation and optionally sends
|
||||
// decoy packets after sending the garbage terminator.
|
||||
func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
|
||||
net wire.BitcoinNet) error {
|
||||
btcnet wire.BitcoinNet) error {
|
||||
|
||||
log.Debugf("Completing v2 handshake (initiating=%v, "+
|
||||
"num_decoys=%d, net=%v)", initiating, len(decoyContentLens),
|
||||
net)
|
||||
btcnet)
|
||||
|
||||
var receivedPrefix []byte
|
||||
if initiating {
|
||||
|
|
@ -498,8 +506,28 @@ func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
|
|||
len(receivedPrefix), 64-len(receivedPrefix))
|
||||
}
|
||||
|
||||
recvData, err := p.Receive(64 - len(receivedPrefix))
|
||||
recvData, numRead, err := p.Receive(64 - len(receivedPrefix))
|
||||
if err != nil {
|
||||
// If we receive an error when reading off the wire and we read
|
||||
// zero bytes, then we will reconnect to the peer using v1.
|
||||
// There are several different errors that Receive can return
|
||||
// that indicate we should reconnect. Instead of special-casing
|
||||
// them all, just perform these checks if any error was
|
||||
// returned.
|
||||
if numRead == 0 && initiating {
|
||||
// The peer most likely attempted to parse our 64-byte
|
||||
// elligator-swift key as a version message and failed
|
||||
// when trying to parse the message header into
|
||||
// something valid. In this case, return a special
|
||||
// error that signals to the server that we can
|
||||
// reconnect with the OG v1 scheme.
|
||||
log.Debugf("Received transport error during " +
|
||||
"v2 handshake, retying downgraded v1 " +
|
||||
"connection.")
|
||||
return ErrShouldDowngradeToV1
|
||||
}
|
||||
|
||||
// If we are the recipient, we can fail.
|
||||
log.Errorf("Failed to receive peer's ellswift key data: %v",
|
||||
err)
|
||||
return err
|
||||
|
|
@ -533,7 +561,7 @@ func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
|
|||
|
||||
// Calculate the v1 protocol's message prefix and see if the bytes read
|
||||
// read into ellswiftTheirs matches it.
|
||||
v1Prefix := createV1Prefix(net)
|
||||
v1Prefix := createV1Prefix(btcnet)
|
||||
|
||||
// ellswiftTheirs should be at least 16 bytes if receive succeeded, but
|
||||
// just in case, check the size.
|
||||
|
|
@ -546,7 +574,7 @@ func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
|
|||
|
||||
if !initiating && bytes.Equal(ellswiftTheirs[4:16], v1Prefix[4:16]) {
|
||||
log.Warnf("Peer sent v1 version message for wrong network "+
|
||||
"(expected %v)", net)
|
||||
"(expected %v)", btcnet)
|
||||
return errWrongNetV1Peer
|
||||
}
|
||||
|
||||
|
|
@ -563,7 +591,7 @@ func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
|
|||
|
||||
log.Tracef("Calculated ECDH shared secret: %x", ecdhSecret)
|
||||
|
||||
err = p.createV2Ciphers(ecdhSecret[:], initiating, net)
|
||||
err = p.createV2Ciphers(ecdhSecret[:], initiating, btcnet)
|
||||
if err != nil {
|
||||
// createV2Ciphers logs its own errors
|
||||
return err
|
||||
|
|
@ -606,7 +634,7 @@ func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
|
|||
p.recvGarbageTerm)
|
||||
|
||||
// Skip garbage until encountering garbage terminator.
|
||||
recvGarbage, err := p.Receive(16)
|
||||
recvGarbage, _, err := p.Receive(16)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to receive initial 16 bytes of "+
|
||||
"garbage: %v", err)
|
||||
|
|
@ -645,7 +673,7 @@ func (p *Peer) CompleteHandshake(initiating bool, decoyContentLens []int,
|
|||
log.Tracef("Garbage terminator not found, receiving 1 more "+
|
||||
"byte (total_received=%d)", recvGarbageLen)
|
||||
|
||||
recvData, err := p.Receive(1)
|
||||
recvData, _, err := p.Receive(1)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to receive garbage "+
|
||||
"byte %d: %v", recvGarbageLen+1, err)
|
||||
|
|
@ -746,7 +774,7 @@ func (p *Peer) V2ReceivePacket(aad []byte) ([]byte, error) {
|
|||
lengthFieldLen)
|
||||
|
||||
// Decrypt the length field so we know how many more bytes to receive.
|
||||
encContentsLen, err := p.Receive(lengthFieldLen)
|
||||
encContentsLen, _, err := p.Receive(lengthFieldLen)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to receive encrypted length: %v",
|
||||
err)
|
||||
|
|
@ -783,7 +811,7 @@ func (p *Peer) V2ReceivePacket(aad []byte) ([]byte, error) {
|
|||
log.Tracef("Receiving %d bytes for encrypted packet body",
|
||||
numBytes)
|
||||
|
||||
aeadCiphertext, err := p.Receive(numBytes)
|
||||
aeadCiphertext, _, err := p.Receive(numBytes)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to receive encrypted "+
|
||||
"packet body: %v", err)
|
||||
|
|
@ -844,7 +872,7 @@ func (p *Peer) Send(data []byte) (int, error) {
|
|||
}
|
||||
|
||||
// Receive receives numBytes bytes from the underlying connection.
|
||||
func (p *Peer) Receive(numBytes int) ([]byte, error) {
|
||||
func (p *Peer) Receive(numBytes int) ([]byte, int, error) {
|
||||
b := make([]byte, numBytes)
|
||||
index := 0
|
||||
total := 0
|
||||
|
|
@ -858,12 +886,12 @@ func (p *Peer) Receive(numBytes int) ([]byte, error) {
|
|||
// used implicitly by the loop structure.
|
||||
log.Criticalf("Receive logic error: total=%d > "+
|
||||
"numBytes=%d", total, numBytes)
|
||||
return nil, errFailedToRecv
|
||||
return nil, total, errFailedToRecv
|
||||
}
|
||||
|
||||
if total == numBytes {
|
||||
log.Tracef("Successfully received %d bytes", total)
|
||||
return b, nil
|
||||
return b, total, nil
|
||||
}
|
||||
|
||||
log.Tracef("Calling Read (need %d bytes, have "+
|
||||
|
|
@ -873,7 +901,7 @@ func (p *Peer) Receive(numBytes int) ([]byte, error) {
|
|||
if err != nil {
|
||||
log.Errorf("Receive failed after reading %d bytes "+
|
||||
"(target %d): %v", total+n, numBytes, err)
|
||||
return nil, err
|
||||
return nil, total, err
|
||||
}
|
||||
|
||||
log.Tracef("Read returned %d bytes", n)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue