good morning!!!!

Skip to content
Snippets Groups Projects
Commit 3f503ffc authored by Jeffrey Wilcke's avatar Jeffrey Wilcke
Browse files

Implemented support for UPnP

parent ae0d4eb7
No related branches found
No related tags found
No related merge requests found
......@@ -8,6 +8,7 @@ import (
"github.com/ethereum/ethwire-go"
"log"
"net"
"strconv"
"sync"
"sync/atomic"
"time"
......@@ -29,6 +30,7 @@ const (
type Ethereum struct {
// Channel for shutting down the ethereum
shutdownChan chan bool
quit chan bool
// DB interface
//db *ethdb.LDBDatabase
db *ethdb.MemDatabase
......@@ -48,6 +50,8 @@ type Ethereum struct {
// Capabilities for outgoing peers
serverCaps Caps
nat NAT
}
func New(caps Caps) (*Ethereum, error) {
......@@ -57,15 +61,30 @@ func New(caps Caps) (*Ethereum, error) {
return nil, err
}
/*
gateway := net.ParseIP("192.168.192.1")
nat := NewNatPMP(gateway)
port, err := nat.AddPortMapping("tcp", 30303, 30303, "", 60)
log.Println(port, err)
*/
nat, err := Discover()
if err != nil {
log.Println("UPnP failed", err)
return nil, err
}
ethutil.Config.Db = db
nonce, _ := ethutil.RandomUint64()
ethereum := &Ethereum{
shutdownChan: make(chan bool),
quit: make(chan bool),
db: db,
peers: list.New(),
Nonce: nonce,
serverCaps: caps,
nat: nat,
}
ethereum.TxPool = ethchain.NewTxPool()
ethereum.TxPool.Speaker = ethereum
......@@ -217,6 +236,8 @@ func (s *Ethereum) Start() {
go s.peerHandler(ln)
}
go s.upnpUpdateThread()
// Start the reaping processes
go s.ReapDeadPeerHandler()
......@@ -245,6 +266,8 @@ func (s *Ethereum) Stop() {
p.Stop()
})
close(s.quit)
s.shutdownChan <- true
s.TxPool.Stop()
......@@ -254,3 +277,42 @@ func (s *Ethereum) Stop() {
func (s *Ethereum) WaitForShutdown() {
<-s.shutdownChan
}
func (s *Ethereum) upnpUpdateThread() {
// Go off immediately to prevent code duplication, thereafter we renew
// lease every 15 minutes.
timer := time.NewTimer(0 * time.Second)
lport, _ := strconv.ParseInt("30303", 10, 16)
first := true
out:
for {
select {
case <-timer.C:
listenPort, err := s.nat.AddPortMapping("TCP", int(lport), int(lport), "eth listen port", 20*60)
if err != nil {
log.Println("can't add UPnP port mapping:", err)
break out
}
if first && err == nil {
externalip, err := s.nat.GetExternalAddress()
if err != nil {
log.Println("UPnP can't get external address:", err)
continue out
}
log.Println("Successfully bound via UPnP to", externalip, listenPort)
first = false
}
timer.Reset(time.Minute * 15)
case <-s.quit:
break out
}
}
timer.Stop()
if err := s.nat.DeletePortMapping("TCP", int(lport), int(lport)); err != nil {
log.Println("unable to remove UPnP port mapping:", err)
} else {
log.Println("succesfully disestablished UPnP port mapping")
}
}
nat.go 0 → 100644
package eth
import (
"net"
)
// protocol is either "udp" or "tcp"
type NAT interface {
GetExternalAddress() (addr net.IP, err error)
AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error)
DeletePortMapping(protocol string, externalPort, internalPort int) (err error)
}
package eth
import (
natpmp "code.google.com/p/go-nat-pmp"
"fmt"
"net"
)
// Adapt the NAT-PMP protocol to the NAT interface
// TODO:
// + Register for changes to the external address.
// + Re-register port mapping when router reboots.
// + A mechanism for keeping a port mapping registered.
type natPMPClient struct {
client *natpmp.Client
}
func NewNatPMP(gateway net.IP) (nat NAT) {
return &natPMPClient{natpmp.NewClient(gateway)}
}
func (n *natPMPClient) GetExternalAddress() (addr net.IP, err error) {
response, err := n.client.GetExternalAddress()
if err != nil {
return
}
ip := response.ExternalIPAddress
addr = net.IPv4(ip[0], ip[1], ip[2], ip[3])
return
}
func (n *natPMPClient) AddPortMapping(protocol string, externalPort, internalPort int,
description string, timeout int) (mappedExternalPort int, err error) {
if timeout <= 0 {
err = fmt.Errorf("timeout must not be <= 0")
return
}
// Note order of port arguments is switched between our AddPortMapping and the client's AddPortMapping.
response, err := n.client.AddPortMapping(protocol, internalPort, externalPort, timeout)
if err != nil {
return
}
mappedExternalPort = int(response.MappedExternalPort)
return
}
func (n *natPMPClient) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {
// To destroy a mapping, send an add-port with
// an internalPort of the internal port to destroy, an external port of zero and a time of zero.
_, err = n.client.AddPortMapping(protocol, internalPort, 0, 0)
return
}
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
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment