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