github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/p2p/netutil/iptrack.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // Copyright 2019 The go-aigar Authors 3 // This file is part of the go-aigar library. 4 // 5 // The go-aigar library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-aigar library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>. 17 18 package netutil 19 20 import ( 21 "time" 22 23 "github.com/AigarNetwork/aigar/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[string]ipStatement 34 contact map[string]mclock.AbsTime 35 lastStatementGC mclock.AbsTime 36 lastContactGC mclock.AbsTime 37 } 38 39 type ipStatement struct { 40 endpoint string 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[string]ipStatement), 56 minStatements: minStatements, 57 contact: make(map[string]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() string { 79 it.gcStatements(it.clock.Now()) 80 81 // The current strategy is simple: find the endpoint with most statements. 82 counts := make(map[string]int) 83 maxcount, max := 0, "" 84 for _, s := range it.statements { 85 c := counts[s.endpoint] + 1 86 counts[s.endpoint] = c 87 if c > maxcount && c >= it.minStatements { 88 maxcount, max = c, s.endpoint 89 } 90 } 91 return max 92 } 93 94 // AddStatement records that a certain host thinks our external endpoint is the one given. 95 func (it *IPTracker) AddStatement(host, endpoint string) { 96 now := it.clock.Now() 97 it.statements[host] = ipStatement{endpoint, now} 98 if time.Duration(now-it.lastStatementGC) >= it.window { 99 it.gcStatements(now) 100 } 101 } 102 103 // AddContact records that a packet containing our endpoint information has been sent to a 104 // certain host. 105 func (it *IPTracker) AddContact(host string) { 106 now := it.clock.Now() 107 it.contact[host] = now 108 if time.Duration(now-it.lastContactGC) >= it.contactWindow { 109 it.gcContact(now) 110 } 111 } 112 113 func (it *IPTracker) gcStatements(now mclock.AbsTime) { 114 it.lastStatementGC = now 115 cutoff := now.Add(-it.window) 116 for host, s := range it.statements { 117 if s.time < cutoff { 118 delete(it.statements, host) 119 } 120 } 121 } 122 123 func (it *IPTracker) gcContact(now mclock.AbsTime) { 124 it.lastContactGC = now 125 cutoff := now.Add(-it.contactWindow) 126 for host, ct := range it.contact { 127 if ct < cutoff { 128 delete(it.contact, host) 129 } 130 } 131 }