github.com/ethereum/go-ethereum@v1.16.1/p2p/nat/natupnp.go (about) 1 // Copyright 2015 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 nat 18 19 import ( 20 "errors" 21 "fmt" 22 "math" 23 "math/rand" 24 "net" 25 "strings" 26 "sync" 27 "time" 28 29 "github.com/ethereum/go-ethereum/log" 30 "github.com/huin/goupnp" 31 "github.com/huin/goupnp/dcps/internetgateway1" 32 "github.com/huin/goupnp/dcps/internetgateway2" 33 ) 34 35 const ( 36 soapRequestTimeout = 3 * time.Second 37 rateLimit = 200 * time.Millisecond 38 retryCount = 3 // number of retries after a failed AddPortMapping 39 randomCount = 3 // number of random ports to try 40 ) 41 42 type upnp struct { 43 dev *goupnp.RootDevice 44 service string 45 client upnpClient 46 mu sync.Mutex 47 lastReqTime time.Time 48 rand *rand.Rand 49 } 50 51 type upnpClient interface { 52 GetExternalIPAddress() (string, error) 53 AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error 54 DeletePortMapping(string, uint16, string) error 55 GetNATRSIPStatus() (sip bool, nat bool, err error) 56 } 57 58 func (n *upnp) natEnabled() bool { 59 var ok bool 60 var err error 61 n.withRateLimit(func() error { 62 _, ok, err = n.client.GetNATRSIPStatus() 63 return err 64 }) 65 return err == nil && ok 66 } 67 68 func (n *upnp) ExternalIP() (addr net.IP, err error) { 69 var ipString string 70 n.withRateLimit(func() error { 71 ipString, err = n.client.GetExternalIPAddress() 72 return err 73 }) 74 75 if err != nil { 76 return nil, err 77 } 78 ip := net.ParseIP(ipString) 79 if ip == nil { 80 return nil, errors.New("bad IP in response") 81 } 82 return ip, nil 83 } 84 85 func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) (uint16, error) { 86 ip, err := n.internalAddress() 87 if err != nil { 88 return 0, err 89 } 90 protocol = strings.ToUpper(protocol) 91 lifetimeS := uint32(lifetime / time.Second) 92 93 if extport == 0 { 94 extport = intport 95 } 96 97 // Try to add port mapping, preferring the specified external port. 98 return n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS) 99 } 100 101 // addAnyPortMapping tries to add a port mapping with the specified external port. 102 // If the external port is already in use, it will try to assign another port. 103 func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) { 104 if client, ok := n.client.(*internetgateway2.WANIPConnection2); ok { 105 return n.portWithRateLimit(func() (uint16, error) { 106 return client.AddAnyPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) 107 }) 108 } 109 // For IGDv1 and v1 services we should first try to add with extport. 110 for i := 0; i < retryCount+1; i++ { 111 err := n.withRateLimit(func() error { 112 return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) 113 }) 114 if err == nil { 115 return uint16(extport), nil 116 } 117 log.Debug("Failed to add port mapping", "protocol", protocol, "extport", extport, "intport", intport, "err", err) 118 } 119 120 // If above fails, we retry with a random port. 121 // We retry several times because of possible port conflicts. 122 var err error 123 for i := 0; i < randomCount; i++ { 124 extport = n.randomPort() 125 err := n.withRateLimit(func() error { 126 return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) 127 }) 128 if err == nil { 129 return uint16(extport), nil 130 } 131 log.Debug("Failed to add random port mapping", "protocol", protocol, "extport", extport, "intport", intport, "err", err) 132 } 133 return 0, err 134 } 135 136 func (n *upnp) randomPort() int { 137 if n.rand == nil { 138 n.rand = rand.New(rand.NewSource(time.Now().UnixNano())) 139 } 140 return n.rand.Intn(math.MaxUint16-10000) + 10000 141 } 142 143 func (n *upnp) internalAddress() (net.IP, error) { 144 devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host) 145 if err != nil { 146 return nil, err 147 } 148 ifaces, err := net.Interfaces() 149 if err != nil { 150 return nil, err 151 } 152 for _, iface := range ifaces { 153 addrs, err := iface.Addrs() 154 if err != nil { 155 return nil, err 156 } 157 for _, addr := range addrs { 158 if x, ok := addr.(*net.IPNet); ok && x.Contains(devaddr.IP) { 159 return x.IP, nil 160 } 161 } 162 } 163 return nil, fmt.Errorf("could not find local address in same net as %v", devaddr) 164 } 165 166 func (n *upnp) DeleteMapping(protocol string, extport, intport int) error { 167 return n.withRateLimit(func() error { 168 return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol)) 169 }) 170 } 171 172 func (n *upnp) String() string { 173 return "UPNP " + n.service 174 } 175 176 func (n *upnp) portWithRateLimit(pfn func() (uint16, error)) (uint16, error) { 177 var port uint16 178 var err error 179 fn := func() error { 180 port, err = pfn() 181 return err 182 } 183 n.withRateLimit(fn) 184 return port, err 185 } 186 187 func (n *upnp) withRateLimit(fn func() error) error { 188 n.mu.Lock() 189 defer n.mu.Unlock() 190 191 lastreq := time.Since(n.lastReqTime) 192 if lastreq < rateLimit { 193 time.Sleep(rateLimit - lastreq) 194 } 195 err := fn() 196 n.lastReqTime = time.Now() 197 return err 198 } 199 200 // discoverUPnP searches for Internet Gateway Devices 201 // and returns the first one it can find on the local network. 202 func discoverUPnP() Interface { 203 found := make(chan *upnp, 2) 204 // IGDv1 205 go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(sc goupnp.ServiceClient) *upnp { 206 switch sc.Service.ServiceType { 207 case internetgateway1.URN_WANIPConnection_1: 208 return &upnp{service: "IGDv1-IP1", client: &internetgateway1.WANIPConnection1{ServiceClient: sc}} 209 case internetgateway1.URN_WANPPPConnection_1: 210 return &upnp{service: "IGDv1-PPP1", client: &internetgateway1.WANPPPConnection1{ServiceClient: sc}} 211 } 212 return nil 213 }) 214 // IGDv2 215 go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(sc goupnp.ServiceClient) *upnp { 216 switch sc.Service.ServiceType { 217 case internetgateway2.URN_WANIPConnection_1: 218 return &upnp{service: "IGDv2-IP1", client: &internetgateway2.WANIPConnection1{ServiceClient: sc}} 219 case internetgateway2.URN_WANIPConnection_2: 220 return &upnp{service: "IGDv2-IP2", client: &internetgateway2.WANIPConnection2{ServiceClient: sc}} 221 case internetgateway2.URN_WANPPPConnection_1: 222 return &upnp{service: "IGDv2-PPP1", client: &internetgateway2.WANPPPConnection1{ServiceClient: sc}} 223 } 224 return nil 225 }) 226 for i := 0; i < cap(found); i++ { 227 if c := <-found; c != nil { 228 return c 229 } 230 } 231 return nil 232 } 233 234 // discover finds devices matching the given target and calls matcher for 235 // all advertised services of each device. The first non-nil service found 236 // is sent into out. If no service matched, nil is sent. 237 func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient) *upnp) { 238 devs, err := goupnp.DiscoverDevices(target) 239 if err != nil { 240 out <- nil 241 return 242 } 243 found := false 244 for i := 0; i < len(devs) && !found; i++ { 245 if devs[i].Root == nil { 246 continue 247 } 248 devs[i].Root.Device.VisitServices(func(service *goupnp.Service) { 249 if found { 250 return 251 } 252 // check for a matching IGD service 253 sc := goupnp.ServiceClient{ 254 SOAPClient: service.NewSOAPClient(), 255 RootDevice: devs[i].Root, 256 Location: devs[i].Location, 257 Service: service, 258 } 259 sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout 260 upnp := matcher(sc) 261 if upnp == nil { 262 return 263 } 264 upnp.dev = devs[i].Root 265 266 // check whether port mapping is enabled 267 if upnp.natEnabled() { 268 out <- upnp 269 found = true 270 } 271 }) 272 } 273 if !found { 274 out <- nil 275 } 276 }