github.com/vipernet-xyz/tm@v0.34.24/p2p/upnp/upnp.go (about) 1 // Taken from taipei-torrent. 2 // Just enough UPnP to be able to forward ports 3 // For more information, see: http://www.upnp-hacks.org/upnp.html 4 package upnp 5 6 // TODO: use syscalls to get actual ourIP, see issue #712 7 8 import ( 9 "bytes" 10 "encoding/xml" 11 "errors" 12 "fmt" 13 "io" 14 "net" 15 "net/http" 16 "strconv" 17 "strings" 18 "time" 19 ) 20 21 type upnpNAT struct { 22 serviceURL string 23 ourIP string 24 urnDomain string 25 } 26 27 // protocol is either "udp" or "tcp" 28 type NAT interface { 29 GetExternalAddress() (addr net.IP, err error) 30 AddPortMapping( 31 protocol string, 32 externalPort, 33 internalPort int, 34 description string, 35 timeout int) (mappedExternalPort int, err error) 36 DeletePortMapping(protocol string, externalPort, internalPort int) (err error) 37 } 38 39 func Discover() (nat NAT, err error) { 40 ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900") 41 if err != nil { 42 return 43 } 44 conn, err := net.ListenPacket("udp4", ":0") 45 if err != nil { 46 return 47 } 48 socket := conn.(*net.UDPConn) 49 defer socket.Close() 50 51 if err := socket.SetDeadline(time.Now().Add(3 * time.Second)); err != nil { 52 return nil, err 53 } 54 55 st := "InternetGatewayDevice:1" 56 57 buf := bytes.NewBufferString( 58 "M-SEARCH * HTTP/1.1\r\n" + 59 "HOST: 239.255.255.250:1900\r\n" + 60 "ST: ssdp:all\r\n" + 61 "MAN: \"ssdp:discover\"\r\n" + 62 "MX: 2\r\n\r\n") 63 message := buf.Bytes() 64 answerBytes := make([]byte, 1024) 65 for i := 0; i < 3; i++ { 66 _, err = socket.WriteToUDP(message, ssdp) 67 if err != nil { 68 return 69 } 70 var n int 71 _, _, err = socket.ReadFromUDP(answerBytes) 72 if err != nil { 73 return 74 } 75 for { 76 n, _, err = socket.ReadFromUDP(answerBytes) 77 if err != nil { 78 break 79 } 80 answer := string(answerBytes[0:n]) 81 if !strings.Contains(answer, st) { 82 continue 83 } 84 // HTTP header field names are case-insensitive. 85 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 86 locString := "\r\nlocation:" 87 answer = strings.ToLower(answer) 88 locIndex := strings.Index(answer, locString) 89 if locIndex < 0 { 90 continue 91 } 92 loc := answer[locIndex+len(locString):] 93 endIndex := strings.Index(loc, "\r\n") 94 if endIndex < 0 { 95 continue 96 } 97 locURL := strings.TrimSpace(loc[0:endIndex]) 98 var serviceURL, urnDomain string 99 serviceURL, urnDomain, err = getServiceURL(locURL) 100 if err != nil { 101 return 102 } 103 var ourIP net.IP 104 ourIP, err = localIPv4() 105 if err != nil { 106 return 107 } 108 nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP.String(), urnDomain: urnDomain} 109 return 110 } 111 } 112 err = errors.New("upnp port discovery failed") 113 return nat, err 114 } 115 116 type Envelope struct { 117 XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` 118 Soap *SoapBody 119 } 120 type SoapBody struct { 121 XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` 122 ExternalIP *ExternalIPAddressResponse 123 } 124 125 type ExternalIPAddressResponse struct { 126 XMLName xml.Name `xml:"GetExternalIPAddressResponse"` 127 IPAddress string `xml:"NewExternalIPAddress"` 128 } 129 130 type ExternalIPAddress struct { 131 XMLName xml.Name `xml:"NewExternalIPAddress"` 132 IP string 133 } 134 135 type Service struct { 136 ServiceType string `xml:"serviceType"` 137 ControlURL string `xml:"controlURL"` 138 } 139 140 type DeviceList struct { 141 Device []Device `xml:"device"` 142 } 143 144 type ServiceList struct { 145 Service []Service `xml:"service"` 146 } 147 148 type Device struct { 149 XMLName xml.Name `xml:"device"` 150 DeviceType string `xml:"deviceType"` 151 DeviceList DeviceList `xml:"deviceList"` 152 ServiceList ServiceList `xml:"serviceList"` 153 } 154 155 type Root struct { 156 Device Device 157 } 158 159 func getChildDevice(d *Device, deviceType string) *Device { 160 dl := d.DeviceList.Device 161 for i := 0; i < len(dl); i++ { 162 if strings.Contains(dl[i].DeviceType, deviceType) { 163 return &dl[i] 164 } 165 } 166 return nil 167 } 168 169 func getChildService(d *Device, serviceType string) *Service { 170 sl := d.ServiceList.Service 171 for i := 0; i < len(sl); i++ { 172 if strings.Contains(sl[i].ServiceType, serviceType) { 173 return &sl[i] 174 } 175 } 176 return nil 177 } 178 179 func localIPv4() (net.IP, error) { 180 tt, err := net.Interfaces() 181 if err != nil { 182 return nil, err 183 } 184 for _, t := range tt { 185 aa, err := t.Addrs() 186 if err != nil { 187 return nil, err 188 } 189 for _, a := range aa { 190 ipnet, ok := a.(*net.IPNet) 191 if !ok { 192 continue 193 } 194 v4 := ipnet.IP.To4() 195 if v4 == nil || v4[0] == 127 { // loopback address 196 continue 197 } 198 return v4, nil 199 } 200 } 201 return nil, errors.New("cannot find local IP address") 202 } 203 204 func getServiceURL(rootURL string) (url, urnDomain string, err error) { 205 r, err := http.Get(rootURL) //nolint: gosec 206 if err != nil { 207 return 208 } 209 defer r.Body.Close() 210 211 if r.StatusCode >= 400 { 212 err = errors.New(string(rune(r.StatusCode))) 213 return 214 } 215 var root Root 216 err = xml.NewDecoder(r.Body).Decode(&root) 217 if err != nil { 218 return 219 } 220 a := &root.Device 221 if !strings.Contains(a.DeviceType, "InternetGatewayDevice:1") { 222 err = errors.New("no InternetGatewayDevice") 223 return 224 } 225 b := getChildDevice(a, "WANDevice:1") 226 if b == nil { 227 err = errors.New("no WANDevice") 228 return 229 } 230 c := getChildDevice(b, "WANConnectionDevice:1") 231 if c == nil { 232 err = errors.New("no WANConnectionDevice") 233 return 234 } 235 d := getChildService(c, "WANIPConnection:1") 236 if d == nil { 237 // Some routers don't follow the UPnP spec, and put WanIPConnection under WanDevice, 238 // instead of under WanConnectionDevice 239 d = getChildService(b, "WANIPConnection:1") 240 241 if d == nil { 242 err = errors.New("no WANIPConnection") 243 return 244 } 245 } 246 // Extract the domain name, which isn't always 'schemas-upnp-org' 247 urnDomain = strings.Split(d.ServiceType, ":")[1] 248 url = combineURL(rootURL, d.ControlURL) 249 return url, urnDomain, err 250 } 251 252 func combineURL(rootURL, subURL string) string { 253 protocolEnd := "://" 254 protoEndIndex := strings.Index(rootURL, protocolEnd) 255 a := rootURL[protoEndIndex+len(protocolEnd):] 256 rootIndex := strings.Index(a, "/") 257 return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL 258 } 259 260 func soapRequest(url, function, message, domain string) (r *http.Response, err error) { 261 fullMessage := "<?xml version=\"1.0\" ?>" + 262 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " + 263 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" + 264 "<s:Body>" + message + "</s:Body></s:Envelope>" 265 266 req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage)) 267 if err != nil { 268 return nil, err 269 } 270 req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"") 271 req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3") 272 // req.Header.Set("Transfer-Encoding", "chunked") 273 req.Header.Set("SOAPAction", "\"urn:"+domain+":service:WANIPConnection:1#"+function+"\"") 274 req.Header.Set("Connection", "Close") 275 req.Header.Set("Cache-Control", "no-cache") 276 req.Header.Set("Pragma", "no-cache") 277 278 // log.Stderr("soapRequest ", req) 279 280 r, err = http.DefaultClient.Do(req) 281 if err != nil { 282 return nil, err 283 } 284 /*if r.Body != nil { 285 defer r.Body.Close() 286 }*/ 287 288 if r.StatusCode >= 400 { 289 // log.Stderr(function, r.StatusCode) 290 err = errors.New("error " + strconv.Itoa(r.StatusCode) + " for " + function) 291 r = nil 292 return 293 } 294 return r, err 295 } 296 297 type statusInfo struct { 298 externalIPAddress string 299 } 300 301 func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) { 302 message := "<u:GetExternalIPAddress xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" + 303 "</u:GetExternalIPAddress>" 304 305 var response *http.Response 306 response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain) 307 if response != nil { 308 defer response.Body.Close() 309 } 310 if err != nil { 311 return 312 } 313 var envelope Envelope 314 data, err := io.ReadAll(response.Body) 315 if err != nil { 316 return 317 } 318 reader := bytes.NewReader(data) 319 err = xml.NewDecoder(reader).Decode(&envelope) 320 if err != nil { 321 return 322 } 323 324 info = statusInfo{envelope.Soap.ExternalIP.IPAddress} 325 326 if err != nil { 327 return 328 } 329 330 return info, err 331 } 332 333 // GetExternalAddress returns an external IP. If GetExternalIPAddress action 334 // fails or IP returned is invalid, GetExternalAddress returns an error. 335 func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) { 336 info, err := n.getExternalIPAddress() 337 if err != nil { 338 return 339 } 340 addr = net.ParseIP(info.externalIPAddress) 341 if addr == nil { 342 err = fmt.Errorf("failed to parse IP: %v", info.externalIPAddress) 343 } 344 return 345 } 346 347 func (n *upnpNAT) AddPortMapping( 348 protocol string, 349 externalPort, 350 internalPort int, 351 description string, 352 timeout int, 353 ) (mappedExternalPort int, err error) { 354 // A single concatenation would break ARM compilation. 355 message := "<u:AddPortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" + 356 "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) 357 message += "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" 358 message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" + 359 "<NewInternalClient>" + n.ourIP + "</NewInternalClient>" + 360 "<NewEnabled>1</NewEnabled><NewPortMappingDescription>" 361 message += description + 362 "</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) + 363 "</NewLeaseDuration></u:AddPortMapping>" 364 365 var response *http.Response 366 response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain) 367 if response != nil { 368 defer response.Body.Close() 369 } 370 if err != nil { 371 return 372 } 373 374 // TODO: check response to see if the port was forwarded 375 // log.Println(message, response) 376 // JAE: 377 // body, err := io.ReadAll(response.Body) 378 // fmt.Println(string(body), err) 379 mappedExternalPort = externalPort 380 _ = response 381 return mappedExternalPort, err 382 } 383 384 func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) { 385 message := "<u:DeletePortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" + 386 "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) + 387 "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" + 388 "</u:DeletePortMapping>" 389 390 var response *http.Response 391 response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain) 392 if response != nil { 393 defer response.Body.Close() 394 } 395 if err != nil { 396 return 397 } 398 399 // TODO: check response to see if the port was deleted 400 // log.Println(message, response) 401 _ = response 402 return 403 }