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