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