github.com/klaytn/klaytn@v1.12.1/networks/p2p/nat/natupnp.go (about) 1 // Modifications Copyright 2018 The klaytn Authors 2 // Copyright 2015 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum 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-ethereum 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-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from p2p/nat/natupnp.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package nat 22 23 import ( 24 "errors" 25 "fmt" 26 "net" 27 "strings" 28 "time" 29 30 "github.com/huin/goupnp" 31 "github.com/huin/goupnp/dcps/internetgateway1" 32 "github.com/huin/goupnp/dcps/internetgateway2" 33 ) 34 35 const soapRequestTimeout = 3 * time.Second 36 37 type upnp struct { 38 dev *goupnp.RootDevice 39 service string 40 client upnpClient 41 } 42 43 type upnpClient interface { 44 GetExternalIPAddress() (string, error) 45 AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error 46 DeletePortMapping(string, uint16, string) error 47 GetNATRSIPStatus() (sip bool, nat bool, err error) 48 } 49 50 func (n *upnp) ExternalIP() (addr net.IP, err error) { 51 ipString, err := n.client.GetExternalIPAddress() 52 if err != nil { 53 return nil, err 54 } 55 ip := net.ParseIP(ipString) 56 if ip == nil { 57 return nil, errors.New("bad IP in response") 58 } 59 return ip, nil 60 } 61 62 func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) error { 63 ip, err := n.internalAddress() 64 if err != nil { 65 return nil 66 } 67 protocol = strings.ToUpper(protocol) 68 lifetimeS := uint32(lifetime / time.Second) 69 n.DeleteMapping(protocol, extport, intport) 70 return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) 71 } 72 73 func (n *upnp) internalAddress() (net.IP, error) { 74 devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host) 75 if err != nil { 76 return nil, err 77 } 78 ifaces, err := net.Interfaces() 79 if err != nil { 80 return nil, err 81 } 82 for _, iface := range ifaces { 83 addrs, err := iface.Addrs() 84 if err != nil { 85 return nil, err 86 } 87 for _, addr := range addrs { 88 switch x := addr.(type) { 89 case *net.IPNet: 90 if x.Contains(devaddr.IP) { 91 return x.IP, nil 92 } 93 } 94 } 95 } 96 return nil, fmt.Errorf("could not find local address in same net as %v", devaddr) 97 } 98 99 func (n *upnp) DeleteMapping(protocol string, extport, intport int) error { 100 return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol)) 101 } 102 103 func (n *upnp) String() string { 104 return "UPNP " + n.service 105 } 106 107 // discoverUPnP searches for Internet Gateway Devices 108 // and returns the first one it can find on the local network. 109 func discoverUPnP() Interface { 110 found := make(chan *upnp, 2) 111 // IGDv1 112 go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp { 113 switch sc.Service.ServiceType { 114 case internetgateway1.URN_WANIPConnection_1: 115 return &upnp{dev, "IGDv1-IP1", &internetgateway1.WANIPConnection1{ServiceClient: sc}} 116 case internetgateway1.URN_WANPPPConnection_1: 117 return &upnp{dev, "IGDv1-PPP1", &internetgateway1.WANPPPConnection1{ServiceClient: sc}} 118 } 119 return nil 120 }) 121 // IGDv2 122 go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp { 123 switch sc.Service.ServiceType { 124 case internetgateway2.URN_WANIPConnection_1: 125 return &upnp{dev, "IGDv2-IP1", &internetgateway2.WANIPConnection1{ServiceClient: sc}} 126 case internetgateway2.URN_WANIPConnection_2: 127 return &upnp{dev, "IGDv2-IP2", &internetgateway2.WANIPConnection2{ServiceClient: sc}} 128 case internetgateway2.URN_WANPPPConnection_1: 129 return &upnp{dev, "IGDv2-PPP1", &internetgateway2.WANPPPConnection1{ServiceClient: sc}} 130 } 131 return nil 132 }) 133 for i := 0; i < cap(found); i++ { 134 if c := <-found; c != nil { 135 return c 136 } 137 } 138 return nil 139 } 140 141 // finds devices matching the given target and calls matcher for all 142 // advertised services of each device. The first non-nil service found 143 // is sent into out. If no service matched, nil is sent. 144 func discover(out chan<- *upnp, target string, matcher func(*goupnp.RootDevice, goupnp.ServiceClient) *upnp) { 145 devs, err := goupnp.DiscoverDevices(target) 146 if err != nil { 147 out <- nil 148 return 149 } 150 found := false 151 for i := 0; i < len(devs) && !found; i++ { 152 if devs[i].Root == nil { 153 continue 154 } 155 devs[i].Root.Device.VisitServices(func(service *goupnp.Service) { 156 if found { 157 return 158 } 159 // check for a matching IGD service 160 sc := goupnp.ServiceClient{ 161 SOAPClient: service.NewSOAPClient(), 162 RootDevice: devs[i].Root, 163 Location: devs[i].Location, 164 Service: service, 165 } 166 sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout 167 upnp := matcher(devs[i].Root, sc) 168 if upnp == nil { 169 return 170 } 171 // check whether port mapping is enabled 172 if _, nat, err := upnp.client.GetNATRSIPStatus(); err != nil || !nat { 173 return 174 } 175 out <- upnp 176 found = true 177 }) 178 } 179 if !found { 180 out <- nil 181 } 182 }