|
|
|
@ -1,34 +1,5 @@ |
|
|
|
|
package eth |
|
|
|
|
|
|
|
|
|
// Upnp code taken from Taipei Torrent license is below:
|
|
|
|
|
// Copyright (c) 2010 Jack Palevich. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
// Redistribution and use in source and binary forms, with or without
|
|
|
|
|
// modification, are permitted provided that the following conditions are
|
|
|
|
|
// met:
|
|
|
|
|
//
|
|
|
|
|
// * Redistributions of source code must retain the above copyright
|
|
|
|
|
// notice, this list of conditions and the following disclaimer.
|
|
|
|
|
// * Redistributions in binary form must reproduce the above
|
|
|
|
|
// copyright notice, this list of conditions and the following disclaimer
|
|
|
|
|
// in the documentation and/or other materials provided with the
|
|
|
|
|
// distribution.
|
|
|
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
|
|
|
// contributors may be used to endorse or promote products derived from
|
|
|
|
|
// this software without specific prior written permission.
|
|
|
|
|
//
|
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
|
|
// Just enough UPnP to be able to forward ports
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
@ -44,27 +15,11 @@ import ( |
|
|
|
|
"time" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// NAT is an interface representing a NAT traversal options for example UPNP or
|
|
|
|
|
// NAT-PMP. It provides methods to query and manipulate this traversal to allow
|
|
|
|
|
// access to services.
|
|
|
|
|
type NAT interface { |
|
|
|
|
// Get the external address from outside the NAT.
|
|
|
|
|
GetExternalAddress() (addr net.IP, err error) |
|
|
|
|
// Add a port mapping for protocol ("udp" or "tcp") from externalport to
|
|
|
|
|
// internal port with description lasting for timeout.
|
|
|
|
|
AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) |
|
|
|
|
// Remove a previously added port mapping from externalport to
|
|
|
|
|
// internal port.
|
|
|
|
|
DeletePortMapping(protocol string, externalPort, internalPort int) (err error) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type upnpNAT struct { |
|
|
|
|
serviceURL string |
|
|
|
|
ourIP string |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Discover searches the local network for a UPnP router returning a NAT
|
|
|
|
|
// for the network if so, nil if not.
|
|
|
|
|
func Discover() (nat NAT, err error) { |
|
|
|
|
ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900") |
|
|
|
|
if err != nil { |
|
|
|
@ -77,7 +32,7 @@ func Discover() (nat NAT, err error) { |
|
|
|
|
socket := conn.(*net.UDPConn) |
|
|
|
|
defer socket.Close() |
|
|
|
|
|
|
|
|
|
err = socket.SetDeadline(time.Now().Add(3 * time.Second)) |
|
|
|
|
err = socket.SetDeadline(time.Now().Add(10 * time.Second)) |
|
|
|
|
if err != nil { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
@ -134,7 +89,7 @@ func Discover() (nat NAT, err error) { |
|
|
|
|
nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP} |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
err = errors.New("UPnP port discovery failed") |
|
|
|
|
err = errors.New("UPnP port discovery failed.") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -190,39 +145,38 @@ type root struct { |
|
|
|
|
Device device |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// getChildDevice searches the children of device for a device with the given
|
|
|
|
|
// type.
|
|
|
|
|
func getChildDevice(d *device, deviceType string) *device { |
|
|
|
|
for i := range d.DeviceList.Device { |
|
|
|
|
if d.DeviceList.Device[i].DeviceType == deviceType { |
|
|
|
|
return &d.DeviceList.Device[i] |
|
|
|
|
dl := d.DeviceList.Device |
|
|
|
|
for i := 0; i < len(dl); i++ { |
|
|
|
|
if dl[i].DeviceType == deviceType { |
|
|
|
|
return &dl[i] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// getChildDevice searches the service list of device for a service with the
|
|
|
|
|
// given type.
|
|
|
|
|
func getChildService(d *device, serviceType string) *service { |
|
|
|
|
for i := range d.ServiceList.Service { |
|
|
|
|
if d.ServiceList.Service[i].ServiceType == serviceType { |
|
|
|
|
return &d.ServiceList.Service[i] |
|
|
|
|
sl := d.ServiceList.Service |
|
|
|
|
for i := 0; i < len(sl); i++ { |
|
|
|
|
if sl[i].ServiceType == serviceType { |
|
|
|
|
return &sl[i] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// getOurIP returns a best guess at what the local IP is.
|
|
|
|
|
func getOurIP() (ip string, err error) { |
|
|
|
|
hostname, err := os.Hostname() |
|
|
|
|
if err != nil { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
return net.LookupCNAME(hostname) |
|
|
|
|
p, err := net.LookupIP(hostname) |
|
|
|
|
if err != nil && len(p) > 0 { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
return p[0].String(), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// getServiceURL parses the xml description at the given root url to find the
|
|
|
|
|
// url for the WANIPConnection service to be used for port forwarding.
|
|
|
|
|
func getServiceURL(rootURL string) (url string, err error) { |
|
|
|
|
r, err := http.Get(rootURL) |
|
|
|
|
if err != nil { |
|
|
|
@ -235,34 +189,34 @@ func getServiceURL(rootURL string) (url string, err error) { |
|
|
|
|
} |
|
|
|
|
var root root |
|
|
|
|
err = xml.NewDecoder(r.Body).Decode(&root) |
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
a := &root.Device |
|
|
|
|
if a.DeviceType != "urn:schemas-upnp-org:device:InternetGatewayDevice:1" { |
|
|
|
|
err = errors.New("no InternetGatewayDevice") |
|
|
|
|
err = errors.New("No InternetGatewayDevice") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
b := getChildDevice(a, "urn:schemas-upnp-org:device:WANDevice:1") |
|
|
|
|
if b == nil { |
|
|
|
|
err = errors.New("no WANDevice") |
|
|
|
|
err = errors.New("No WANDevice") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
c := getChildDevice(b, "urn:schemas-upnp-org:device:WANConnectionDevice:1") |
|
|
|
|
if c == nil { |
|
|
|
|
err = errors.New("no WANConnectionDevice") |
|
|
|
|
err = errors.New("No WANConnectionDevice") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
d := getChildService(c, "urn:schemas-upnp-org:service:WANIPConnection:1") |
|
|
|
|
if d == nil { |
|
|
|
|
err = errors.New("no WANIPConnection") |
|
|
|
|
err = errors.New("No WANIPConnection") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
url = combineURL(rootURL, d.ControlURL) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// combineURL appends subURL onto rootURL.
|
|
|
|
|
func combineURL(rootURL, subURL string) string { |
|
|
|
|
protocolEnd := "://" |
|
|
|
|
protoEndIndex := strings.Index(rootURL, protocolEnd) |
|
|
|
@ -271,24 +225,7 @@ func combineURL(rootURL, subURL string) string { |
|
|
|
|
return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// soapBody represents the <s:Body> element in a SOAP reply.
|
|
|
|
|
// fields we don't care about are elided.
|
|
|
|
|
type soapBody struct { |
|
|
|
|
XMLName xml.Name `xml:"Body"` |
|
|
|
|
Data []byte `xml:",innerxml"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// soapEnvelope represents the <s:Envelope> element in a SOAP reply.
|
|
|
|
|
// fields we don't care about are elided.
|
|
|
|
|
type soapEnvelope struct { |
|
|
|
|
XMLName xml.Name `xml:"Envelope"` |
|
|
|
|
Body soapBody `xml:"Body"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// soapRequests performs a soap request with the given parameters and returns
|
|
|
|
|
// the xml replied stripped of the soap headers. in the case that the request is
|
|
|
|
|
// unsuccessful the an error is returned.
|
|
|
|
|
func soapRequest(url, function, message string) (replyXML []byte, err error) { |
|
|
|
|
func soapRequest(url, function, message string) (r *http.Response, err error) { |
|
|
|
|
fullMessage := "<?xml version=\"1.0\" ?>" + |
|
|
|
|
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" + |
|
|
|
|
"<s:Body>" + message + "</s:Body></s:Envelope>" |
|
|
|
@ -305,10 +242,10 @@ func soapRequest(url, function, message string) (replyXML []byte, err error) { |
|
|
|
|
req.Header.Set("Cache-Control", "no-cache") |
|
|
|
|
req.Header.Set("Pragma", "no-cache") |
|
|
|
|
|
|
|
|
|
r, err := http.DefaultClient.Do(req) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
// log.Stderr("soapRequest ", req)
|
|
|
|
|
//fmt.Println(fullMessage)
|
|
|
|
|
|
|
|
|
|
r, err = http.DefaultClient.Do(req) |
|
|
|
|
if r.Body != nil { |
|
|
|
|
defer r.Body.Close() |
|
|
|
|
} |
|
|
|
@ -319,45 +256,39 @@ func soapRequest(url, function, message string) (replyXML []byte, err error) { |
|
|
|
|
r = nil |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
var reply soapEnvelope |
|
|
|
|
err = xml.NewDecoder(r.Body).Decode(&reply) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
return reply.Body.Data, nil |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// getExternalIPAddressResponse represents the XML response to a
|
|
|
|
|
// GetExternalIPAddress SOAP request.
|
|
|
|
|
type getExternalIPAddressResponse struct { |
|
|
|
|
XMLName xml.Name `xml:"GetExternalIPAddressResponse"` |
|
|
|
|
ExternalIPAddress string `xml:"NewExternalIPAddress"` |
|
|
|
|
type statusInfo struct { |
|
|
|
|
externalIpAddress string |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetExternalAddress implements the NAT interface by fetching the external IP
|
|
|
|
|
// from the UPnP router.
|
|
|
|
|
func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) { |
|
|
|
|
message := "<u:GetExternalIPAddress xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\"/>\r\n" |
|
|
|
|
response, err := soapRequest(n.serviceURL, "GetExternalIPAddress", message) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
func (n *upnpNAT) getStatusInfo() (info statusInfo, err error) { |
|
|
|
|
|
|
|
|
|
message := "<u:GetStatusInfo xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" + |
|
|
|
|
"</u:GetStatusInfo>" |
|
|
|
|
|
|
|
|
|
var reply getExternalIPAddressResponse |
|
|
|
|
err = xml.Unmarshal(response, &reply) |
|
|
|
|
var response *http.Response |
|
|
|
|
response, err = soapRequest(n.serviceURL, "GetStatusInfo", message) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
addr = net.ParseIP(reply.ExternalIPAddress) |
|
|
|
|
if addr == nil { |
|
|
|
|
return nil, errors.New("unable to parse ip address") |
|
|
|
|
// TODO: Write a soap reply parser. It has to eat the Body and envelope tags...
|
|
|
|
|
|
|
|
|
|
response.Body.Close() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) { |
|
|
|
|
info, err := n.getStatusInfo() |
|
|
|
|
if err != nil { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
return addr, nil |
|
|
|
|
addr = net.ParseIP(info.externalIpAddress) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AddPortMapping implements the NAT interface by setting up a port forwarding
|
|
|
|
|
// from the UPnP router to the local machine with the given ports and protocol.
|
|
|
|
|
func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) { |
|
|
|
|
// A single concatenation would break ARM compilation.
|
|
|
|
|
message := "<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" + |
|
|
|
@ -370,22 +301,19 @@ func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int |
|
|
|
|
"</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) + |
|
|
|
|
"</NewLeaseDuration></u:AddPortMapping>" |
|
|
|
|
|
|
|
|
|
response, err := soapRequest(n.serviceURL, "AddPortMapping", message) |
|
|
|
|
var response *http.Response |
|
|
|
|
response, err = soapRequest(n.serviceURL, "AddPortMapping", message) |
|
|
|
|
if err != nil { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO: check response to see if the port was forwarded
|
|
|
|
|
// If the port was not wildcard we don't get an reply with the port in
|
|
|
|
|
// it. Not sure about wildcard yet. miniupnpc just checks for error
|
|
|
|
|
// codes here.
|
|
|
|
|
// log.Println(message, response)
|
|
|
|
|
mappedExternalPort = externalPort |
|
|
|
|
_ = response |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AddPortMapping implements the NAT interface by removing up a port forwarding
|
|
|
|
|
// from the UPnP router to the local machine with the given ports and.
|
|
|
|
|
func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) { |
|
|
|
|
|
|
|
|
|
message := "<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\r\n" + |
|
|
|
@ -393,7 +321,8 @@ func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort |
|
|
|
|
"</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" + |
|
|
|
|
"</u:DeletePortMapping>" |
|
|
|
|
|
|
|
|
|
response, err := soapRequest(n.serviceURL, "DeletePortMapping", message) |
|
|
|
|
var response *http.Response |
|
|
|
|
response, err = soapRequest(n.serviceURL, "DeletePortMapping", message) |
|
|
|
|
if err != nil { |
|
|
|
|
return |
|
|
|
|
} |