github.com/ethereum/go-ethereum@v1.16.1/p2p/netutil/iptrack.go (about) 1 // Copyright 2018 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 netutil 18 19 import ( 20 "net/netip" 21 "time" 22 23 "github.com/ethereum/go-ethereum/common/mclock" 24 ) 25 26 // IPTracker predicts the external endpoint, i.e. IP address and port, of the local host 27 // based on statements made by other hosts. 28 type IPTracker struct { 29 window time.Duration 30 contactWindow time.Duration 31 minStatements int 32 clock mclock.Clock 33 statements map[netip.Addr]ipStatement 34 contact map[netip.Addr]mclock.AbsTime 35 lastStatementGC mclock.AbsTime 36 lastContactGC mclock.AbsTime 37 } 38 39 type ipStatement struct { 40 endpoint netip.AddrPort 41 time mclock.AbsTime 42 } 43 44 // NewIPTracker creates an IP tracker. 45 // 46 // The window parameters configure the amount of past network events which are kept. The 47 // minStatements parameter enforces a minimum number of statements which must be recorded 48 // before any prediction is made. Higher values for these parameters decrease 'flapping' of 49 // predictions as network conditions change. Window duration values should typically be in 50 // the range of minutes. 51 func NewIPTracker(window, contactWindow time.Duration, minStatements int) *IPTracker { 52 return &IPTracker{ 53 window: window, 54 contactWindow: contactWindow, 55 statements: make(map[netip.Addr]ipStatement), 56 minStatements: minStatements, 57 contact: make(map[netip.Addr]mclock.AbsTime), 58 clock: mclock.System{}, 59 } 60 } 61 62 // PredictFullConeNAT checks whether the local host is behind full cone NAT. It predicts by 63 // checking whether any statement has been received from a node we didn't contact before 64 // the statement was made. 65 func (it *IPTracker) PredictFullConeNAT() bool { 66 now := it.clock.Now() 67 it.gcContact(now) 68 it.gcStatements(now) 69 for host, st := range it.statements { 70 if c, ok := it.contact[host]; !ok || c > st.time { 71 return true 72 } 73 } 74 return false 75 } 76 77 // PredictEndpoint returns the current prediction of the external endpoint. 78 func (it *IPTracker) PredictEndpoint() netip.AddrPort { 79 it.gcStatements(it.clock.Now()) 80 81 // The current strategy is simple: find the endpoint with most statements. 82 var ( 83 counts = make(map[netip.AddrPort]int, len(it.statements)) 84 maxcount int 85 max netip.AddrPort 86 ) 87 for _, s := range it.statements { 88 c := counts[s.endpoint] + 1 89 counts[s.endpoint] = c 90 if c > maxcount && c >= it.minStatements { 91 maxcount, max = c, s.endpoint 92 } 93 } 94 return max 95 } 96 97 // AddStatement records that a certain host thinks our external endpoint is the one given. 98 func (it *IPTracker) AddStatement(host netip.Addr, endpoint netip.AddrPort) { 99 now := it.clock.Now() 100 it.statements[host] = ipStatement{endpoint, now} 101 if time.Duration(now-it.lastStatementGC) >= it.window { 102 it.gcStatements(now) 103 } 104 } 105 106 // AddContact records that a packet containing our endpoint information has been sent to a 107 // certain host. 108 func (it *IPTracker) AddContact(host netip.Addr) { 109 now := it.clock.Now() 110 it.contact[host] = now 111 if time.Duration(now-it.lastContactGC) >= it.contactWindow { 112 it.gcContact(now) 113 } 114 } 115 116 func (it *IPTracker) gcStatements(now mclock.AbsTime) { 117 it.lastStatementGC = now 118 cutoff := now.Add(-it.window) 119 for host, s := range it.statements { 120 if s.time < cutoff { 121 delete(it.statements, host) 122 } 123 } 124 } 125 126 func (it *IPTracker) gcContact(now mclock.AbsTime) { 127 it.lastContactGC = now 128 cutoff := now.Add(-it.contactWindow) 129 for host, ct := range it.contact { 130 if ct < cutoff { 131 delete(it.contact, host) 132 } 133 } 134 }