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