Commit 3f503ffc authored by obscuren's avatar obscuren

Implemented support for UPnP

parent ae0d4eb7
......@@ -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")
}
}
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
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment