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 17package netutil 18 19import ( 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. 27type 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 38type 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. 50func 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. 64func (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. 77func (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. 94func (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. 104func (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 112func (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 122func (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} 131