aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorobscuren <geffobscura@gmail.com>2014-02-02 04:30:54 +0800
committerobscuren <geffobscura@gmail.com>2014-02-02 04:30:54 +0800
commitdfa778fed684e97f868aab9b246646156a39e24a (patch)
treef04df963b3ccc5dbd300d73b6f72a947029f54d5
parent8c4746a3dfed68603612bb0d702fe1f3aca1e26f (diff)
downloadgo-tangerine-dfa778fed684e97f868aab9b246646156a39e24a.tar
go-tangerine-dfa778fed684e97f868aab9b246646156a39e24a.tar.gz
go-tangerine-dfa778fed684e97f868aab9b246646156a39e24a.tar.bz2
go-tangerine-dfa778fed684e97f868aab9b246646156a39e24a.tar.lz
go-tangerine-dfa778fed684e97f868aab9b246646156a39e24a.tar.xz
go-tangerine-dfa778fed684e97f868aab9b246646156a39e24a.tar.zst
go-tangerine-dfa778fed684e97f868aab9b246646156a39e24a.zip
UPNP wip
-rw-r--r--ethereum.go64
-rw-r--r--peer.go45
-rw-r--r--upnp.go405
3 files changed, 484 insertions, 30 deletions
diff --git a/ethereum.go b/ethereum.go
index 0137f68de..b192b544d 100644
--- a/ethereum.go
+++ b/ethereum.go
@@ -8,6 +8,7 @@ import (
"github.com/ethereum/ethwire-go"
"log"
"net"
+ "strconv"
"sync/atomic"
"time"
)
@@ -40,6 +41,10 @@ type Ethereum struct {
peers *list.List
// Nonce
Nonce uint64
+
+ Addr net.Addr
+
+ nat NAT
}
func New() (*Ethereum, error) {
@@ -51,12 +56,20 @@ func New() (*Ethereum, error) {
ethutil.Config.Db = db
+ /*
+ nat, err := Discover()
+ if err != nil {
+ log.Printf("Can'them discover upnp: %v", err)
+ }
+ */
+
nonce, _ := ethutil.RandomUint64()
ethereum := &Ethereum{
shutdownChan: make(chan bool),
db: db,
peers: list.New(),
Nonce: nonce,
+ //nat: nat,
}
ethereum.TxPool = ethchain.NewTxPool()
ethereum.TxPool.Speaker = ethereum
@@ -179,18 +192,59 @@ func (s *Ethereum) ReapDeadPeers() {
}
}
+// FIXME
+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.Printf("can't add UPnP port mapping: %v\n", err)
+ }
+ if first && err == nil {
+ externalip, err := s.nat.GetExternalAddress()
+ if err != nil {
+ log.Printf("UPnP can't get external address: %v\n", err)
+ continue out
+ }
+ // externalip, listenport
+ log.Println("Successfully bound via UPnP to", externalip, listenPort)
+ first = false
+ }
+ timer.Reset(time.Minute * 15)
+ case <-s.shutdownChan:
+ break out
+ }
+ }
+
+ timer.Stop()
+
+ if err := s.nat.DeletePortMapping("tcp", int(lport), int(lport)); err != nil {
+ log.Printf("unable to remove UPnP port mapping: %v\n", err)
+ } else {
+ log.Printf("succesfully disestablished UPnP port mapping\n")
+ }
+}
+
// Start the ethereum
func (s *Ethereum) Start() {
// Bind to addr and port
ln, err := net.Listen("tcp", ":30303")
if err != nil {
// This is mainly for testing to create a "network"
- if ethutil.Config.Debug {
- log.Println("Connection listening disabled. Acting as client")
- } else {
- log.Fatal(err)
- }
+ //if ethutil.Config.Debug {
+ //log.Println("Connection listening disabled. Acting as client")
+ //} else {
+ log.Fatal(err)
+ //}
} else {
+ s.Addr = ln.Addr()
// Starting accepting connections
go func() {
log.Println("Ready and accepting connections")
diff --git a/peer.go b/peer.go
index c91df79db..5d22b545c 100644
--- a/peer.go
+++ b/peer.go
@@ -253,22 +253,21 @@ out:
case ethwire.MsgPeersTy:
// Received a list of peers (probably because MsgGetPeersTy was send)
// Only act on message if we actually requested for a peers list
- if p.requestedPeerList {
- data := msg.Data
- // Create new list of possible peers for the ethereum to process
- peers := make([]string, data.Length())
- // Parse each possible peer
- for i := 0; i < data.Length(); i++ {
- peers[i] = unpackAddr(data.Get(i).Get(0).AsBytes(), data.Get(i).Get(1).AsUint())
- log.Println(peers[i])
- }
+ //if p.requestedPeerList {
+ data := msg.Data
+ // Create new list of possible peers for the ethereum to process
+ peers := make([]string, data.Length())
+ // Parse each possible peer
+ for i := 0; i < data.Length(); i++ {
+ peers[i] = unpackAddr(data.Get(i).Get(0), data.Get(i).Get(1).AsUint())
+ }
- // Connect to the list of peers
- p.ethereum.ProcessPeerList(peers)
- // Mark unrequested again
- p.requestedPeerList = false
+ // Connect to the list of peers
+ p.ethereum.ProcessPeerList(peers)
+ // Mark unrequested again
+ p.requestedPeerList = false
- }
+ //}
case ethwire.MsgGetChainTy:
var parent *ethchain.Block
// Length minus one since the very last element in the array is a count
@@ -326,15 +325,11 @@ func packAddr(address, port string) ([]byte, uint16) {
return host, uint16(prt)
}
-func unpackAddr(h []byte, p uint64) string {
- if len(h) != 4 {
- return ""
- }
-
- a := strconv.Itoa(int(h[0]))
- b := strconv.Itoa(int(h[1]))
- c := strconv.Itoa(int(h[2]))
- d := strconv.Itoa(int(h[3]))
+func unpackAddr(value *ethutil.RlpValue, p uint64) string {
+ a := strconv.Itoa(int(value.Get(0).AsUint()))
+ b := strconv.Itoa(int(value.Get(1).AsUint()))
+ c := strconv.Itoa(int(value.Get(2).AsUint()))
+ d := strconv.Itoa(int(value.Get(3).AsUint()))
host := strings.Join([]string{a, b, c, d}, ".")
port := strconv.Itoa(int(p))
@@ -349,9 +344,9 @@ func (p *Peer) Start(seed bool) {
if peerHost == servHost {
log.Println("Connected to self")
- //p.Stop()
+ p.Stop()
- //return
+ return
}
if p.inbound {
diff --git a/upnp.go b/upnp.go
new file mode 100644
index 000000000..c84ed04f8
--- /dev/null
+++ b/upnp.go
@@ -0,0 +1,405 @@
+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
+//
+
+import (
+ "bytes"
+ "encoding/xml"
+ "errors"
+ "net"
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+ "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 {
+ return
+ }
+ conn, err := net.ListenPacket("udp4", ":0")
+ if err != nil {
+ return
+ }
+ socket := conn.(*net.UDPConn)
+ defer socket.Close()
+
+ err = socket.SetDeadline(time.Now().Add(3 * time.Second))
+ if err != nil {
+ return
+ }
+
+ st := "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n"
+ buf := bytes.NewBufferString(
+ "M-SEARCH * HTTP/1.1\r\n" +
+ "HOST: 239.255.255.250:1900\r\n" +
+ st +
+ "MAN: \"ssdp:discover\"\r\n" +
+ "MX: 2\r\n\r\n")
+ message := buf.Bytes()
+ answerBytes := make([]byte, 1024)
+ for i := 0; i < 3; i++ {
+ _, err = socket.WriteToUDP(message, ssdp)
+ if err != nil {
+ return
+ }
+ var n int
+ n, _, err = socket.ReadFromUDP(answerBytes)
+ if err != nil {
+ continue
+ // socket.Close()
+ // return
+ }
+ answer := string(answerBytes[0:n])
+ if strings.Index(answer, "\r\n"+st) < 0 {
+ continue
+ }
+ // HTTP header field names are case-insensitive.
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
+ locString := "\r\nlocation: "
+ answer = strings.ToLower(answer)
+ locIndex := strings.Index(answer, locString)
+ if locIndex < 0 {
+ continue
+ }
+ loc := answer[locIndex+len(locString):]
+ endIndex := strings.Index(loc, "\r\n")
+ if endIndex < 0 {
+ continue
+ }
+ locURL := loc[0:endIndex]
+ var serviceURL string
+ serviceURL, err = getServiceURL(locURL)
+ if err != nil {
+ return
+ }
+ var ourIP string
+ ourIP, err = getOurIP()
+ if err != nil {
+ return
+ }
+ nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP}
+ return
+ }
+ err = errors.New("UPnP port discovery failed")
+ return
+}
+
+// service represents the Service type in an UPnP xml description.
+// Only the parts we care about are present and thus the xml may have more
+// fields than present in the structure.
+type service struct {
+ ServiceType string `xml:"serviceType"`
+ ControlURL string `xml:"controlURL"`
+}
+
+// deviceList represents the deviceList type in an UPnP xml description.
+// Only the parts we care about are present and thus the xml may have more
+// fields than present in the structure.
+type deviceList struct {
+ XMLName xml.Name `xml:"deviceList"`
+ Device []device `xml:"device"`
+}
+
+// serviceList represents the serviceList type in an UPnP xml description.
+// Only the parts we care about are present and thus the xml may have more
+// fields than present in the structure.
+type serviceList struct {
+ XMLName xml.Name `xml:"serviceList"`
+ Service []service `xml:"service"`
+}
+
+// device represents the device type in an UPnP xml description.
+// Only the parts we care about are present and thus the xml may have more
+// fields than present in the structure.
+type device struct {
+ XMLName xml.Name `xml:"device"`
+ DeviceType string `xml:"deviceType"`
+ DeviceList deviceList `xml:"deviceList"`
+ ServiceList serviceList `xml:"serviceList"`
+}
+
+// specVersion represents the specVersion in a UPnP xml description.
+// Only the parts we care about are present and thus the xml may have more
+// fields than present in the structure.
+type specVersion struct {
+ XMLName xml.Name `xml:"specVersion"`
+ Major int `xml:"major"`
+ Minor int `xml:"minor"`
+}
+
+// root represents the Root document for a UPnP xml description.
+// Only the parts we care about are present and thus the xml may have more
+// fields than present in the structure.
+type root struct {
+ XMLName xml.Name `xml:"root"`
+ SpecVersion specVersion
+ 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]
+ }
+ }
+ 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]
+ }
+ }
+ 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)
+}
+
+// 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 {
+ return
+ }
+ defer r.Body.Close()
+ if r.StatusCode >= 400 {
+ err = errors.New(string(r.StatusCode))
+ return
+ }
+ 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")
+ return
+ }
+ b := getChildDevice(a, "urn:schemas-upnp-org:device:WANDevice:1")
+ if b == nil {
+ err = errors.New("no WANDevice")
+ return
+ }
+ c := getChildDevice(b, "urn:schemas-upnp-org:device:WANConnectionDevice:1")
+ if c == nil {
+ err = errors.New("no WANConnectionDevice")
+ return
+ }
+ d := getChildService(c, "urn:schemas-upnp-org:service:WANIPConnection:1")
+ if d == nil {
+ 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)
+ a := rootURL[protoEndIndex+len(protocolEnd):]
+ rootIndex := strings.Index(a, "/")
+ 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) {
+ 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>"
+
+ req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"")
+ req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3")
+ //req.Header.Set("Transfer-Encoding", "chunked")
+ req.Header.Set("SOAPAction", "\"urn:schemas-upnp-org:service:WANIPConnection:1#"+function+"\"")
+ req.Header.Set("Connection", "Close")
+ 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
+ }
+ if r.Body != nil {
+ defer r.Body.Close()
+ }
+
+ if r.StatusCode >= 400 {
+ // log.Stderr(function, r.StatusCode)
+ err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
+ r = nil
+ return
+ }
+ var reply soapEnvelope
+ err = xml.NewDecoder(r.Body).Decode(&reply)
+ if err != nil {
+ return nil, err
+ }
+ return reply.Body.Data, nil
+}
+
+// getExternalIPAddressResponse represents the XML response to a
+// GetExternalIPAddress SOAP request.
+type getExternalIPAddressResponse struct {
+ XMLName xml.Name `xml:"GetExternalIPAddressResponse"`
+ ExternalIPAddress string `xml:"NewExternalIPAddress"`
+}
+
+// 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
+ }
+
+ var reply getExternalIPAddressResponse
+ err = xml.Unmarshal(response, &reply)
+ if err != nil {
+ return nil, err
+ }
+
+ addr = net.ParseIP(reply.ExternalIPAddress)
+ if addr == nil {
+ return nil, errors.New("unable to parse ip address")
+ }
+ return addr, nil
+}
+
+// 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" +
+ "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)
+ message += "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>"
+ message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +
+ "<NewInternalClient>" + n.ourIP + "</NewInternalClient>" +
+ "<NewEnabled>1</NewEnabled><NewPortMappingDescription>"
+ message += description +
+ "</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +
+ "</NewLeaseDuration></u:AddPortMapping>"
+
+ 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.
+ 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" +
+ "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
+ "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" +
+ "</u:DeletePortMapping>"
+
+ response, err := soapRequest(n.serviceURL, "DeletePortMapping", message)
+ if err != nil {
+ return
+ }
+
+ // TODO: check response to see if the port was deleted
+ // log.Println(message, response)
+ _ = response
+ return
+}