github.com/ethereum/go-ethereum@v1.16.1/p2p/server_nat.go (about) 1 // Copyright 2023 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 p2p 18 19 import ( 20 "net" 21 "time" 22 23 "github.com/ethereum/go-ethereum/common/mclock" 24 "github.com/ethereum/go-ethereum/log" 25 "github.com/ethereum/go-ethereum/p2p/enr" 26 "github.com/ethereum/go-ethereum/p2p/nat" 27 ) 28 29 const ( 30 portMapDuration = 10 * time.Minute 31 portMapRefreshInterval = 8 * time.Minute 32 portMapRetryInterval = 5 * time.Minute 33 extipRetryInterval = 2 * time.Minute 34 maxRetries = 5 // max number of failed attempts to refresh the mapping 35 ) 36 37 type portMapping struct { 38 protocol string 39 name string 40 port int 41 retries int // number of failed attempts to refresh the mapping 42 43 // for use by the portMappingLoop goroutine: 44 extPort int // the mapped port returned by the NAT interface 45 nextTime mclock.AbsTime 46 } 47 48 // setupPortMapping starts the port mapping loop if necessary. 49 // Note: this needs to be called after the LocalNode instance has been set on the server. 50 func (srv *Server) setupPortMapping() { 51 // portMappingRegister will receive up to two values: one for the TCP port if 52 // listening is enabled, and one more for enabling UDP port mapping if discovery is 53 // enabled. We make it buffered to avoid blocking setup while a mapping request is in 54 // progress. 55 srv.portMappingRegister = make(chan *portMapping, 2) 56 57 switch srv.NAT.(type) { 58 case nil: 59 // No NAT interface configured. 60 srv.loopWG.Add(1) 61 go srv.consumePortMappingRequests() 62 63 case nat.ExtIP: 64 // ExtIP doesn't block, set the IP right away. 65 ip, _ := srv.NAT.ExternalIP() 66 srv.localnode.SetStaticIP(ip) 67 srv.loopWG.Add(1) 68 go srv.consumePortMappingRequests() 69 70 default: 71 srv.loopWG.Add(1) 72 go srv.portMappingLoop() 73 } 74 } 75 76 func (srv *Server) consumePortMappingRequests() { 77 defer srv.loopWG.Done() 78 for { 79 select { 80 case <-srv.quit: 81 return 82 case <-srv.portMappingRegister: 83 } 84 } 85 } 86 87 // portMappingLoop manages port mappings for UDP and TCP. 88 func (srv *Server) portMappingLoop() { 89 defer srv.loopWG.Done() 90 91 newLogger := func(p string, e int, i int) log.Logger { 92 return log.New("proto", p, "extport", e, "intport", i, "interface", srv.NAT) 93 } 94 95 var ( 96 mappings = make(map[string]*portMapping, 2) 97 refresh = mclock.NewAlarm(srv.clock) 98 extip = mclock.NewAlarm(srv.clock) 99 lastExtIP net.IP 100 ) 101 extip.Schedule(srv.clock.Now()) 102 defer func() { 103 refresh.Stop() 104 extip.Stop() 105 for _, m := range mappings { 106 if m.extPort != 0 { 107 log := newLogger(m.protocol, m.extPort, m.port) 108 log.Debug("Deleting port mapping") 109 srv.NAT.DeleteMapping(m.protocol, m.extPort, m.port) 110 } 111 } 112 }() 113 114 for { 115 // Schedule refresh of existing mappings. 116 for _, m := range mappings { 117 refresh.Schedule(m.nextTime) 118 } 119 120 select { 121 case <-srv.quit: 122 return 123 124 case <-extip.C(): 125 extip.Schedule(srv.clock.Now().Add(extipRetryInterval)) 126 ip, err := srv.NAT.ExternalIP() 127 if err != nil { 128 log.Debug("Couldn't get external IP", "err", err, "interface", srv.NAT) 129 } else if !ip.Equal(lastExtIP) { 130 log.Debug("External IP changed", "ip", ip, "interface", srv.NAT) 131 } else { 132 continue 133 } 134 // Here, we either failed to get the external IP, or it has changed. 135 lastExtIP = ip 136 srv.localnode.SetStaticIP(ip) 137 // Ensure port mappings are refreshed in case we have moved to a new network. 138 for _, m := range mappings { 139 m.nextTime = srv.clock.Now() 140 } 141 142 case m := <-srv.portMappingRegister: 143 if m.protocol != "TCP" && m.protocol != "UDP" { 144 panic("unknown NAT protocol name: " + m.protocol) 145 } 146 mappings[m.protocol] = m 147 m.nextTime = srv.clock.Now() 148 149 case <-refresh.C(): 150 for _, m := range mappings { 151 if srv.clock.Now() < m.nextTime { 152 continue 153 } 154 155 log := newLogger(m.protocol, m.extPort, m.port) 156 log.Trace("Attempting port mapping") 157 p, err := srv.NAT.AddMapping(m.protocol, m.extPort, m.port, m.name, portMapDuration) 158 if err != nil { 159 // Failed to add or refresh port mapping. 160 if m.extPort == 0 { 161 log.Debug("Couldn't add port mapping", "err", err) 162 } else { 163 // Failed refresh. Since UPnP implementation are often buggy, 164 // and lifetime is larger than the retry interval, this does not 165 // mean we lost our existing mapping. We do not reset the external 166 // port, as it is still our best chance, but we do retry soon. 167 // We could check the error code, but UPnP implementations are buggy. 168 log.Debug("Couldn't refresh port mapping", "err", err) 169 m.retries++ 170 if m.retries > maxRetries { 171 m.retries = 0 172 err := srv.NAT.DeleteMapping(m.protocol, m.extPort, m.port) 173 log.Debug("Couldn't refresh port mapping, trying to delete it:", "err", err) 174 m.extPort = 0 175 } 176 } 177 m.nextTime = srv.clock.Now().Add(portMapRetryInterval) 178 // Note ENR is not updated here, i.e. we keep the last port. 179 continue 180 } 181 182 // It was mapped! 183 m.retries = 0 184 log = newLogger(m.protocol, int(p), m.port) 185 if int(p) != m.extPort { 186 m.extPort = int(p) 187 if m.port != m.extPort { 188 log.Info("NAT mapped alternative port") 189 } else { 190 log.Info("NAT mapped port") 191 } 192 193 // Update port in local ENR. 194 switch m.protocol { 195 case "TCP": 196 srv.localnode.Set(enr.TCP(m.extPort)) 197 case "UDP": 198 srv.localnode.SetFallbackUDP(m.extPort) 199 } 200 } 201 m.nextTime = srv.clock.Now().Add(portMapRefreshInterval) 202 } 203 } 204 } 205 }