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