github.com/jimmyx0x/go-ethereum@v1.10.28/p2p/discover/v5wire/session.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 v5wire 18 19 import ( 20 "crypto/ecdsa" 21 crand "crypto/rand" 22 "encoding/binary" 23 "time" 24 25 "github.com/ethereum/go-ethereum/common/lru" 26 "github.com/ethereum/go-ethereum/common/mclock" 27 "github.com/ethereum/go-ethereum/crypto" 28 "github.com/ethereum/go-ethereum/p2p/enode" 29 ) 30 31 const handshakeTimeout = time.Second 32 33 // The SessionCache keeps negotiated encryption keys and 34 // state for in-progress handshakes in the Discovery v5 wire protocol. 35 type SessionCache struct { 36 sessions lru.BasicLRU[sessionID, *session] 37 handshakes map[sessionID]*Whoareyou 38 clock mclock.Clock 39 40 // hooks for overriding randomness. 41 nonceGen func(uint32) (Nonce, error) 42 maskingIVGen func([]byte) error 43 ephemeralKeyGen func() (*ecdsa.PrivateKey, error) 44 } 45 46 // sessionID identifies a session or handshake. 47 type sessionID struct { 48 id enode.ID 49 addr string 50 } 51 52 // session contains session information 53 type session struct { 54 writeKey []byte 55 readKey []byte 56 nonceCounter uint32 57 } 58 59 // keysFlipped returns a copy of s with the read and write keys flipped. 60 func (s *session) keysFlipped() *session { 61 return &session{s.readKey, s.writeKey, s.nonceCounter} 62 } 63 64 func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache { 65 return &SessionCache{ 66 sessions: lru.NewBasicLRU[sessionID, *session](maxItems), 67 handshakes: make(map[sessionID]*Whoareyou), 68 clock: clock, 69 nonceGen: generateNonce, 70 maskingIVGen: generateMaskingIV, 71 ephemeralKeyGen: crypto.GenerateKey, 72 } 73 } 74 75 func generateNonce(counter uint32) (n Nonce, err error) { 76 binary.BigEndian.PutUint32(n[:4], counter) 77 _, err = crand.Read(n[4:]) 78 return n, err 79 } 80 81 func generateMaskingIV(buf []byte) error { 82 _, err := crand.Read(buf) 83 return err 84 } 85 86 // nextNonce creates a nonce for encrypting a message to the given session. 87 func (sc *SessionCache) nextNonce(s *session) (Nonce, error) { 88 s.nonceCounter++ 89 return sc.nonceGen(s.nonceCounter) 90 } 91 92 // session returns the current session for the given node, if any. 93 func (sc *SessionCache) session(id enode.ID, addr string) *session { 94 item, _ := sc.sessions.Get(sessionID{id, addr}) 95 return item 96 } 97 98 // readKey returns the current read key for the given node. 99 func (sc *SessionCache) readKey(id enode.ID, addr string) []byte { 100 if s := sc.session(id, addr); s != nil { 101 return s.readKey 102 } 103 return nil 104 } 105 106 // storeNewSession stores new encryption keys in the cache. 107 func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session) { 108 sc.sessions.Add(sessionID{id, addr}, s) 109 } 110 111 // getHandshake gets the handshake challenge we previously sent to the given remote node. 112 func (sc *SessionCache) getHandshake(id enode.ID, addr string) *Whoareyou { 113 return sc.handshakes[sessionID{id, addr}] 114 } 115 116 // storeSentHandshake stores the handshake challenge sent to the given remote node. 117 func (sc *SessionCache) storeSentHandshake(id enode.ID, addr string, challenge *Whoareyou) { 118 challenge.sent = sc.clock.Now() 119 sc.handshakes[sessionID{id, addr}] = challenge 120 } 121 122 // deleteHandshake deletes handshake data for the given node. 123 func (sc *SessionCache) deleteHandshake(id enode.ID, addr string) { 124 delete(sc.handshakes, sessionID{id, addr}) 125 } 126 127 // handshakeGC deletes timed-out handshakes. 128 func (sc *SessionCache) handshakeGC() { 129 deadline := sc.clock.Now().Add(-handshakeTimeout) 130 for key, challenge := range sc.handshakes { 131 if challenge.sent < deadline { 132 delete(sc.handshakes, key) 133 } 134 } 135 }