OSDN Git Service

Add skeleton size validity check (#354)
[bytom/vapor.git] / p2p / upnp / upnp.go
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(string(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 }