github.com/MetalBlockchain/metalgo@v1.11.9/nat/upnp.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package nat 5 6 import ( 7 "fmt" 8 "math" 9 "net" 10 "net/netip" 11 "time" 12 13 "github.com/huin/goupnp" 14 "github.com/huin/goupnp/dcps/internetgateway1" 15 "github.com/huin/goupnp/dcps/internetgateway2" 16 17 "github.com/MetalBlockchain/metalgo/utils/ips" 18 ) 19 20 const ( 21 // upnpProtocol is intentionally uppercase and should not be confused with 22 // pmpProtocol. 23 // See: 24 // - https://github.com/huin/goupnp/blob/v1.0.3/dcps/internetgateway1/internetgateway1.go#L2361 25 // - https://github.com/huin/goupnp/blob/v1.0.3/dcps/internetgateway1/internetgateway1.go#L3618 26 // - https://github.com/huin/goupnp/blob/v1.0.3/dcps/internetgateway2/internetgateway2.go#L3919 27 upnpProtocol = "TCP" 28 soapRequestTimeout = 10 * time.Second 29 ) 30 31 var _ Router = (*upnpRouter)(nil) 32 33 // upnpClient is the interface used by goupnp for their client implementations 34 type upnpClient interface { 35 // attempts to map connection using the provided protocol from the external 36 // port to the internal port for the lease duration. 37 AddPortMapping( 38 newRemoteHost string, 39 newExternalPort uint16, 40 newProtocol string, 41 newInternalPort uint16, 42 newInternalClient string, 43 newEnabled bool, 44 newPortMappingDescription string, 45 newLeaseDuration uint32) error 46 47 // attempt to remove any mapping from the external port. 48 DeletePortMapping( 49 newRemoteHost string, 50 newExternalPort uint16, 51 newProtocol string) error 52 53 // attempts to return the external IP address, formatted as a string. 54 GetExternalIPAddress() (ip string, err error) 55 56 // returns if there is rsip available, nat enabled, or an unexpected error. 57 GetNATRSIPStatus() (newRSIPAvailable bool, natEnabled bool, err error) 58 59 // attempts to get port mapping information give a external port and protocol 60 GetSpecificPortMappingEntry( 61 NewRemoteHost string, 62 NewExternalPort uint16, 63 NewProtocol string, 64 ) ( 65 NewInternalPort uint16, 66 NewInternalClient string, 67 NewEnabled bool, 68 NewPortMappingDescription string, 69 NewLeaseDuration uint32, 70 err error, 71 ) 72 } 73 74 type upnpRouter struct { 75 dev *goupnp.RootDevice 76 client upnpClient 77 } 78 79 func (*upnpRouter) SupportsNAT() bool { 80 return true 81 } 82 83 func (r *upnpRouter) localIP() (net.IP, error) { 84 // attempt to get an address on the router 85 deviceAddr, err := net.ResolveUDPAddr("udp", r.dev.URLBase.Host) 86 if err != nil { 87 return nil, err 88 } 89 deviceIP := deviceAddr.IP 90 91 netInterfaces, err := net.Interfaces() 92 if err != nil { 93 return nil, err 94 } 95 96 // attempt to find one of my IPs that matches router's record 97 for _, netInterface := range netInterfaces { 98 addrs, err := netInterface.Addrs() 99 if err != nil { 100 continue 101 } 102 103 for _, addr := range addrs { 104 ipNet, ok := addr.(*net.IPNet) 105 if !ok { 106 continue 107 } 108 109 if ipNet.Contains(deviceIP) { 110 return ipNet.IP, nil 111 } 112 } 113 } 114 return nil, fmt.Errorf("couldn't find the local address in the same network as %s", deviceIP) 115 } 116 117 func (r *upnpRouter) ExternalIP() (netip.Addr, error) { 118 str, err := r.client.GetExternalIPAddress() 119 if err != nil { 120 return netip.Addr{}, err 121 } 122 return ips.ParseAddr(str) 123 } 124 125 func (r *upnpRouter) MapPort( 126 intPort, 127 extPort uint16, 128 desc string, 129 duration time.Duration, 130 ) error { 131 ip, err := r.localIP() 132 if err != nil { 133 return err 134 } 135 lifetime := duration.Seconds() 136 if lifetime < 0 || lifetime > math.MaxUint32 { 137 return errInvalidLifetime 138 } 139 140 return r.client.AddPortMapping("", extPort, upnpProtocol, intPort, 141 ip.String(), true, desc, uint32(lifetime)) 142 } 143 144 func (r *upnpRouter) UnmapPort(_, extPort uint16) error { 145 return r.client.DeletePortMapping("", extPort, upnpProtocol) 146 } 147 148 // create UPnP SOAP service client with URN 149 func getUPnPClient(client goupnp.ServiceClient) upnpClient { 150 switch client.Service.ServiceType { 151 case internetgateway1.URN_WANIPConnection_1: 152 return &internetgateway1.WANIPConnection1{ServiceClient: client} 153 case internetgateway1.URN_WANPPPConnection_1: 154 return &internetgateway1.WANPPPConnection1{ServiceClient: client} 155 case internetgateway2.URN_WANIPConnection_2: 156 return &internetgateway2.WANIPConnection2{ServiceClient: client} 157 default: 158 return nil 159 } 160 } 161 162 // discover() tries to find gateway device 163 func discover(target string) *upnpRouter { 164 devs, err := goupnp.DiscoverDevices(target) 165 if err != nil { 166 return nil 167 } 168 169 router := make(chan *upnpRouter) 170 for i := 0; i < len(devs); i++ { 171 if devs[i].Root == nil { 172 continue 173 } 174 go func(dev *goupnp.MaybeRootDevice) { 175 var r *upnpRouter 176 dev.Root.Device.VisitServices(func(service *goupnp.Service) { 177 c := goupnp.ServiceClient{ 178 SOAPClient: service.NewSOAPClient(), 179 RootDevice: dev.Root, 180 Location: dev.Location, 181 Service: service, 182 } 183 c.SOAPClient.HTTPClient.Timeout = soapRequestTimeout 184 client := getUPnPClient(c) 185 if client == nil { 186 return 187 } 188 if _, nat, err := client.GetNATRSIPStatus(); err != nil || !nat { 189 return 190 } 191 newRouter := &upnpRouter{ 192 dev: dev.Root, 193 client: client, 194 } 195 if _, err := newRouter.localIP(); err != nil { 196 return 197 } 198 r = newRouter 199 }) 200 router <- r 201 }(&devs[i]) 202 } 203 204 for i := 0; i < len(devs); i++ { 205 if r := <-router; r != nil { 206 return r 207 } 208 } 209 210 return nil 211 } 212 213 // getUPnPRouter searches for internet gateway using both Device Control Protocol 214 // and returns the first one it can find. It returns nil if no UPnP gateway is found 215 func getUPnPRouter() *upnpRouter { 216 targets := []string{ 217 internetgateway1.URN_WANConnectionDevice_1, 218 internetgateway2.URN_WANConnectionDevice_2, 219 } 220 221 routers := make(chan *upnpRouter) 222 223 for _, urn := range targets { 224 go func(urn string) { 225 routers <- discover(urn) 226 }(urn) 227 } 228 229 for i := 0; i < len(targets); i++ { 230 if r := <-routers; r != nil { 231 return r 232 } 233 } 234 235 return nil 236 }