github.com/number571/tendermint@v0.34.11-gost/internal/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( 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 303 message := "<u:GetExternalIPAddress xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" + 304 "</u:GetExternalIPAddress>" 305 306 var response *http.Response 307 response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain) 308 if response != nil { 309 defer response.Body.Close() 310 } 311 if err != nil { 312 return 313 } 314 var envelope Envelope 315 data, err := ioutil.ReadAll(response.Body) 316 if err != nil { 317 return 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 info, err 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 return 346 } 347 348 func (n *upnpNAT) AddPortMapping( 349 protocol string, 350 externalPort, 351 internalPort int, 352 description string, 353 timeout int) (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 := ioutil.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 386 message := "<u:DeletePortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" + 387 "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) + 388 "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" + 389 "</u:DeletePortMapping>" 390 391 var response *http.Response 392 response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain) 393 if response != nil { 394 defer response.Body.Close() 395 } 396 if err != nil { 397 return 398 } 399 400 // TODO: check response to see if the port was deleted 401 // log.Println(message, response) 402 _ = response 403 return 404 }