3 // Upnp code taken from Taipei Torrent license is below:
4 // Copyright (c) 2010 Jack Palevich. All rights reserved.
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are
10 // * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 // * Redistributions in binary form must reproduce the above
13 // copyright notice, this list of conditions and the following disclaimer
14 // in the documentation and/or other materials provided with the
16 // * Neither the name of Google Inc. nor the names of its
17 // contributors may be used to endorse or promote products derived from
18 // this software without specific prior written permission.
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 // Just enough UPnP to be able to forward ports
47 // NAT is an interface representing a NAT traversal options for example UPNP or
48 // NAT-PMP. It provides methods to query and manipulate this traversal to allow
49 // access to services.
51 // Get the external address from outside the NAT.
52 GetExternalAddress() (addr net.IP, err error)
53 // Add a port mapping for protocol ("udp" or "tcp") from external port to
54 // internal port with description lasting for timeout.
55 AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error)
56 // Remove a previously added port mapping from external port to
58 DeletePortMapping(protocol string, externalPort, internalPort int) (err error)
66 // Discover searches the local network for a UPnP router returning a NAT
67 // for the network if so, nil if not.
68 func Discover() (nat NAT, err error) {
69 ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
73 conn, err := net.ListenPacket("udp4", ":0")
77 socket := conn.(*net.UDPConn)
80 err = socket.SetDeadline(time.Now().Add(3 * time.Second))
85 st := "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n"
86 buf := bytes.NewBufferString(
87 "M-SEARCH * HTTP/1.1\r\n" +
88 "HOST: 239.255.255.250:1900\r\n" +
90 "MAN: \"ssdp:discover\"\r\n" +
92 message := buf.Bytes()
93 answerBytes := make([]byte, 1024)
94 for i := 0; i < 3; i++ {
95 _, err = socket.WriteToUDP(message, ssdp)
100 n, _, err = socket.ReadFromUDP(answerBytes)
106 answer := string(answerBytes[0:n])
107 if !strings.Contains(answer, "\r\n"+st) {
110 // HTTP header field names are case-insensitive.
111 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
112 locString := "\r\nlocation: "
113 locIndex := strings.Index(strings.ToLower(answer), locString)
117 loc := answer[locIndex+len(locString):]
118 endIndex := strings.Index(loc, "\r\n")
122 locURL := loc[0:endIndex]
123 var serviceURL string
124 serviceURL, err = getServiceURL(locURL)
129 ourIP, err = getOurIP()
133 nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP}
136 err = errors.New("UPnP port discovery failed")
140 // service represents the Service type in an UPnP xml description.
141 // Only the parts we care about are present and thus the xml may have more
142 // fields than present in the structure.
143 type service struct {
144 ServiceType string `xml:"serviceType"`
145 ControlURL string `xml:"controlURL"`
148 // deviceList represents the deviceList type in an UPnP xml description.
149 // Only the parts we care about are present and thus the xml may have more
150 // fields than present in the structure.
151 type deviceList struct {
152 XMLName xml.Name `xml:"deviceList"`
153 Device []device `xml:"device"`
156 // serviceList represents the serviceList type in an UPnP xml description.
157 // Only the parts we care about are present and thus the xml may have more
158 // fields than present in the structure.
159 type serviceList struct {
160 XMLName xml.Name `xml:"serviceList"`
161 Service []service `xml:"service"`
164 // device represents the device type in an UPnP xml description.
165 // Only the parts we care about are present and thus the xml may have more
166 // fields than present in the structure.
168 XMLName xml.Name `xml:"device"`
169 DeviceType string `xml:"deviceType"`
170 DeviceList deviceList `xml:"deviceList"`
171 ServiceList serviceList `xml:"serviceList"`
174 // specVersion represents the specVersion in a UPnP xml description.
175 // Only the parts we care about are present and thus the xml may have more
176 // fields than present in the structure.
177 type specVersion struct {
178 XMLName xml.Name `xml:"specVersion"`
179 Major int `xml:"major"`
180 Minor int `xml:"minor"`
183 // root represents the Root document for a UPnP xml description.
184 // Only the parts we care about are present and thus the xml may have more
185 // fields than present in the structure.
187 XMLName xml.Name `xml:"root"`
188 SpecVersion specVersion
192 // getChildDevice searches the children of device for a device with the given
194 func getChildDevice(d *device, deviceType string) *device {
195 for i := range d.DeviceList.Device {
196 if d.DeviceList.Device[i].DeviceType == deviceType {
197 return &d.DeviceList.Device[i]
203 // getChildDevice searches the service list of device for a service with the
205 func getChildService(d *device, serviceType string) *service {
206 for i := range d.ServiceList.Service {
207 if d.ServiceList.Service[i].ServiceType == serviceType {
208 return &d.ServiceList.Service[i]
214 // getOurIP returns a best guess at what the local IP is.
215 func getOurIP() (ip string, err error) {
216 hostname, err := os.Hostname()
220 return net.LookupCNAME(hostname)
223 // getServiceURL parses the xml description at the given root url to find the
224 // url for the WANIPConnection service to be used for port forwarding.
225 func getServiceURL(rootURL string) (url string, err error) {
226 r, err := http.Get(rootURL)
231 if r.StatusCode >= 400 {
232 err = errors.New(string(r.StatusCode))
236 err = xml.NewDecoder(r.Body).Decode(&root)
241 if a.DeviceType != "urn:schemas-upnp-org:device:InternetGatewayDevice:1" {
242 err = errors.New("no InternetGatewayDevice")
245 b := getChildDevice(a, "urn:schemas-upnp-org:device:WANDevice:1")
247 err = errors.New("no WANDevice")
250 c := getChildDevice(b, "urn:schemas-upnp-org:device:WANConnectionDevice:1")
252 err = errors.New("no WANConnectionDevice")
255 d := getChildService(c, "urn:schemas-upnp-org:service:WANIPConnection:1")
257 err = errors.New("no WANIPConnection")
260 url = combineURL(rootURL, d.ControlURL)
264 // combineURL appends subURL onto rootURL.
265 func combineURL(rootURL, subURL string) string {
267 protoEndIndex := strings.Index(rootURL, protocolEnd)
268 a := rootURL[protoEndIndex+len(protocolEnd):]
269 rootIndex := strings.Index(a, "/")
270 return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL
273 // soapBody represents the <s:Body> element in a SOAP reply.
274 // fields we don't care about are elided.
275 type soapBody struct {
276 XMLName xml.Name `xml:"Body"`
277 Data []byte `xml:",innerxml"`
280 // soapEnvelope represents the <s:Envelope> element in a SOAP reply.
281 // fields we don't care about are elided.
282 type soapEnvelope struct {
283 XMLName xml.Name `xml:"Envelope"`
284 Body soapBody `xml:"Body"`
287 // soapRequests performs a soap request with the given parameters and returns
288 // the xml replied stripped of the soap headers. in the case that the request is
289 // unsuccessful the an error is returned.
290 func soapRequest(url, function, message string) (replyXML []byte, err error) {
291 fullMessage := "<?xml version=\"1.0\" ?>" +
292 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
293 "<s:Body>" + message + "</s:Body></s:Envelope>"
295 req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage))
299 req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"")
300 req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3")
301 //req.Header.Set("Transfer-Encoding", "chunked")
302 req.Header.Set("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#"+function+"\"")
303 req.Header.Set("Connection", "Close")
304 req.Header.Set("Cache-Control", "no-cache")
305 req.Header.Set("Pragma", "no-cache")
307 r, err := http.DefaultClient.Do(req)
315 if r.StatusCode >= 400 {
316 // log.Stderr(function, r.StatusCode)
317 err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
321 var reply soapEnvelope
322 err = xml.NewDecoder(r.Body).Decode(&reply)
326 return reply.Body.Data, nil
329 // getExternalIPAddressResponse represents the XML response to a
330 // GetExternalIPAddress SOAP request.
331 type getExternalIPAddressResponse struct {
332 XMLName xml.Name `xml:"GetExternalIPAddressResponse"`
333 ExternalIPAddress string `xml:"NewExternalIPAddress"`
336 // GetExternalAddress implements the NAT interface by fetching the external IP
337 // from the UPnP router.
338 func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
339 message := "<u:GetExternalIPAddress xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\"/>\r\n"
340 response, err := soapRequest(n.serviceURL, "GetExternalIPAddress", message)
345 var reply getExternalIPAddressResponse
346 err = xml.Unmarshal(response, &reply)
351 addr = net.ParseIP(reply.ExternalIPAddress)
353 return nil, errors.New("unable to parse ip address")
358 // AddPortMapping implements the NAT interface by setting up a port forwarding
359 // from the UPnP router to the local machine with the given ports and protocol.
360 func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) {
361 // A single concatenation would break ARM compilation.
362 message := "<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" +
363 "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)
364 message += "</NewExternalPort><NewProtocol>" + strings.ToUpper(protocol) + "</NewProtocol>"
365 message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +
366 "<NewInternalClient>" + n.ourIP + "</NewInternalClient>" +
367 "<NewEnabled>1</NewEnabled><NewPortMappingDescription>"
368 message += description +
369 "</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +
370 "</NewLeaseDuration></u:AddPortMapping>"
372 response, err := soapRequest(n.serviceURL, "AddPortMapping", message)
377 // TODO: check response to see if the port was forwarded
378 // If the port was not wildcard we don't get an reply with the port in
379 // it. Not sure about wildcard yet. miniupnpc just checks for error
381 mappedExternalPort = externalPort
386 // DeletePortMapping implements the NAT interface by removing up a port forwarding
387 // from the UPnP router to the local machine with the given ports and.
388 func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {
390 message := "<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" +
391 "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
392 "</NewExternalPort><NewProtocol>" + strings.ToUpper(protocol) + "</NewProtocol>" +
393 "</u:DeletePortMapping>"
395 response, err := soapRequest(n.serviceURL, "DeletePortMapping", message)
400 // TODO: check response to see if the port was deleted
401 // log.Println(message, response)