iptrack.go 4.0 KB

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