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