github.com/ethereum/go-ethereum@v1.16.1/p2p/transport.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package p2p 18 19 import ( 20 "bytes" 21 "crypto/ecdsa" 22 "errors" 23 "fmt" 24 "io" 25 "net" 26 "sync" 27 "time" 28 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/common/bitutil" 31 "github.com/ethereum/go-ethereum/metrics" 32 "github.com/ethereum/go-ethereum/p2p/rlpx" 33 "github.com/ethereum/go-ethereum/rlp" 34 ) 35 36 const ( 37 // total timeout for encryption handshake and protocol 38 // handshake in both directions. 39 handshakeTimeout = 5 * time.Second 40 41 // This is the timeout for sending the disconnect reason. 42 // This is shorter than the usual timeout because we don't want 43 // to wait if the connection is known to be bad anyway. 44 discWriteTimeout = 1 * time.Second 45 ) 46 47 // rlpxTransport is the transport used by actual (non-test) connections. 48 // It wraps an RLPx connection with locks and read/write deadlines. 49 type rlpxTransport struct { 50 rmu, wmu sync.Mutex 51 wbuf bytes.Buffer 52 conn *rlpx.Conn 53 } 54 55 func newRLPX(conn net.Conn, dialDest *ecdsa.PublicKey) transport { 56 return &rlpxTransport{conn: rlpx.NewConn(conn, dialDest)} 57 } 58 59 func (t *rlpxTransport) ReadMsg() (Msg, error) { 60 t.rmu.Lock() 61 defer t.rmu.Unlock() 62 63 var msg Msg 64 t.conn.SetReadDeadline(time.Now().Add(frameReadTimeout)) 65 code, data, wireSize, err := t.conn.Read() 66 if err == nil { 67 // Protocol messages are dispatched to subprotocol handlers asynchronously, 68 // but package rlpx may reuse the returned 'data' buffer on the next call 69 // to Read. Copy the message data to avoid this being an issue. 70 data = common.CopyBytes(data) 71 msg = Msg{ 72 ReceivedAt: time.Now(), 73 Code: code, 74 Size: uint32(len(data)), 75 meterSize: uint32(wireSize), 76 Payload: bytes.NewReader(data), 77 } 78 } 79 return msg, err 80 } 81 82 func (t *rlpxTransport) WriteMsg(msg Msg) error { 83 t.wmu.Lock() 84 defer t.wmu.Unlock() 85 86 // Copy message data to write buffer. 87 t.wbuf.Reset() 88 if _, err := io.CopyN(&t.wbuf, msg.Payload, int64(msg.Size)); err != nil { 89 return err 90 } 91 92 // Write the message. 93 t.conn.SetWriteDeadline(time.Now().Add(frameWriteTimeout)) 94 size, err := t.conn.Write(msg.Code, t.wbuf.Bytes()) 95 if err != nil { 96 return err 97 } 98 99 // Set metrics. 100 msg.meterSize = size 101 if metrics.Enabled() && msg.meterCap.Name != "" { // don't meter non-subprotocol messages 102 m := fmt.Sprintf("%s/%s/%d/%#02x", egressMeterName, msg.meterCap.Name, msg.meterCap.Version, msg.meterCode) 103 metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize)) 104 metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1) 105 } 106 return nil 107 } 108 109 func (t *rlpxTransport) close(err error) { 110 t.wmu.Lock() 111 defer t.wmu.Unlock() 112 113 // Tell the remote end why we're disconnecting if possible. 114 // We only bother doing this if the underlying connection supports 115 // setting a timeout tough. 116 if reason, ok := err.(DiscReason); ok && reason != DiscNetworkError { 117 // We do not use the WriteMsg func since we want a custom deadline 118 deadline := time.Now().Add(discWriteTimeout) 119 if err := t.conn.SetWriteDeadline(deadline); err == nil { 120 // Connection supports write deadline. 121 t.wbuf.Reset() 122 rlp.Encode(&t.wbuf, []any{reason}) 123 t.conn.Write(discMsg, t.wbuf.Bytes()) 124 } 125 } 126 t.conn.Close() 127 } 128 129 func (t *rlpxTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) { 130 t.conn.SetDeadline(time.Now().Add(handshakeTimeout)) 131 return t.conn.Handshake(prv) 132 } 133 134 func (t *rlpxTransport) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) { 135 // Writing our handshake happens concurrently, we prefer 136 // returning the handshake read error. If the remote side 137 // disconnects us early with a valid reason, we should return it 138 // as the error so it can be tracked elsewhere. 139 werr := make(chan error, 1) 140 go func() { werr <- Send(t, handshakeMsg, our) }() 141 if their, err = readProtocolHandshake(t); err != nil { 142 <-werr // make sure the write terminates too 143 return nil, err 144 } 145 if err := <-werr; err != nil { 146 return nil, fmt.Errorf("write error: %v", err) 147 } 148 // If the protocol version supports Snappy encoding, upgrade immediately 149 t.conn.SetSnappy(their.Version >= snappyProtocolVersion) 150 151 return their, nil 152 } 153 154 func readProtocolHandshake(rw MsgReader) (*protoHandshake, error) { 155 msg, err := rw.ReadMsg() 156 if err != nil { 157 return nil, err 158 } 159 if msg.Size > baseProtocolMaxMsgSize { 160 return nil, errors.New("message too big") 161 } 162 if msg.Code == discMsg { 163 // Disconnect before protocol handshake is valid according to the 164 // spec and we send it ourself if the post-handshake checks fail. 165 r := decodeDisconnectMessage(msg.Payload) 166 return nil, r 167 } 168 if msg.Code != handshakeMsg { 169 return nil, fmt.Errorf("expected handshake, got %x", msg.Code) 170 } 171 var hs protoHandshake 172 if err := msg.Decode(&hs); err != nil { 173 return nil, err 174 } 175 if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) { 176 return nil, DiscInvalidIdentity 177 } 178 return &hs, nil 179 }