github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/p2p/nat/natupnp.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // Copyright 2019 The go-aigar Authors 3 // This file is part of the go-aigar library. 4 // 5 // The go-aigar library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-aigar library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>. 17 18 package nat 19 20 import ( 21 "errors" 22 "fmt" 23 "net" 24 "strings" 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 soapRequestTimeout = 3 * time.Second 33 34 type upnp struct { 35 dev *goupnp.RootDevice 36 service string 37 client upnpClient 38 } 39 40 type upnpClient interface { 41 GetExternalIPAddress() (string, error) 42 AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error 43 DeletePortMapping(string, uint16, string) error 44 GetNATRSIPStatus() (sip bool, nat bool, err error) 45 } 46 47 func (n *upnp) ExternalIP() (addr net.IP, err error) { 48 ipString, err := n.client.GetExternalIPAddress() 49 if err != nil { 50 return nil, err 51 } 52 ip := net.ParseIP(ipString) 53 if ip == nil { 54 return nil, errors.New("bad IP in response") 55 } 56 return ip, nil 57 } 58 59 func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) error { 60 ip, err := n.internalAddress() 61 if err != nil { 62 return nil 63 } 64 protocol = strings.ToUpper(protocol) 65 lifetimeS := uint32(lifetime / time.Second) 66 n.DeleteMapping(protocol, extport, intport) 67 return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) 68 } 69 70 func (n *upnp) internalAddress() (net.IP, error) { 71 devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host) 72 if err != nil { 73 return nil, err 74 } 75 ifaces, err := net.Interfaces() 76 if err != nil { 77 return nil, err 78 } 79 for _, iface := range ifaces { 80 addrs, err := iface.Addrs() 81 if err != nil { 82 return nil, err 83 } 84 for _, addr := range addrs { 85 if x, ok := addr.(*net.IPNet); ok && x.Contains(devaddr.IP) { 86 return x.IP, nil 87 } 88 } 89 } 90 return nil, fmt.Errorf("could not find local address in same net as %v", devaddr) 91 } 92 93 func (n *upnp) DeleteMapping(protocol string, extport, intport int) error { 94 return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol)) 95 } 96 97 func (n *upnp) String() string { 98 return "UPNP " + n.service 99 } 100 101 // discoverUPnP searches for Internet Gateway Devices 102 // and returns the first one it can find on the local network. 103 func discoverUPnP() Interface { 104 found := make(chan *upnp, 2) 105 // IGDv1 106 go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp { 107 switch sc.Service.ServiceType { 108 case internetgateway1.URN_WANIPConnection_1: 109 return &upnp{dev, "IGDv1-IP1", &internetgateway1.WANIPConnection1{ServiceClient: sc}} 110 case internetgateway1.URN_WANPPPConnection_1: 111 return &upnp{dev, "IGDv1-PPP1", &internetgateway1.WANPPPConnection1{ServiceClient: sc}} 112 } 113 return nil 114 }) 115 // IGDv2 116 go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp { 117 switch sc.Service.ServiceType { 118 case internetgateway2.URN_WANIPConnection_1: 119 return &upnp{dev, "IGDv2-IP1", &internetgateway2.WANIPConnection1{ServiceClient: sc}} 120 case internetgateway2.URN_WANIPConnection_2: 121 return &upnp{dev, "IGDv2-IP2", &internetgateway2.WANIPConnection2{ServiceClient: sc}} 122 case internetgateway2.URN_WANPPPConnection_1: 123 return &upnp{dev, "IGDv2-PPP1", &internetgateway2.WANPPPConnection1{ServiceClient: sc}} 124 } 125 return nil 126 }) 127 for i := 0; i < cap(found); i++ { 128 if c := <-found; c != nil { 129 return c 130 } 131 } 132 return nil 133 } 134 135 // finds devices matching the given target and calls matcher for all 136 // advertised services of each device. The first non-nil service found 137 // is sent into out. If no service matched, nil is sent. 138 func discover(out chan<- *upnp, target string, matcher func(*goupnp.RootDevice, goupnp.ServiceClient) *upnp) { 139 devs, err := goupnp.DiscoverDevices(target) 140 if err != nil { 141 out <- nil 142 return 143 } 144 found := false 145 for i := 0; i < len(devs) && !found; i++ { 146 if devs[i].Root == nil { 147 continue 148 } 149 devs[i].Root.Device.VisitServices(func(service *goupnp.Service) { 150 if found { 151 return 152 } 153 // check for a matching IGD service 154 sc := goupnp.ServiceClient{ 155 SOAPClient: service.NewSOAPClient(), 156 RootDevice: devs[i].Root, 157 Location: devs[i].Location, 158 Service: service, 159 } 160 sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout 161 upnp := matcher(devs[i].Root, sc) 162 if upnp == nil { 163 return 164 } 165 // check whether port mapping is enabled 166 if _, nat, err := upnp.client.GetNATRSIPStatus(); err != nil || !nat { 167 return 168 } 169 out <- upnp 170 found = true 171 }) 172 } 173 if !found { 174 out <- nil 175 } 176 }