diff options
author | Felix Lange <fjl@twurst.com> | 2015-02-12 00:19:31 +0800 |
---|---|---|
committer | Felix Lange <fjl@twurst.com> | 2015-02-13 18:39:31 +0800 |
commit | d0a2e655c9599f462bb20bd49bc69b8e1e330a21 (patch) | |
tree | ebd2685477461d33b80207b5eeee2f08ade33f3a /p2p | |
parent | 1543833ca0b920d38e98994367f3871867d66781 (diff) | |
download | dexon-d0a2e655c9599f462bb20bd49bc69b8e1e330a21.tar.gz dexon-d0a2e655c9599f462bb20bd49bc69b8e1e330a21.tar.zst dexon-d0a2e655c9599f462bb20bd49bc69b8e1e330a21.zip |
cmd/ethereum, cmd/mist, eth, p2p: use package p2p/nat
This deletes the old NAT implementation.
Diffstat (limited to 'p2p')
-rw-r--r-- | p2p/nat.go | 23 | ||||
-rw-r--r-- | p2p/natpmp.go | 55 | ||||
-rw-r--r-- | p2p/natupnp.go | 341 | ||||
-rw-r--r-- | p2p/server.go | 70 |
4 files changed, 11 insertions, 478 deletions
diff --git a/p2p/nat.go b/p2p/nat.go deleted file mode 100644 index 9b771c3e8..000000000 --- a/p2p/nat.go +++ /dev/null @@ -1,23 +0,0 @@ -package p2p - -import ( - "fmt" - "net" -) - -func ParseNAT(natType string, gateway string) (nat NAT, err error) { - switch natType { - case "UPNP": - nat = UPNP() - case "PMP": - ip := net.ParseIP(gateway) - if ip == nil { - return nil, fmt.Errorf("cannot resolve PMP gateway IP %s", gateway) - } - nat = PMP(ip) - case "": - default: - return nil, fmt.Errorf("unrecognised NAT type '%s'", natType) - } - return -} diff --git a/p2p/natpmp.go b/p2p/natpmp.go deleted file mode 100644 index 6714678c4..000000000 --- a/p2p/natpmp.go +++ /dev/null @@ -1,55 +0,0 @@ -package p2p - -import ( - "fmt" - "net" - "time" - - natpmp "github.com/jackpal/go-nat-pmp" -) - -// 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. -// + Discover gateway address automatically. - -type natPMPClient struct { - client *natpmp.Client -} - -// PMP returns a NAT traverser that uses NAT-PMP. The provided gateway -// address should be the IP of your router. -func PMP(gateway net.IP) (nat NAT) { - return &natPMPClient{natpmp.NewClient(gateway)} -} - -func (*natPMPClient) String() string { - return "NAT-PMP" -} - -func (n *natPMPClient) GetExternalAddress() (net.IP, error) { - response, err := n.client.GetExternalAddress() - if err != nil { - return nil, err - } - return response.ExternalIPAddress[:], nil -} - -func (n *natPMPClient) AddPortMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error { - if lifetime <= 0 { - return fmt.Errorf("lifetime must not be <= 0") - } - // Note order of port arguments is switched between our AddPortMapping and the client's AddPortMapping. - _, err := n.client.AddPortMapping(protocol, intport, extport, int(lifetime/time.Second)) - return err -} - -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 -} diff --git a/p2p/natupnp.go b/p2p/natupnp.go deleted file mode 100644 index 2e0d8ce8d..000000000 --- a/p2p/natupnp.go +++ /dev/null @@ -1,341 +0,0 @@ -package p2p - -// Just enough UPnP to be able to forward ports -// - -import ( - "bytes" - "encoding/xml" - "errors" - "fmt" - "net" - "net/http" - "os" - "strconv" - "strings" - "time" -) - -const ( - upnpDiscoverAttempts = 3 - upnpDiscoverTimeout = 5 * time.Second -) - -// UPNP returns a NAT port mapper that uses UPnP. It will attempt to -// discover the address of your router using UDP broadcasts. -func UPNP() NAT { - return &upnpNAT{} -} - -type upnpNAT struct { - serviceURL string - ourIP string -} - -func (n *upnpNAT) String() string { - return "UPNP" -} - -func (n *upnpNAT) discover() error { - if n.serviceURL != "" { - // already discovered - return nil - } - - ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900") - if err != nil { - return err - } - // TODO: try on all network interfaces simultaneously. - // Broadcasting on 0.0.0.0 could select a random interface - // to send on (platform specific). - conn, err := net.ListenPacket("udp4", ":0") - if err != nil { - return err - } - defer conn.Close() - - conn.SetDeadline(time.Now().Add(10 * time.Second)) - 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 < upnpDiscoverAttempts; i++ { - _, err = conn.WriteTo(message, ssdp) - if err != nil { - return err - } - nn, _, err := conn.ReadFrom(answerBytes) - if err != nil { - continue - } - answer := string(answerBytes[0:nn]) - 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 err - } - var ourIP string - ourIP, err = getOurIP() - if err != nil { - return err - } - n.serviceURL = serviceURL - n.ourIP = ourIP - return nil - } - return errors.New("UPnP port discovery failed.") -} - -func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) { - if err := n.discover(); err != nil { - return nil, err - } - info, err := n.getStatusInfo() - return net.ParseIP(info.externalIpAddress), err -} - -func (n *upnpNAT) AddPortMapping(protocol string, extport, intport int, description string, lifetime time.Duration) error { - if err := n.discover(); err != nil { - return err - } - - // 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(extport) - message += "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" - message += "<NewInternalPort>" + strconv.Itoa(extport) + "</NewInternalPort>" + - "<NewInternalClient>" + n.ourIP + "</NewInternalClient>" + - "<NewEnabled>1</NewEnabled><NewPortMappingDescription>" - message += description + - "</NewPortMappingDescription><NewLeaseDuration>" + fmt.Sprint(lifetime/time.Second) + - "</NewLeaseDuration></u:AddPortMapping>" - - // TODO: check response to see if the port was forwarded - _, err := soapRequest(n.serviceURL, "AddPortMapping", message) - return err -} - -func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) error { - if err := n.discover(); err != nil { - return err - } - - 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>" - - // TODO: check response to see if the port was deleted - _, err := soapRequest(n.serviceURL, "DeletePortMapping", message) - return err -} - -type statusInfo struct { - externalIpAddress string -} - -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 response *http.Response - response, err = soapRequest(n.serviceURL, "GetStatusInfo", message) - if err != nil { - return - } - - // TODO: Write a soap reply parser. It has to eat the Body and envelope tags... - - response.Body.Close() - 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 -} - -func getChildDevice(d *device, deviceType string) *device { - dl := d.DeviceList.Device - for i := 0; i < len(dl); i++ { - if dl[i].DeviceType == deviceType { - return &dl[i] - } - } - return nil -} - -func getChildService(d *device, serviceType string) *service { - sl := d.ServiceList.Service - for i := 0; i < len(sl); i++ { - if sl[i].ServiceType == serviceType { - return &sl[i] - } - } - return nil -} - -func getOurIP() (ip string, err error) { - hostname, err := os.Hostname() - if err != nil { - return - } - p, err := net.LookupIP(hostname) - if err != nil && len(p) > 0 { - return - } - return p[0].String(), nil -} - -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 -} - -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 -} - -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>" - - req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage)) - if err != nil { - return - } - 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 - } - - 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 - } - return -} diff --git a/p2p/server.go b/p2p/server.go index 3cab61102..a0f2dee23 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -13,13 +13,12 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/nat" ) const ( - defaultDialTimeout = 10 * time.Second - refreshPeersInterval = 30 * time.Second - portMappingUpdateInterval = 15 * time.Minute - portMappingTimeout = 20 * time.Minute + defaultDialTimeout = 10 * time.Second + refreshPeersInterval = 30 * time.Second ) var srvlog = logger.NewLogger("P2P Server") @@ -72,7 +71,7 @@ type Server struct { // If set to a non-nil value, the given NAT port mapper // is used to make the listening port available to the // Internet. - NAT NAT + NAT nat.Interface // If Dialer is set to a non-nil value, the given Dialer // is used to dial outbound peer connections. @@ -89,7 +88,6 @@ type Server struct { lock sync.RWMutex running bool listener net.Listener - laddr *net.TCPAddr // real listen addr peers map[discover.NodeID]*Peer ntab *discover.Table @@ -100,16 +98,6 @@ type Server struct { peerConnect chan *discover.Node } -// NAT is implemented by NAT traversal methods. -type NAT interface { - GetExternalAddress() (net.IP, error) - AddPortMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error - DeletePortMapping(protocol string, extport, intport int) error - - // Should return name of the method. - String() string -} - type handshakeFunc func(io.ReadWriter, *ecdsa.PrivateKey, *discover.Node) (discover.NodeID, []byte, error) type newPeerHook func(*Peer) @@ -220,14 +208,17 @@ func (srv *Server) startListening() error { if err != nil { return err } - srv.ListenAddr = listener.Addr().String() - srv.laddr = listener.Addr().(*net.TCPAddr) + laddr := listener.Addr().(*net.TCPAddr) + srv.ListenAddr = laddr.String() srv.listener = listener srv.loopWG.Add(1) go srv.listenLoop() - if !srv.laddr.IP.IsLoopback() && srv.NAT != nil { + if !laddr.IP.IsLoopback() && srv.NAT != nil { srv.loopWG.Add(1) - go srv.natLoop(srv.laddr.Port) + go func() { + nat.Map(srv.NAT, srv.quit, "tcp", laddr.Port, laddr.Port, "ethereum p2p") + srv.loopWG.Done() + }() } return nil } @@ -276,45 +267,6 @@ func (srv *Server) listenLoop() { } } -func (srv *Server) natLoop(port int) { - defer srv.loopWG.Done() - for { - srv.updatePortMapping(port) - select { - case <-time.After(portMappingUpdateInterval): - // one more round - case <-srv.quit: - srv.removePortMapping(port) - return - } - } -} - -func (srv *Server) updatePortMapping(port int) { - srvlog.Infoln("Attempting to map port", port, "with", srv.NAT) - err := srv.NAT.AddPortMapping("tcp", port, port, "ethereum p2p", portMappingTimeout) - if err != nil { - srvlog.Errorln("Port mapping error:", err) - return - } - extip, err := srv.NAT.GetExternalAddress() - if err != nil { - srvlog.Errorln("Error getting external IP:", err) - return - } - srv.lock.Lock() - extaddr := *(srv.listener.Addr().(*net.TCPAddr)) - extaddr.IP = extip - srvlog.Infoln("Mapped port, external addr is", &extaddr) - srv.laddr = &extaddr - srv.lock.Unlock() -} - -func (srv *Server) removePortMapping(port int) { - srvlog.Infoln("Removing port mapping for", port, "with", srv.NAT) - srv.NAT.DeletePortMapping("tcp", port, port) -} - func (srv *Server) dialLoop() { defer srv.loopWG.Done() refresh := time.NewTicker(refreshPeersInterval) |