Sfoglia il codice sorgente

本地节点设置成功

skyfffire 2 anni fa
parent
commit
abf437dd85
7 ha cambiato i file con 623 aggiunte e 12 eliminazioni
  1. 2 0
      cmd/p2p/main.go
  2. 2 1
      p2p/config.go
  3. 1 3
      p2p/enode/localnode.go
  4. 239 0
      p2p/nat/nat.go
  5. 131 0
      p2p/nat/natpmp.go
  6. 215 0
      p2p/nat/natupnp.go
  7. 33 8
      p2p/server.go

+ 2 - 0
cmd/p2p/main.go

@@ -2,6 +2,7 @@ package main
 
 import (
 	"blockchain-go/p2p"
+	"blockchain-go/p2p/nat"
 	"github.com/ethereum/go-ethereum/crypto"
 )
 
@@ -10,6 +11,7 @@ func main() {
 	config := p2p.Config{
 		PrivateKey: key,
 		Name:       "p2p",
+		NAT:        nat.Any(),
 	}
 	server := &p2p.Server{
 		Config: config,

+ 2 - 1
p2p/config.go

@@ -3,6 +3,7 @@ package p2p
 import (
 	"blockchain-go/common/mclock"
 	"blockchain-go/log"
+	"blockchain-go/p2p/nat"
 	"crypto/ecdsa"
 	"time"
 )
@@ -106,7 +107,7 @@ type Config 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.Interface `toml:",omitempty"`
+	NAT nat.Interface `toml:",omitempty"`
 
 	// If Dialer is set to a non-nil value, the given Dialer
 	// is used to dial outbound peer connections.

+ 1 - 3
p2p/enode/localnode.go

@@ -64,8 +64,7 @@ type lnEndpoint struct {
 // NewLocalNode creates a local node.
 func NewLocalNode(key *ecdsa.PrivateKey) *LocalNode {
 	ln := &LocalNode{
-		id: PubkeyToIDV4(&key.PublicKey),
-		//db:      db,
+		id:      PubkeyToIDV4(&key.PublicKey),
 		key:     key,
 		entries: make(map[string]enr.Entry),
 		endpoint4: lnEndpoint{
@@ -75,7 +74,6 @@ func NewLocalNode(key *ecdsa.PrivateKey) *LocalNode {
 			track: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements),
 		},
 	}
-	//ln.seq = db.localSeq(ln.id)
 	ln.invalidate()
 	return ln
 }

+ 239 - 0
p2p/nat/nat.go

@@ -0,0 +1,239 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// Package nat provides access to common network port mapping protocols.
+package nat
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"strings"
+	"sync"
+	"time"
+
+	"blockchain-go/common/gopool"
+	"blockchain-go/log"
+
+	natpmp "github.com/jackpal/go-nat-pmp"
+)
+
+// An implementation of nat.Interface can map local ports to ports
+// accessible from the Internet.
+type Interface interface {
+	// These methods manage a mapping between a port on the local
+	// machine to a port that can be connected to from the internet.
+	//
+	// protocol is "UDP" or "TCP". Some implementations allow setting
+	// a display name for the mapping. The mapping may be removed by
+	// the gateway when its lifetime ends.
+	AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error
+	DeleteMapping(protocol string, extport, intport int) error
+
+	// This method should return the external (Internet-facing)
+	// address of the gateway device.
+	ExternalIP() (net.IP, error)
+
+	// Should return name of the method. This is used for logging.
+	String() string
+}
+
+// Parse parses a NAT interface description.
+// The following formats are currently accepted.
+// Note that mechanism names are not case-sensitive.
+//
+//     "" or "none"         return nil
+//     "extip:77.12.33.4"   will assume the local machine is reachable on the given IP
+//     "any"                uses the first auto-detected mechanism
+//     "upnp"               uses the Universal Plug and Play protocol
+//     "pmp"                uses NAT-PMP with an auto-detected gateway address
+//     "pmp:192.168.0.1"    uses NAT-PMP with the given gateway address
+func Parse(spec string) (Interface, error) {
+	var (
+		parts = strings.SplitN(spec, ":", 2)
+		mech  = strings.ToLower(parts[0])
+		ip    net.IP
+	)
+	if len(parts) > 1 {
+		ip = net.ParseIP(parts[1])
+		if ip == nil {
+			return nil, errors.New("invalid IP address")
+		}
+	}
+	switch mech {
+	case "", "none", "off":
+		return nil, nil
+	case "any", "auto", "on":
+		return Any(), nil
+	case "extip", "ip":
+		if ip == nil {
+			return nil, errors.New("missing IP address")
+		}
+		return ExtIP(ip), nil
+	case "upnp":
+		return UPnP(), nil
+	case "pmp", "natpmp", "nat-pmp":
+		return PMP(ip), nil
+	default:
+		return nil, fmt.Errorf("unknown mechanism %q", parts[0])
+	}
+}
+
+const (
+	mapTimeout = 10 * time.Minute
+)
+
+// Map adds a port mapping on m and keeps it alive until c is closed.
+// This function is typically invoked in its own goroutine.
+func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, name string) {
+	log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m)
+	refresh := time.NewTimer(mapTimeout)
+	defer func() {
+		refresh.Stop()
+		log.Debug("Deleting port mapping")
+		m.DeleteMapping(protocol, extport, intport)
+	}()
+	if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil {
+		log.Debug("Couldn't add port mapping", "err", err)
+	} else {
+		log.Info("Mapped network port")
+	}
+	for {
+		select {
+		case _, ok := <-c:
+			if !ok {
+				return
+			}
+		case <-refresh.C:
+			log.Trace("Refreshing port mapping")
+			if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil {
+				log.Debug("Couldn't add port mapping", "err", err)
+			}
+			refresh.Reset(mapTimeout)
+		}
+	}
+}
+
+// ExtIP assumes that the local machine is reachable on the given
+// external IP address, and that any required ports were mapped manually.
+// Mapping operations will not return an error but won't actually do anything.
+type ExtIP net.IP
+
+func (n ExtIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
+func (n ExtIP) String() string              { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
+
+// These do nothing.
+
+func (ExtIP) AddMapping(string, int, int, string, time.Duration) error { return nil }
+func (ExtIP) DeleteMapping(string, int, int) error                     { return nil }
+
+// Any returns a port mapper that tries to discover any supported
+// mechanism on the local network.
+func Any() Interface {
+	// TODO: attempt to discover whether the local machine has an
+	// Internet-class address. Return ExtIP in this case.
+	return startautodisc("UPnP or NAT-PMP", func() Interface {
+		found := make(chan Interface, 2)
+		gopool.Submit(func() { found <- discoverUPnP() })
+		gopool.Submit(func() { found <- discoverPMP() })
+		for i := 0; i < cap(found); i++ {
+			if c := <-found; c != nil {
+				return c
+			}
+		}
+		return nil
+	})
+}
+
+// UPnP returns a port mapper that uses UPnP. It will attempt to
+// discover the address of your router using UDP broadcasts.
+func UPnP() Interface {
+	return startautodisc("UPnP", discoverUPnP)
+}
+
+// PMP returns a port mapper that uses NAT-PMP. The provided gateway
+// address should be the IP of your router. If the given gateway
+// address is nil, PMP will attempt to auto-discover the router.
+func PMP(gateway net.IP) Interface {
+	if gateway != nil {
+		return &pmp{gw: gateway, c: natpmp.NewClient(gateway)}
+	}
+	return startautodisc("NAT-PMP", discoverPMP)
+}
+
+// autodisc represents a port mapping mechanism that is still being
+// auto-discovered. Calls to the Interface methods on this type will
+// wait until the discovery is done and then call the method on the
+// discovered mechanism.
+//
+// This type is useful because discovery can take a while but we
+// want return an Interface value from UPnP, PMP and Auto immediately.
+type autodisc struct {
+	what string // type of interface being autodiscovered
+	once sync.Once
+	doit func() Interface
+
+	mu    sync.Mutex
+	found Interface
+}
+
+func startautodisc(what string, doit func() Interface) Interface {
+	// TODO: monitor network configuration and rerun doit when it changes.
+	return &autodisc{what: what, doit: doit}
+}
+
+func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error {
+	if err := n.wait(); err != nil {
+		return err
+	}
+	return n.found.AddMapping(protocol, extport, intport, name, lifetime)
+}
+
+func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error {
+	if err := n.wait(); err != nil {
+		return err
+	}
+	return n.found.DeleteMapping(protocol, extport, intport)
+}
+
+func (n *autodisc) ExternalIP() (net.IP, error) {
+	if err := n.wait(); err != nil {
+		return nil, err
+	}
+	return n.found.ExternalIP()
+}
+
+func (n *autodisc) String() string {
+	n.mu.Lock()
+	defer n.mu.Unlock()
+	if n.found == nil {
+		return n.what
+	}
+	return n.found.String()
+}
+
+// wait blocks until auto-discovery has been performed.
+func (n *autodisc) wait() error {
+	n.once.Do(func() {
+		n.mu.Lock()
+		n.found = n.doit()
+		n.mu.Unlock()
+	})
+	if n.found == nil {
+		return fmt.Errorf("no %s router discovered", n.what)
+	}
+	return nil
+}

+ 131 - 0
p2p/nat/natpmp.go

@@ -0,0 +1,131 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package nat
+
+import (
+	"fmt"
+	"net"
+	"strings"
+	"time"
+
+	"blockchain-go/common/gopool"
+	natpmp "github.com/jackpal/go-nat-pmp"
+)
+
+// natPMPClient adapts the NAT-PMP protocol implementation so it conforms to
+// the common interface.
+type pmp struct {
+	gw net.IP
+	c  *natpmp.Client
+}
+
+func (n *pmp) String() string {
+	return fmt.Sprintf("NAT-PMP(%v)", n.gw)
+}
+
+func (n *pmp) ExternalIP() (net.IP, error) {
+	response, err := n.c.GetExternalAddress()
+	if err != nil {
+		return nil, err
+	}
+	return response.ExternalIPAddress[:], nil
+}
+
+func (n *pmp) AddMapping(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
+	// AddMapping and the client's AddPortMapping.
+	_, err := n.c.AddPortMapping(strings.ToLower(protocol), intport, extport, int(lifetime/time.Second))
+	return err
+}
+
+func (n *pmp) DeleteMapping(protocol string, extport, intport 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.c.AddPortMapping(strings.ToLower(protocol), intport, 0, 0)
+	return err
+}
+
+func discoverPMP() Interface {
+	// run external address lookups on all potential gateways
+	gws := potentialGateways()
+	found := make(chan *pmp, len(gws))
+	for i := range gws {
+		gw := gws[i]
+		gopool.Submit(func() {
+			c := natpmp.NewClient(gw)
+			if _, err := c.GetExternalAddress(); err != nil {
+				found <- nil
+			} else {
+				found <- &pmp{gw, c}
+			}
+		})
+	}
+	// return the one that responds first.
+	// discovery needs to be quick, so we stop caring about
+	// any responses after a very short timeout.
+	timeout := time.NewTimer(1 * time.Second)
+	defer timeout.Stop()
+	for range gws {
+		select {
+		case c := <-found:
+			if c != nil {
+				return c
+			}
+		case <-timeout.C:
+			return nil
+		}
+	}
+	return nil
+}
+
+var (
+	// LAN IP ranges
+	_, lan10, _  = net.ParseCIDR("10.0.0.0/8")
+	_, lan176, _ = net.ParseCIDR("172.16.0.0/12")
+	_, lan192, _ = net.ParseCIDR("192.168.0.0/16")
+)
+
+// TODO: improve this. We currently assume that (on most networks)
+// the router is X.X.X.1 in a local LAN range.
+func potentialGateways() (gws []net.IP) {
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return nil
+	}
+	for _, iface := range ifaces {
+		ifaddrs, err := iface.Addrs()
+		if err != nil {
+			return gws
+		}
+		for _, addr := range ifaddrs {
+			if x, ok := addr.(*net.IPNet); ok {
+				if lan10.Contains(x.IP) || lan176.Contains(x.IP) || lan192.Contains(x.IP) {
+					ip := x.IP.Mask(x.Mask).To4()
+					if ip != nil {
+						ip[3] = ip[3] | 0x01
+						gws = append(gws, ip)
+					}
+				}
+			}
+		}
+	}
+	return gws
+}

+ 215 - 0
p2p/nat/natupnp.go

@@ -0,0 +1,215 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package nat
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/huin/goupnp"
+	"github.com/huin/goupnp/dcps/internetgateway1"
+	"github.com/huin/goupnp/dcps/internetgateway2"
+)
+
+const (
+	soapRequestTimeout = 3 * time.Second
+	rateLimit          = 200 * time.Millisecond
+)
+
+type upnp struct {
+	dev         *goupnp.RootDevice
+	service     string
+	client      upnpClient
+	mu          sync.Mutex
+	lastReqTime time.Time
+}
+
+type upnpClient interface {
+	GetExternalIPAddress() (string, error)
+	AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
+	DeletePortMapping(string, uint16, string) error
+	GetNATRSIPStatus() (sip bool, nat bool, err error)
+}
+
+func (n *upnp) natEnabled() bool {
+	var ok bool
+	var err error
+	n.withRateLimit(func() error {
+		_, ok, err = n.client.GetNATRSIPStatus()
+		return err
+	})
+	return err == nil && ok
+}
+
+func (n *upnp) ExternalIP() (addr net.IP, err error) {
+	var ipString string
+	n.withRateLimit(func() error {
+		ipString, err = n.client.GetExternalIPAddress()
+		return err
+	})
+
+	if err != nil {
+		return nil, err
+	}
+	ip := net.ParseIP(ipString)
+	if ip == nil {
+		return nil, errors.New("bad IP in response")
+	}
+	return ip, nil
+}
+
+func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) error {
+	ip, err := n.internalAddress()
+	if err != nil {
+		return nil
+	}
+	protocol = strings.ToUpper(protocol)
+	lifetimeS := uint32(lifetime / time.Second)
+	n.DeleteMapping(protocol, extport, intport)
+
+	return n.withRateLimit(func() error {
+		return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
+	})
+}
+
+func (n *upnp) internalAddress() (net.IP, error) {
+	devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host)
+	if err != nil {
+		return nil, err
+	}
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return nil, err
+	}
+	for _, iface := range ifaces {
+		addrs, err := iface.Addrs()
+		if err != nil {
+			return nil, err
+		}
+		for _, addr := range addrs {
+			if x, ok := addr.(*net.IPNet); ok && x.Contains(devaddr.IP) {
+				return x.IP, nil
+			}
+		}
+	}
+	return nil, fmt.Errorf("could not find local address in same net as %v", devaddr)
+}
+
+func (n *upnp) DeleteMapping(protocol string, extport, intport int) error {
+	return n.withRateLimit(func() error {
+		return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol))
+	})
+}
+
+func (n *upnp) String() string {
+	return "UPNP " + n.service
+}
+
+func (n *upnp) withRateLimit(fn func() error) error {
+	n.mu.Lock()
+	defer n.mu.Unlock()
+
+	lastreq := time.Since(n.lastReqTime)
+	if lastreq < rateLimit {
+		time.Sleep(rateLimit - lastreq)
+	}
+	err := fn()
+	n.lastReqTime = time.Now()
+	return err
+}
+
+// discoverUPnP searches for Internet Gateway Devices
+// and returns the first one it can find on the local network.
+func discoverUPnP() Interface {
+	found := make(chan *upnp, 2)
+	// IGDv1
+	go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(sc goupnp.ServiceClient) *upnp {
+		switch sc.Service.ServiceType {
+		case internetgateway1.URN_WANIPConnection_1:
+			return &upnp{service: "IGDv1-IP1", client: &internetgateway1.WANIPConnection1{ServiceClient: sc}}
+		case internetgateway1.URN_WANPPPConnection_1:
+			return &upnp{service: "IGDv1-PPP1", client: &internetgateway1.WANPPPConnection1{ServiceClient: sc}}
+		}
+		return nil
+	})
+	// IGDv2
+	go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(sc goupnp.ServiceClient) *upnp {
+		switch sc.Service.ServiceType {
+		case internetgateway2.URN_WANIPConnection_1:
+			return &upnp{service: "IGDv2-IP1", client: &internetgateway2.WANIPConnection1{ServiceClient: sc}}
+		case internetgateway2.URN_WANIPConnection_2:
+			return &upnp{service: "IGDv2-IP2", client: &internetgateway2.WANIPConnection2{ServiceClient: sc}}
+		case internetgateway2.URN_WANPPPConnection_1:
+			return &upnp{service: "IGDv2-PPP1", client: &internetgateway2.WANPPPConnection1{ServiceClient: sc}}
+		}
+		return nil
+	})
+	for i := 0; i < cap(found); i++ {
+		if c := <-found; c != nil {
+			return c
+		}
+	}
+	return nil
+}
+
+// finds devices matching the given target and calls matcher for all
+// advertised services of each device. The first non-nil service found
+// is sent into out. If no service matched, nil is sent.
+func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient) *upnp) {
+	devs, err := goupnp.DiscoverDevices(target)
+	if err != nil {
+		out <- nil
+		return
+	}
+	found := false
+	for i := 0; i < len(devs) && !found; i++ {
+		if devs[i].Root == nil {
+			continue
+		}
+		devs[i].Root.Device.VisitServices(func(service *goupnp.Service) {
+			if found {
+				return
+			}
+			// check for a matching IGD service
+			sc := goupnp.ServiceClient{
+				SOAPClient: service.NewSOAPClient(),
+				RootDevice: devs[i].Root,
+				Location:   devs[i].Location,
+				Service:    service,
+			}
+			sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
+			upnp := matcher(sc)
+			if upnp == nil {
+				return
+			}
+			upnp.dev = devs[i].Root
+
+			// check whether port mapping is enabled
+			if upnp.natEnabled() {
+				out <- upnp
+				found = true
+			}
+		})
+	}
+	if !found {
+		out <- nil
+	}
+}

+ 33 - 8
p2p/server.go

@@ -1,9 +1,11 @@
 package p2p
 
 import (
+	"blockchain-go/p2p/enode"
 	"errors"
 	"fmt"
 	"github.com/ethereum/go-ethereum/crypto"
+	"net"
 	"sync"
 )
 
@@ -32,7 +34,7 @@ type Server struct {
 	//log log.Logger
 
 	//nodedb    *enode.DB
-	//localnode *enode.LocalNode
+	localnode *enode.LocalNode
 	//ntab      *discover.UDPv4
 	//DiscV5    *discover.UDPv5
 	//discmix *enode.FairMix
@@ -52,22 +54,45 @@ type Server struct {
 	//inboundHistory expHeap
 }
 
-func (srv *Server) Start() (err error) {
+func (server *Server) Start() (err error) {
 	fmt.Println("Hello World.")
 
 	return nil
 }
 
-func (srv *Server) setupLocalNode() (err error) {
-	publicKey := crypto.FromECDSAPub(&srv.PrivateKey.PublicKey)
-	// 新增握手处理器
-	srv.ourHandshake = &protoHandshake{
+// 配置本地节点
+func (server *Server) setupLocalNode() (err error) {
+	// 创建握手所需对象
+	publicKey := crypto.FromECDSAPub(&server.PrivateKey.PublicKey)
+	server.ourHandshake = &protoHandshake{
 		Version: baseProtocolVersion,
-		Name:    srv.Name,
+		Name:    server.Name,
 		ID:      publicKey[1:],
 	}
-
 	// TODO 新增协议,欺骗对等节点,先尝试没有协议的版本
 
+	// 创建本地节点
+	server.localnode = enode.NewLocalNode(server.PrivateKey)
+	server.localnode.SetFallbackIP(net.IP{127, 0, 0, 1})
+
+	// 配置本地静态IP
+	ip, _ := server.NAT.ExternalIP()
+	server.localnode.SetStaticIP(ip)
+
+	return nil
+}
+
+// 配置节点发现逻辑
+func (server *Server) setupDiscovery() (err error) {
+	return nil
+}
+
+// 监听器
+func (server *Server) setupListening() (err error) {
+	return nil
+}
+
+// 设置拨号调度器
+func (server *Server) setupDialScheduler() (err error) {
 	return nil
 }