From aaa00b34880aba88421230d9f923fd2e9d62503a Mon Sep 17 00:00:00 2001 From: Hongbo Zhang Date: Sun, 7 Jun 2020 20:02:29 -0400 Subject: [PATCH 01/12] upnp --- go.mod | 2 +- go.sum | 2 + main/main.go | 8 +- main/params.go | 4 +- nat/mapper.go | 143 ------------------------------- nat/nat.go | 94 ++++++++++++++++++++ nat/no_router.go | 28 ------ nat/pmp.go | 71 --------------- nat/router.go | 65 -------------- nat/upnp.go | 219 ++++++++++++++++++----------------------------- node/config.go | 2 +- 11 files changed, 187 insertions(+), 451 deletions(-) delete mode 100644 nat/mapper.go create mode 100644 nat/nat.go delete mode 100644 nat/no_router.go delete mode 100644 nat/pmp.go delete mode 100644 nat/router.go diff --git a/go.mod b/go.mod index 4636c8c..c6f4e79 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/karalabe/usb v0.0.0-20191104083709-911d15fe12a9 // indirect github.com/mattn/go-colorable v0.1.6 // indirect github.com/mitchellh/go-homedir v1.1.0 - github.com/mr-tron/base58 v1.1.3 + github.com/mr-tron/base58 v1.2.0 github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d github.com/olekukonko/tablewriter v0.0.4 // indirect github.com/pborman/uuid v1.2.0 // indirect diff --git a/go.sum b/go.sum index d79e9a8..2f989e1 100644 --- a/go.sum +++ b/go.sum @@ -165,6 +165,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= diff --git a/main/main.go b/main/main.go index 98cb581..b49d320 100644 --- a/main/main.go +++ b/main/main.go @@ -68,11 +68,11 @@ func main() { log.Debug("assertions are enabled. This may slow down execution") } - mapper := nat.NewDefaultMapper(log, Config.Nat, nat.TCP, "gecko") - defer mapper.UnmapAllPorts() + router := nat.NewRouter(log, Config.Nat) + defer router.UnmapAllPorts() - mapper.MapPort(Config.StakingIP.Port, Config.StakingIP.Port) - mapper.MapPort(Config.HTTPPort, Config.HTTPPort) + router.Map("TCP", Config.StakingIP.Port, Config.StakingIP.Port, "gecko") + router.Map("TCP", Config.HTTPPort, Config.HTTPPort, "gecko http") node := node.Node{} diff --git a/main/params.go b/main/params.go index 6dcad06..86eca34 100644 --- a/main/params.go +++ b/main/params.go @@ -281,12 +281,12 @@ func init() { Config.DB = memdb.New() } - Config.Nat = nat.NewRouter() + Config.Nat = nat.GetNATRouter() var ip net.IP // If public IP is not specified, get it using shell command dig if *consensusIP == "" { - ip, err = Config.Nat.IP() + ip, err = Config.Nat.ExternalIP() if err != nil { ip = net.IPv4zero // Couldn't get my IP...set to 0.0.0.0 } diff --git a/nat/mapper.go b/nat/mapper.go deleted file mode 100644 index 3beaedd..0000000 --- a/nat/mapper.go +++ /dev/null @@ -1,143 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package nat - -import ( - "sync" - "time" - - "github.com/ava-labs/gecko/utils/logging" - "github.com/ava-labs/gecko/utils/wrappers" -) - -const ( - defaultMappingTimeout = 30 * time.Minute - defaultMappingUpdateInterval = 3 * defaultMappingTimeout / 4 -) - -// Mapper maps port -type Mapper interface { - MapPort(newInternalPort, newExternalPort uint16) error - UnmapAllPorts() error -} - -type mapper struct { - log logging.Logger - router Router - networkProtocol NetworkProtocol - mappingNames string - mappingTimeout time.Duration - mappingUpdateInterval time.Duration - - closer chan struct{} - wg sync.WaitGroup - errLock sync.Mutex - errs wrappers.Errs -} - -// NewMapper returns a new mapper that can map ports on a router -func NewMapper( - log logging.Logger, - router Router, - networkProtocol NetworkProtocol, - mappingNames string, - mappingTimeout time.Duration, - mappingUpdateInterval time.Duration, -) Mapper { - return &mapper{ - log: log, - router: router, - networkProtocol: networkProtocol, - mappingNames: mappingNames, - mappingTimeout: mappingTimeout, - mappingUpdateInterval: mappingUpdateInterval, - closer: make(chan struct{}), - } -} - -// NewDefaultMapper returns a new mapper that can map ports on a router with -// default settings -func NewDefaultMapper( - log logging.Logger, - router Router, - networkProtocol NetworkProtocol, - mappingNames string, -) Mapper { - return NewMapper( - log, - router, - networkProtocol, - mappingNames, - defaultMappingTimeout, // uses the default value - defaultMappingUpdateInterval, // uses the default value - ) -} - -// MapPort maps a local port to a port on the router until UnmapAllPorts is -// called. -func (m *mapper) MapPort(newInternalPort, newExternalPort uint16) error { - m.wg.Add(1) - go m.mapPort(newInternalPort, newExternalPort) - return nil -} - -func (m *mapper) mapPort(newInternalPort, newExternalPort uint16) { - // duration is set to 0 here so that the select case will execute - // immediately - updateTimer := time.NewTimer(0) - defer func() { - updateTimer.Stop() - - m.errLock.Lock() - m.errs.Add(m.router.UnmapPort( - m.networkProtocol, - newInternalPort, - newExternalPort)) - m.errLock.Unlock() - - m.log.Debug("Unmapped external port %d to internal port %d", - newExternalPort, - newInternalPort) - - m.wg.Done() - }() - - for { - select { - case <-updateTimer.C: - err := m.router.MapPort( - m.networkProtocol, - newInternalPort, - newExternalPort, - m.mappingNames, - m.mappingTimeout) - - if err != nil { - m.errLock.Lock() - m.errs.Add(err) - m.errLock.Unlock() - - m.log.Debug("Failed to add mapping from external port %d to internal port %d due to %s", - newExternalPort, - newInternalPort, - err) - } else { - m.log.Debug("Mapped external port %d to internal port %d", - newExternalPort, - newInternalPort) - } - - // remap the port in m.mappingUpdateInterval - updateTimer.Reset(m.mappingUpdateInterval) - case _, _ = <-m.closer: - return // only return when all ports are unmapped - } - } -} - -func (m *mapper) UnmapAllPorts() error { - close(m.closer) - m.wg.Wait() - return m.errs.Err -} diff --git a/nat/nat.go b/nat/nat.go new file mode 100644 index 0000000..1c3c2fa --- /dev/null +++ b/nat/nat.go @@ -0,0 +1,94 @@ +package nat + +import ( + "net" + "sync" + "time" + + "github.com/ava-labs/gecko/utils/logging" + "github.com/ava-labs/gecko/utils/wrappers" +) + +const ( + mapTimeout = 30 * time.Minute + mapUpdate = mapTimeout / 2 +) + +type NATRouter interface { + MapPort(protocol string, intport, extport uint16, desc string, duration time.Duration) error + UnmapPort(protocol string, extport uint16) error + ExternalIP() (net.IP, error) +} + +func GetNATRouter() NATRouter { + //TODO other protocol + return getUPnPRouter() +} + +type Router struct { + log logging.Logger + r NATRouter + closer chan struct{} + wg sync.WaitGroup + errLock sync.Mutex + errs wrappers.Errs +} + +func NewRouter(log logging.Logger, r NATRouter) Router { + return Router{ + log: log, + r: r, + closer: make(chan struct{}), + } +} + +func (dev *Router) Map(protocol string, intport, extport uint16, desc string) { + dev.wg.Add(1) + go dev.mapPort(protocol, intport, extport, desc) +} + +func (dev *Router) mapPort(protocol string, intport, extport uint16, desc string) { + updater := time.NewTimer(mapUpdate) + defer func() { + updater.Stop() + + dev.log.Info("Unmap protocol %s external port %d", protocol, extport) + dev.errLock.Lock() + dev.errs.Add(dev.r.UnmapPort(protocol, extport)) + dev.errLock.Unlock() + + dev.wg.Done() + }() + + if err := dev.r.MapPort(protocol, intport, extport, desc, mapTimeout); err != nil { + dev.log.Warn("Map port failed. Protocol %s Internal %d External %d. %s", + protocol, intport, extport, err) + dev.errLock.Lock() + dev.errs.Add(err) + dev.errLock.Unlock() + } else { + dev.log.Info("Mapped Protocol %s Internal %d External %d. %s", protocol, + intport, extport) + } + + for { + select { + case <-updater.C: + if err := dev.r.MapPort(protocol, intport, extport, desc, mapTimeout); err != nil { + dev.log.Warn("Renew port mapping failed. Protocol %s Internal %d External %d. %s", + protocol, intport, extport, err) + } else { + dev.log.Info("Renew port mapping Protocol %s Internal %d External %d. %s", protocol, + intport, extport) + } + + updater.Reset(mapUpdate) + } + } +} + +func (dev *Router) UnmapAllPorts() error { + close(dev.closer) + dev.wg.Wait() + return dev.errs.Err +} diff --git a/nat/no_router.go b/nat/no_router.go deleted file mode 100644 index edb86b6..0000000 --- a/nat/no_router.go +++ /dev/null @@ -1,28 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package nat - -import ( - "errors" - "net" - "time" -) - -var ( - errNoRouter = errors.New("no nat enabled router was discovered") -) - -type noRouter struct{} - -func (noRouter) MapPort(_ NetworkProtocol, _, _ uint16, _ string, _ time.Duration) error { - return errNoRouter -} - -func (noRouter) UnmapPort(_ NetworkProtocol, _, _ uint16) error { - return errNoRouter -} - -func (noRouter) IP() (net.IP, error) { - return nil, errNoRouter -} diff --git a/nat/pmp.go b/nat/pmp.go deleted file mode 100644 index 311375d..0000000 --- a/nat/pmp.go +++ /dev/null @@ -1,71 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package nat - -import ( - "net" - "time" - - "github.com/jackpal/gateway" - "github.com/jackpal/go-nat-pmp" -) - -var ( - pmpClientTimeout = 500 * time.Millisecond -) - -// natPMPClient adapts the NAT-PMP protocol implementation so it conforms to -// the common interface. -type pmpClient struct { - client *natpmp.Client -} - -func (pmp *pmpClient) MapPort( - networkProtocol NetworkProtocol, - newInternalPort uint16, - newExternalPort uint16, - mappingName string, - mappingDuration time.Duration) error { - protocol := string(networkProtocol) - internalPort := int(newInternalPort) - externalPort := int(newExternalPort) - // go-nat-pmp uses seconds to denote their lifetime - lifetime := int(mappingDuration / time.Second) - - _, err := pmp.client.AddPortMapping(protocol, internalPort, externalPort, lifetime) - return err -} - -func (pmp *pmpClient) UnmapPort( - networkProtocol NetworkProtocol, - internalPort uint16, - _ uint16) error { - protocol := string(networkProtocol) - internalPortInt := int(internalPort) - - _, err := pmp.client.AddPortMapping(protocol, internalPortInt, 0, 0) - return err -} - -func (pmp *pmpClient) IP() (net.IP, error) { - response, err := pmp.client.GetExternalAddress() - if err != nil { - return nil, err - } - return response.ExternalIPAddress[:], nil -} - -func getPMPRouter() Router { - gatewayIP, err := gateway.DiscoverGateway() - if err != nil { - return nil - } - - pmp := &pmpClient{natpmp.NewClientWithTimeout(gatewayIP, pmpClientTimeout)} - if _, err := pmp.IP(); err != nil { - return nil - } - - return pmp -} diff --git a/nat/router.go b/nat/router.go deleted file mode 100644 index 11b58f9..0000000 --- a/nat/router.go +++ /dev/null @@ -1,65 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Package nat performs network address translation and provides helpers for -// routing ports. -package nat - -import ( - "net" - "time" -) - -// NetworkProtocol is a protocol that will be used through a port -type NetworkProtocol string - -// Available protocol -const ( - TCP NetworkProtocol = "TCP" - UDP NetworkProtocol = "UDP" -) - -// Router provides a standard NAT router functions. Specifically, allowing the -// fetching of public IPs and port forwarding to this computer. -type Router interface { - // mapPort creates a mapping between a port on the local computer to an - // external port on the router. - // - // The mappingName is something displayed on the router, so it is included - // for completeness. - MapPort( - networkProtocol NetworkProtocol, - newInternalPort uint16, - newExternalPort uint16, - mappingName string, - mappingDuration time.Duration) error - - // UnmapPort clears a mapping that was previous made by a call to MapPort - UnmapPort( - networkProtocol NetworkProtocol, - internalPort uint16, - externalPort uint16) error - - // Returns the routers IP address on the network the router considers - // external - IP() (net.IP, error) -} - -// NewRouter returns a new router discovered on the local network -func NewRouter() Router { - routers := make(chan Router) - // Because getting a router can take a noticeable amount of time to error, - // we run these requests in parallel - go func() { - routers <- getUPnPRouter() - }() - go func() { - routers <- getPMPRouter() - }() - for i := 0; i < 2; i++ { - if router := <-routers; router != nil { - return router - } - } - return noRouter{} -} diff --git a/nat/upnp.go b/nat/upnp.go index e60cd6e..7f62760 100644 --- a/nat/upnp.go +++ b/nat/upnp.go @@ -1,10 +1,6 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - package nat import ( - "errors" "fmt" "net" "time" @@ -14,13 +10,7 @@ import ( "github.com/huin/goupnp/dcps/internetgateway2" ) -const ( - soapTimeout = time.Second -) - -var ( - errNoGateway = errors.New("Failed to connect to any avaliable gateways") -) +const soapRequestTimeout = 3 * time.Second // upnpClient is the interface used by goupnp for their client implementations type upnpClient interface { @@ -50,66 +40,13 @@ type upnpClient interface { } type upnpRouter struct { - root *goupnp.RootDevice + dev *goupnp.RootDevice client upnpClient } -func (n *upnpRouter) MapPort( - networkProtocol NetworkProtocol, - newInternalPort uint16, - newExternalPort uint16, - mappingName string, - mappingDuration time.Duration, -) error { - ip, err := n.localAddress() - if err != nil { - return err - } - - protocol := string(networkProtocol) - // goupnp uses seconds to denote their lifetime - lifetime := uint32(mappingDuration / time.Second) - - // UnmapPort's error is intentionally dropped, because the mapping may not - // exist. - n.UnmapPort(networkProtocol, newInternalPort, newExternalPort) - - return n.client.AddPortMapping( - "", // newRemoteHost isn't used to limit the mapping to a host - newExternalPort, - protocol, - newInternalPort, - ip.String(), // newInternalClient is the client traffic should be sent to - true, // newEnabled enables port mappings - mappingName, - lifetime, - ) -} - -func (n *upnpRouter) UnmapPort(networkProtocol NetworkProtocol, _, externalPort uint16) error { - protocol := string(networkProtocol) - return n.client.DeletePortMapping( - "", // newRemoteHost isn't used to limit the mapping to a host - externalPort, - protocol) -} - -func (n *upnpRouter) IP() (net.IP, error) { - ipStr, err := n.client.GetExternalIPAddress() - if err != nil { - return nil, err - } - - ip := net.ParseIP(ipStr) - if ip == nil { - return nil, fmt.Errorf("invalid IP %s", ipStr) - } - return ip, nil -} - -func (n *upnpRouter) localAddress() (net.IP, error) { +func (r *upnpRouter) localIP() (net.IP, error) { // attempt to get an address on the router - deviceAddr, err := net.ResolveUDPAddr("udp4", n.root.URLBase.Host) + deviceAddr, err := net.ResolveUDPAddr("udp4", r.dev.URLBase.Host) if err != nil { return nil, err } @@ -142,27 +79,34 @@ func (n *upnpRouter) localAddress() (net.IP, error) { } } return nil, fmt.Errorf("couldn't find the local address in the same network as %s", deviceIP) + } -// getUPnPRouter searches for all Gateway Devices that have avaliable -// connections in the goupnp library and returns the first connection it can -// find. -func getUPnPRouter() Router { - routers := make(chan *upnpRouter) - // Because DiscoverDevices takes a noticeable amount of time to error, we - // run these requests in parallel - go func() { - routers <- connectToGateway(internetgateway1.URN_WANConnectionDevice_1, gateway1) - }() - go func() { - routers <- connectToGateway(internetgateway2.URN_WANConnectionDevice_2, gateway2) - }() - for i := 0; i < 2; i++ { - if router := <-routers; router != nil { - return router - } +func (r *upnpRouter) ExternalIP() (net.IP, error) { + str, err := r.client.GetExternalIPAddress() + if err != nil { + return nil, err } - return nil + + ip := net.ParseIP(str) + if ip == nil { + return nil, fmt.Errorf("invalid IP %s", str) + } + return ip, nil +} + +func (r *upnpRouter) MapPort(protocol string, intport, extport uint16, desc string, duration time.Duration) error { + ip, err := r.localIP() + if err != nil { + return nil + } + lifetime := uint32(duration / time.Second) + r.UnmapPort(protocol, extport) + return r.client.AddPortMapping("", extport, protocol, intport, ip.String(), true, desc, lifetime) +} + +func (r *upnpRouter) UnmapPort(protocol string, extport uint16) error { + return r.client.DeletePortMapping("", extport, protocol) } func gateway1(client goupnp.ServiceClient) upnpClient { @@ -175,7 +119,6 @@ func gateway1(client goupnp.ServiceClient) upnpClient { return nil } } - func gateway2(client goupnp.ServiceClient) upnpClient { switch client.Service.ServiceType { case internetgateway2.URN_WANIPConnection_1: @@ -189,65 +132,69 @@ func gateway2(client goupnp.ServiceClient) upnpClient { } } -func connectToGateway(deviceType string, toClient func(goupnp.ServiceClient) upnpClient) *upnpRouter { - devs, err := goupnp.DiscoverDevices(deviceType) +func getUpnpClient(client goupnp.ServiceClient) upnpClient { + c := gateway1(client) + if c != nil { + return c + } + return gateway2(client) +} + +func getRootDevice(dev *goupnp.MaybeRootDevice) *upnpRouter { + var router *upnpRouter + dev.Root.Device.VisitServices(func(service *goupnp.Service) { + c := goupnp.ServiceClient{ + SOAPClient: service.NewSOAPClient(), + RootDevice: dev.Root, + Location: dev.Location, + Service: service, + } + c.SOAPClient.HTTPClient.Timeout = soapRequestTimeout + client := getUpnpClient(c) + if client == nil { + return + } + router = &upnpRouter{dev.Root, client} + if router == nil { + return + } + if _, nat, err := router.client.GetNATRSIPStatus(); err != nil || !nat { + router = nil + return + } + }) + return router +} + +func discover(target string) *upnpRouter { + devs, err := goupnp.DiscoverDevices(target) if err != nil { return nil } - // we are iterating over all the network devices, acting a possible roots - for i := range devs { - dev := &devs[i] - if dev.Root == nil { + for i := 0; i < len(devs); i++ { + if devs[i].Root == nil { continue } - - // the root device may be a router, so attempt to connect to that - rootDevice := &dev.Root.Device - if upnp := getRouter(dev, rootDevice, toClient); upnp != nil { - return upnp - } - - // attempt to connect to any sub devices - devices := rootDevice.Devices - for i := range devices { - if upnp := getRouter(dev, &devices[i], toClient); upnp != nil { - return upnp - } + u := getRootDevice(&devs[i]) + if u != nil { + return u } } return nil } -func getRouter(rootDevice *goupnp.MaybeRootDevice, device *goupnp.Device, toClient func(goupnp.ServiceClient) upnpClient) *upnpRouter { - for i := range device.Services { - service := &device.Services[i] - - soapClient := service.NewSOAPClient() - // make sure the client times out if needed - soapClient.HTTPClient.Timeout = soapTimeout - - // attempt to create a client connection - serviceClient := goupnp.ServiceClient{ - SOAPClient: soapClient, - RootDevice: rootDevice.Root, - Location: rootDevice.Location, - Service: service, - } - client := toClient(serviceClient) - if client == nil { - continue - } - - // check whether port mapping is enabled - if _, nat, err := client.GetNATRSIPStatus(); err != nil || !nat { - continue - } - - // we found a router! - return &upnpRouter{ - root: rootDevice.Root, - client: client, - } +func getUPnPRouter() *upnpRouter { + r := discover(internetgateway1.URN_WANConnectionDevice_1) + if r != nil { + return r } - return nil + return discover(internetgateway2.URN_WANConnectionDevice_2) +} + +func GetUPnP() *upnpRouter { + r := discover(internetgateway1.URN_WANConnectionDevice_1) + if r != nil { + return r + } + return discover(internetgateway2.URN_WANConnectionDevice_2) } diff --git a/node/config.go b/node/config.go index 2504276..aaadb7a 100644 --- a/node/config.go +++ b/node/config.go @@ -15,7 +15,7 @@ import ( // Config contains all of the configurations of an Ava node. type Config struct { // protocol to use for opening the network interface - Nat nat.Router + Nat nat.NATRouter // ID of the network this node should connect to NetworkID uint32 From 96cfcc0b5b5e1b2de8baf95e9c31fced2bbfda21 Mon Sep 17 00:00:00 2001 From: Hongbo Zhang Date: Fri, 12 Jun 2020 10:07:17 -0400 Subject: [PATCH 02/12] NAT test --- nat/nat.go | 16 ++++++---- nat/nat_test.go | 77 +++++++++++++++++++++++++++++++++++++++++++++++++ nat/upnp.go | 51 ++++++++++++++++++++++---------- 3 files changed, 124 insertions(+), 20 deletions(-) create mode 100644 nat/nat_test.go diff --git a/nat/nat.go b/nat/nat.go index 1c3c2fa..60d3a83 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -22,7 +22,10 @@ type NATRouter interface { func GetNATRouter() NATRouter { //TODO other protocol - return getUPnPRouter() + if r := getUPnPRouter(); r != nil { + return r + } + return nil } type Router struct { @@ -61,13 +64,13 @@ func (dev *Router) mapPort(protocol string, intport, extport uint16, desc string }() if err := dev.r.MapPort(protocol, intport, extport, desc, mapTimeout); err != nil { - dev.log.Warn("Map port failed. Protocol %s Internal %d External %d. %s", + dev.log.Error("Map port failed. Protocol %s Internal %d External %d. %s", protocol, intport, extport, err) dev.errLock.Lock() dev.errs.Add(err) dev.errLock.Unlock() } else { - dev.log.Info("Mapped Protocol %s Internal %d External %d. %s", protocol, + dev.log.Info("Mapped Protocol %s Internal %d External %d.", protocol, intport, extport) } @@ -75,14 +78,16 @@ func (dev *Router) mapPort(protocol string, intport, extport uint16, desc string select { case <-updater.C: if err := dev.r.MapPort(protocol, intport, extport, desc, mapTimeout); err != nil { - dev.log.Warn("Renew port mapping failed. Protocol %s Internal %d External %d. %s", + dev.log.Error("Renew port mapping failed. Protocol %s Internal %d External %d. %s", protocol, intport, extport, err) } else { - dev.log.Info("Renew port mapping Protocol %s Internal %d External %d. %s", protocol, + dev.log.Info("Renew port mapping Protocol %s Internal %d External %d.", protocol, intport, extport) } updater.Reset(mapUpdate) + case _, _ = <-dev.closer: + return } } } @@ -90,5 +95,6 @@ func (dev *Router) mapPort(protocol string, intport, extport uint16, desc string func (dev *Router) UnmapAllPorts() error { close(dev.closer) dev.wg.Wait() + dev.log.Info("Unmapped all ports") return dev.errs.Err } diff --git a/nat/nat_test.go b/nat/nat_test.go new file mode 100644 index 0000000..acb06cd --- /dev/null +++ b/nat/nat_test.go @@ -0,0 +1,77 @@ +package nat + +// go test -run 'HTTP' + +import ( + "context" + "fmt" + "net/http" + "os" + "os/signal" + "strconv" + "testing" + "time" + + "github.com/ava-labs/gecko/utils/logging" +) + +const ( + externalPort = 9876 + localPort = 8080 +) + +func TestHTTP(t *testing.T) { + config, err := logging.DefaultConfig() + if err != nil { + return + } + factory := logging.NewFactory(config) + defer factory.Close() + + log, err := factory.Make() + if err != nil { + return + } + defer log.Stop() + defer log.StopOnPanic() + + log.Info("Logger Initialized") + + n := GetNATRouter() + if n == nil { + log.Error("Unable to get UPnP Device") + return + } + + ip, err := n.ExternalIP() + if err != nil { + log.Error("Unable to get external IP: %v", err) + return + } + log.Info("External Address %s:%d", ip.String(), externalPort) + + r := NewRouter(log, n) + defer r.UnmapAllPorts() + + r.Map("TCP", localPort, externalPort, "AVA UPnP Test") + + log.Info("Starting HTTP Service") + server := &http.Server{Addr: ":" + strconv.Itoa(localPort)} + http.HandleFunc("/", hello) + go func() { + server.ListenAndServe() + }() + + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt) + + <-stop + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + server.Shutdown(ctx) +} + +func hello(w http.ResponseWriter, req *http.Request) { + fmt.Fprintf(w, "AVA UPnP Test\n") +} diff --git a/nat/upnp.go b/nat/upnp.go index 7f62760..f83b845 100644 --- a/nat/upnp.go +++ b/nat/upnp.go @@ -10,7 +10,10 @@ import ( "github.com/huin/goupnp/dcps/internetgateway2" ) -const soapRequestTimeout = 3 * time.Second +const ( + soapRequestTimeout = 3 * time.Second + mapRetry = 20 +) // upnpClient is the interface used by goupnp for their client implementations type upnpClient interface { @@ -79,7 +82,6 @@ func (r *upnpRouter) localIP() (net.IP, error) { } } return nil, fmt.Errorf("couldn't find the local address in the same network as %s", deviceIP) - } func (r *upnpRouter) ExternalIP() (net.IP, error) { @@ -95,14 +97,26 @@ func (r *upnpRouter) ExternalIP() (net.IP, error) { return ip, nil } -func (r *upnpRouter) MapPort(protocol string, intport, extport uint16, desc string, duration time.Duration) error { +func (r *upnpRouter) MapPort(protocol string, intport, extport uint16, + desc string, duration time.Duration) error { ip, err := r.localIP() if err != nil { return nil } lifetime := uint32(duration / time.Second) - r.UnmapPort(protocol, extport) - return r.client.AddPortMapping("", extport, protocol, intport, ip.String(), true, desc, lifetime) + + for i := 0; i < mapRetry; i++ { + externalPort := extport + uint16(i) + err = r.client.AddPortMapping("", externalPort, protocol, intport, + ip.String(), true, desc, lifetime) + if err == nil { + fmt.Printf("Mapped external port %d to local %s:%d\n", externalPort, + ip.String(), intport) + return nil + } + fmt.Printf("Unable to map port, retry with port %d\n", externalPort+1) + } + return err } func (r *upnpRouter) UnmapPort(protocol string, extport uint16) error { @@ -184,17 +198,24 @@ func discover(target string) *upnpRouter { } func getUPnPRouter() *upnpRouter { - r := discover(internetgateway1.URN_WANConnectionDevice_1) - if r != nil { - return r + targets := []string{ + internetgateway1.URN_WANConnectionDevice_1, + internetgateway2.URN_WANConnectionDevice_2, } - return discover(internetgateway2.URN_WANConnectionDevice_2) -} -func GetUPnP() *upnpRouter { - r := discover(internetgateway1.URN_WANConnectionDevice_1) - if r != nil { - return r + routers := make(chan *upnpRouter, len(targets)) + + for _, urn := range targets { + go func(urn string) { + routers <- discover(urn) + }(urn) } - return discover(internetgateway2.URN_WANConnectionDevice_2) + + for i := 0; i < len(targets); i++ { + if r := <-routers; r != nil { + return r + } + } + + return nil } From 54e1c4031e0eea9b0c8471d6d76638f4988551bb Mon Sep 17 00:00:00 2001 From: Hongbo Zhang Date: Sun, 7 Jun 2020 20:02:29 -0400 Subject: [PATCH 03/12] upnp --- nat/upnp.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/nat/upnp.go b/nat/upnp.go index f83b845..bfc6c5e 100644 --- a/nat/upnp.go +++ b/nat/upnp.go @@ -82,6 +82,7 @@ func (r *upnpRouter) localIP() (net.IP, error) { } } return nil, fmt.Errorf("couldn't find the local address in the same network as %s", deviceIP) + } func (r *upnpRouter) ExternalIP() (net.IP, error) { @@ -194,7 +195,7 @@ func discover(target string) *upnpRouter { return u } } - return nil + return gateway2(client) } func getUPnPRouter() *upnpRouter { @@ -219,3 +220,19 @@ func getUPnPRouter() *upnpRouter { return nil } + +func getUPnPRouter() *upnpRouter { + r := discover(internetgateway1.URN_WANConnectionDevice_1) + if r != nil { + return r + } + return discover(internetgateway2.URN_WANConnectionDevice_2) +} + +func GetUPnP() *upnpRouter { + r := discover(internetgateway1.URN_WANConnectionDevice_1) + if r != nil { + return r + } + return discover(internetgateway2.URN_WANConnectionDevice_2) +} From 3281da4ff2ff0f6dda6f8c6f3ba340388365615e Mon Sep 17 00:00:00 2001 From: Hongbo Zhang Date: Fri, 12 Jun 2020 10:07:17 -0400 Subject: [PATCH 04/12] NAT test --- nat/upnp.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/nat/upnp.go b/nat/upnp.go index bfc6c5e..eed99fe 100644 --- a/nat/upnp.go +++ b/nat/upnp.go @@ -82,7 +82,6 @@ func (r *upnpRouter) localIP() (net.IP, error) { } } return nil, fmt.Errorf("couldn't find the local address in the same network as %s", deviceIP) - } func (r *upnpRouter) ExternalIP() (net.IP, error) { @@ -222,17 +221,24 @@ func getUPnPRouter() *upnpRouter { } func getUPnPRouter() *upnpRouter { - r := discover(internetgateway1.URN_WANConnectionDevice_1) - if r != nil { - return r + targets := []string{ + internetgateway1.URN_WANConnectionDevice_1, + internetgateway2.URN_WANConnectionDevice_2, } - return discover(internetgateway2.URN_WANConnectionDevice_2) -} -func GetUPnP() *upnpRouter { - r := discover(internetgateway1.URN_WANConnectionDevice_1) - if r != nil { - return r + routers := make(chan *upnpRouter, len(targets)) + + for _, urn := range targets { + go func(urn string) { + routers <- discover(urn) + }(urn) } - return discover(internetgateway2.URN_WANConnectionDevice_2) + + for i := 0; i < len(targets); i++ { + if r := <-routers; r != nil { + return r + } + } + + return nil } From 661ee3a5428ba4e0b4781942ce91e0074dabda77 Mon Sep 17 00:00:00 2001 From: Hongbo Zhang Date: Fri, 12 Jun 2020 17:40:41 -0400 Subject: [PATCH 05/12] support machine with public IP --- nat/nat.go | 23 +++++++++++++++++++---- nat/nat_test.go | 15 +++++++++++++++ nat/public_ip.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 nat/public_ip.go diff --git a/nat/nat.go b/nat/nat.go index 60d3a83..aa83be0 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -1,6 +1,7 @@ package nat import ( + "fmt" "net" "sync" "time" @@ -21,11 +22,25 @@ type NATRouter interface { } func GetNATRouter() NATRouter { - //TODO other protocol - if r := getUPnPRouter(); r != nil { - return r + router := make(chan NATRouter) + + go func() { + r := getUPnPRouter() + if r != nil { + fmt.Println("Found UPnP Router") + router <- r + } else { + router <- nil + } + }() + + for i := 0; i < 1; i++ { + if r := <-router; r != nil { + return r + } } - return nil + + return NewPublicIP() } type Router struct { diff --git a/nat/nat_test.go b/nat/nat_test.go index acb06cd..efa2233 100644 --- a/nat/nat_test.go +++ b/nat/nat_test.go @@ -20,6 +20,21 @@ const ( localPort = 8080 ) +func TestRouter(t *testing.T) { + n := GetNATRouter() + if n == nil { + fmt.Println("NAT Router is nil") + return + } + + ip, err := n.ExternalIP() + if err != nil { + fmt.Printf("Unable to get external IP: %v\n", err) + return + } + fmt.Printf("External Address %s:%d\n", ip.String(), externalPort) +} + func TestHTTP(t *testing.T) { config, err := logging.DefaultConfig() if err != nil { diff --git a/nat/public_ip.go b/nat/public_ip.go new file mode 100644 index 0000000..3900942 --- /dev/null +++ b/nat/public_ip.go @@ -0,0 +1,48 @@ +package nat + +import ( + "fmt" + "net" + "time" +) + +const googleDNSServer = "8.8.8.8:80" + +type publicIP struct { + ip net.IP +} + +func (publicIP) MapPort(protocol string, intport, extport uint16, desc string, duration time.Duration) error { + if intport != extport { + return fmt.Errorf("cannot map port %d to %d", intport, extport) + } + return nil +} + +func (publicIP) UnmapPort(protocol string, extport uint16) error { + return nil +} + +func (r publicIP) ExternalIP() (net.IP, error) { + return r.ip, nil +} + +func getOutboundIP() (net.IP, error) { + conn, err := net.Dial("udp", googleDNSServer) + if err != nil { + return nil, err + } + defer conn.Close() + + return conn.LocalAddr().(*net.UDPAddr).IP, nil +} + +func NewPublicIP() *publicIP { + ip, err := getOutboundIP() + if err != nil { + return nil + } + return &publicIP{ + ip: ip, + } +} From f8301f11c2a966434d0dc76a6e40f9daca47d7d7 Mon Sep 17 00:00:00 2001 From: Hongbo Zhang Date: Sat, 13 Jun 2020 15:29:28 -0400 Subject: [PATCH 06/12] fix upnp port detection and retry on mapping clean up rm test --- nat/nat.go | 105 +++++++++++++++++------------ nat/nat_test.go | 92 ------------------------- nat/{public_ip.go => no_router.go} | 16 +++-- nat/upnp.go | 44 +++++++----- 4 files changed, 97 insertions(+), 160 deletions(-) delete mode 100644 nat/nat_test.go rename nat/{public_ip.go => no_router.go} (64%) diff --git a/nat/nat.go b/nat/nat.go index aa83be0..026113e 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -1,7 +1,6 @@ package nat import ( - "fmt" "net" "sync" "time" @@ -11,36 +10,25 @@ import ( ) const ( - mapTimeout = 30 * time.Minute - mapUpdate = mapTimeout / 2 + mapTimeout = 30 * time.Minute + mapUpdateTimeout = mapTimeout / 2 + maxRetries = 20 ) type NATRouter interface { MapPort(protocol string, intport, extport uint16, desc string, duration time.Duration) error UnmapPort(protocol string, extport uint16) error ExternalIP() (net.IP, error) + IsMapped(extport uint16, protocol string) bool } func GetNATRouter() NATRouter { - router := make(chan NATRouter) - - go func() { - r := getUPnPRouter() - if r != nil { - fmt.Println("Found UPnP Router") - router <- r - } else { - router <- nil - } - }() - - for i := 0; i < 1; i++ { - if r := <-router; r != nil { - return r - } + //TODO add PMP support + if r := getUPnPRouter(); r != nil { + return r } - return NewPublicIP() + return NewNoRouter() } type Router struct { @@ -60,47 +48,76 @@ func NewRouter(log logging.Logger, r NATRouter) Router { } } -func (dev *Router) Map(protocol string, intport, extport uint16, desc string) { +// Map sets up port mapping using given protocol, internal and external ports +// and returns the final port mapped. It returns 0 if mapping failed after the +// maximun number of retries +func (dev *Router) Map(protocol string, intport, extport uint16, desc string) uint16 { + mappedPort := make(chan uint16) + dev.wg.Add(1) - go dev.mapPort(protocol, intport, extport, desc) + go dev.keepPortMapping(mappedPort, protocol, intport, extport, desc) + + return <-mappedPort } -func (dev *Router) mapPort(protocol string, intport, extport uint16, desc string) { - updater := time.NewTimer(mapUpdate) - defer func() { - updater.Stop() +// keepPortMapping runs in the background to keep a port mapped. It renews the +// the port mapping in mapUpdateTimeout. +func (dev *Router) keepPortMapping(mappedPort chan<- uint16, protocol string, + intport, extport uint16, desc string) { + updateTimer := time.NewTimer(mapUpdateTimeout) + var port uint16 = 0 - dev.log.Info("Unmap protocol %s external port %d", protocol, extport) - dev.errLock.Lock() - dev.errs.Add(dev.r.UnmapPort(protocol, extport)) - dev.errLock.Unlock() + defer func() { + updateTimer.Stop() + + dev.log.Info("Unmap protocol %s external port %d", protocol, port) + if port > 0 { + dev.errLock.Lock() + dev.errs.Add(dev.r.UnmapPort(protocol, port)) + dev.errLock.Unlock() + } dev.wg.Done() }() - if err := dev.r.MapPort(protocol, intport, extport, desc, mapTimeout); err != nil { - dev.log.Error("Map port failed. Protocol %s Internal %d External %d. %s", - protocol, intport, extport, err) - dev.errLock.Lock() - dev.errs.Add(err) - dev.errLock.Unlock() - } else { - dev.log.Info("Mapped Protocol %s Internal %d External %d.", protocol, - intport, extport) + for i := 0; i < maxRetries; i++ { + port = extport + uint16(i) + if dev.r.IsMapped(port, protocol) { + dev.log.Info("Port %d is occupied, retry with the next port", port) + continue + } + if err := dev.r.MapPort(protocol, intport, port, desc, mapTimeout); err != nil { + dev.log.Error("Map port failed. Protocol %s Internal %d External %d. %s", + protocol, intport, port, err) + dev.errLock.Lock() + dev.errs.Add(err) + dev.errLock.Unlock() + } else { + dev.log.Info("Mapped Protocol %s Internal %d External %d.", protocol, + intport, port) + mappedPort <- port + break + } + } + + if port == 0 { + dev.log.Error("Unable to map port %d", extport) + mappedPort <- port + return } for { select { - case <-updater.C: - if err := dev.r.MapPort(protocol, intport, extport, desc, mapTimeout); err != nil { + case <-updateTimer.C: + if err := dev.r.MapPort(protocol, intport, port, desc, mapTimeout); err != nil { dev.log.Error("Renew port mapping failed. Protocol %s Internal %d External %d. %s", - protocol, intport, extport, err) + protocol, intport, port, err) } else { dev.log.Info("Renew port mapping Protocol %s Internal %d External %d.", protocol, - intport, extport) + intport, port) } - updater.Reset(mapUpdate) + updateTimer.Reset(mapUpdateTimeout) case _, _ = <-dev.closer: return } diff --git a/nat/nat_test.go b/nat/nat_test.go deleted file mode 100644 index efa2233..0000000 --- a/nat/nat_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package nat - -// go test -run 'HTTP' - -import ( - "context" - "fmt" - "net/http" - "os" - "os/signal" - "strconv" - "testing" - "time" - - "github.com/ava-labs/gecko/utils/logging" -) - -const ( - externalPort = 9876 - localPort = 8080 -) - -func TestRouter(t *testing.T) { - n := GetNATRouter() - if n == nil { - fmt.Println("NAT Router is nil") - return - } - - ip, err := n.ExternalIP() - if err != nil { - fmt.Printf("Unable to get external IP: %v\n", err) - return - } - fmt.Printf("External Address %s:%d\n", ip.String(), externalPort) -} - -func TestHTTP(t *testing.T) { - config, err := logging.DefaultConfig() - if err != nil { - return - } - factory := logging.NewFactory(config) - defer factory.Close() - - log, err := factory.Make() - if err != nil { - return - } - defer log.Stop() - defer log.StopOnPanic() - - log.Info("Logger Initialized") - - n := GetNATRouter() - if n == nil { - log.Error("Unable to get UPnP Device") - return - } - - ip, err := n.ExternalIP() - if err != nil { - log.Error("Unable to get external IP: %v", err) - return - } - log.Info("External Address %s:%d", ip.String(), externalPort) - - r := NewRouter(log, n) - defer r.UnmapAllPorts() - - r.Map("TCP", localPort, externalPort, "AVA UPnP Test") - - log.Info("Starting HTTP Service") - server := &http.Server{Addr: ":" + strconv.Itoa(localPort)} - http.HandleFunc("/", hello) - go func() { - server.ListenAndServe() - }() - - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt) - - <-stop - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - server.Shutdown(ctx) -} - -func hello(w http.ResponseWriter, req *http.Request) { - fmt.Fprintf(w, "AVA UPnP Test\n") -} diff --git a/nat/public_ip.go b/nat/no_router.go similarity index 64% rename from nat/public_ip.go rename to nat/no_router.go index 3900942..d09731b 100644 --- a/nat/public_ip.go +++ b/nat/no_router.go @@ -8,25 +8,29 @@ import ( const googleDNSServer = "8.8.8.8:80" -type publicIP struct { +type noRouter struct { ip net.IP } -func (publicIP) MapPort(protocol string, intport, extport uint16, desc string, duration time.Duration) error { +func (noRouter) MapPort(protocol string, intport, extport uint16, desc string, duration time.Duration) error { if intport != extport { return fmt.Errorf("cannot map port %d to %d", intport, extport) } return nil } -func (publicIP) UnmapPort(protocol string, extport uint16) error { +func (noRouter) UnmapPort(protocol string, extport uint16) error { return nil } -func (r publicIP) ExternalIP() (net.IP, error) { +func (r noRouter) ExternalIP() (net.IP, error) { return r.ip, nil } +func (noRouter) IsMapped(uint16, string) bool { + return false +} + func getOutboundIP() (net.IP, error) { conn, err := net.Dial("udp", googleDNSServer) if err != nil { @@ -37,12 +41,12 @@ func getOutboundIP() (net.IP, error) { return conn.LocalAddr().(*net.UDPAddr).IP, nil } -func NewPublicIP() *publicIP { +func NewNoRouter() *noRouter { ip, err := getOutboundIP() if err != nil { return nil } - return &publicIP{ + return &noRouter{ ip: ip, } } diff --git a/nat/upnp.go b/nat/upnp.go index eed99fe..943d2e4 100644 --- a/nat/upnp.go +++ b/nat/upnp.go @@ -12,7 +12,6 @@ import ( const ( soapRequestTimeout = 3 * time.Second - mapRetry = 20 ) // upnpClient is the interface used by goupnp for their client implementations @@ -40,6 +39,20 @@ type upnpClient interface { // returns if there is rsip available, nat enabled, or an unexpected error. GetNATRSIPStatus() (newRSIPAvailable bool, natEnabled bool, err error) + + // attempts to get port mapping information give a external port and protocol + GetSpecificPortMappingEntry( + NewRemoteHost string, + NewExternalPort uint16, + NewProtocol string, + ) ( + NewInternalPort uint16, + NewInternalClient string, + NewEnabled bool, + NewPortMappingDescription string, + NewLeaseDuration uint32, + err error, + ) } type upnpRouter struct { @@ -68,9 +81,6 @@ func (r *upnpRouter) localIP() (net.IP, error) { } for _, addr := range addrs { - // this is pretty janky, but it seems to be the best way to get the - // ip mask and properly check if the ip references the device we are - // connected to ipNet, ok := addr.(*net.IPNet) if !ok { continue @@ -105,24 +115,19 @@ func (r *upnpRouter) MapPort(protocol string, intport, extport uint16, } lifetime := uint32(duration / time.Second) - for i := 0; i < mapRetry; i++ { - externalPort := extport + uint16(i) - err = r.client.AddPortMapping("", externalPort, protocol, intport, - ip.String(), true, desc, lifetime) - if err == nil { - fmt.Printf("Mapped external port %d to local %s:%d\n", externalPort, - ip.String(), intport) - return nil - } - fmt.Printf("Unable to map port, retry with port %d\n", externalPort+1) - } - return err + return r.client.AddPortMapping("", extport, protocol, intport, + ip.String(), true, desc, lifetime) } func (r *upnpRouter) UnmapPort(protocol string, extport uint16) error { return r.client.DeletePortMapping("", extport, protocol) } +func (r *upnpRouter) IsMapped(extport uint16, protocol string) bool { + _, _, enabled, _, _, _ := r.client.GetSpecificPortMappingEntry("", extport, protocol) + return enabled +} + func gateway1(client goupnp.ServiceClient) upnpClient { switch client.Service.ServiceType { case internetgateway1.URN_WANIPConnection_1: @@ -133,6 +138,7 @@ func gateway1(client goupnp.ServiceClient) upnpClient { return nil } } + func gateway2(client goupnp.ServiceClient) upnpClient { switch client.Service.ServiceType { case internetgateway2.URN_WANIPConnection_1: @@ -146,7 +152,7 @@ func gateway2(client goupnp.ServiceClient) upnpClient { } } -func getUpnpClient(client goupnp.ServiceClient) upnpClient { +func getUPnPClient(client goupnp.ServiceClient) upnpClient { c := gateway1(client) if c != nil { return c @@ -164,7 +170,7 @@ func getRootDevice(dev *goupnp.MaybeRootDevice) *upnpRouter { Service: service, } c.SOAPClient.HTTPClient.Timeout = soapRequestTimeout - client := getUpnpClient(c) + client := getUPnPClient(c) if client == nil { return } @@ -220,6 +226,8 @@ func getUPnPRouter() *upnpRouter { return nil } +// getUPnPRouter searches for internet gateway using both Device Control Protocol +// and returns the first one it can find. It returns nil if no UPnP gateway is found func getUPnPRouter() *upnpRouter { targets := []string{ internetgateway1.URN_WANConnectionDevice_1, From 3cfba77c70e8d431d24de67e4a4a9a436c2ba95e Mon Sep 17 00:00:00 2001 From: Hongbo Zhang Date: Sun, 14 Jun 2020 03:51:31 -0400 Subject: [PATCH 07/12] staking internal port and external port could be different get mapped port entry; change interface to mapper --- main/main.go | 8 ++-- main/params.go | 1 + nat/nat.go | 24 +++++++---- nat/no_router.go | 4 +- nat/upnp.go | 108 +++++++++++++++-------------------------------- node/config.go | 11 ++--- node/node.go | 2 +- 7 files changed, 63 insertions(+), 95 deletions(-) diff --git a/main/main.go b/main/main.go index b49d320..1a5e8d4 100644 --- a/main/main.go +++ b/main/main.go @@ -68,11 +68,11 @@ func main() { log.Debug("assertions are enabled. This may slow down execution") } - router := nat.NewRouter(log, Config.Nat) - defer router.UnmapAllPorts() + mapper := nat.NewPortMapper(log, Config.Nat) + defer mapper.UnmapAllPorts() - router.Map("TCP", Config.StakingIP.Port, Config.StakingIP.Port, "gecko") - router.Map("TCP", Config.HTTPPort, Config.HTTPPort, "gecko http") + Config.StakingIP.Port = mapper.Map("TCP", Config.StakingLocalPort, Config.StakingIP.Port, "gecko") + Config.HTTPPort = mapper.Map("TCP", Config.HTTPPort, Config.HTTPPort, "gecko http") node := node.Node{} diff --git a/main/params.go b/main/params.go index 86eca34..b339585 100644 --- a/main/params.go +++ b/main/params.go @@ -303,6 +303,7 @@ func init() { IP: ip, Port: uint16(*consensusPort), } + Config.StakingLocalPort = uint16(*consensusPort) defaultBootstrapIPs, defaultBootstrapIDs := GetDefaultBootstraps(networkID, 5) diff --git a/nat/nat.go b/nat/nat.go index 026113e..6c30808 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -19,7 +19,12 @@ type NATRouter interface { MapPort(protocol string, intport, extport uint16, desc string, duration time.Duration) error UnmapPort(protocol string, extport uint16) error ExternalIP() (net.IP, error) - IsMapped(extport uint16, protocol string) bool + GetPortMappingEntry(extport uint16, protocol string) ( + InternalIP string, + InternalPort uint16, + Description string, + err error, + ) } func GetNATRouter() NATRouter { @@ -31,7 +36,7 @@ func GetNATRouter() NATRouter { return NewNoRouter() } -type Router struct { +type Mapper struct { log logging.Logger r NATRouter closer chan struct{} @@ -40,8 +45,8 @@ type Router struct { errs wrappers.Errs } -func NewRouter(log logging.Logger, r NATRouter) Router { - return Router{ +func NewPortMapper(log logging.Logger, r NATRouter) Mapper { + return Mapper{ log: log, r: r, closer: make(chan struct{}), @@ -51,7 +56,7 @@ func NewRouter(log logging.Logger, r NATRouter) Router { // Map sets up port mapping using given protocol, internal and external ports // and returns the final port mapped. It returns 0 if mapping failed after the // maximun number of retries -func (dev *Router) Map(protocol string, intport, extport uint16, desc string) uint16 { +func (dev *Mapper) Map(protocol string, intport, extport uint16, desc string) uint16 { mappedPort := make(chan uint16) dev.wg.Add(1) @@ -62,7 +67,7 @@ func (dev *Router) Map(protocol string, intport, extport uint16, desc string) ui // keepPortMapping runs in the background to keep a port mapped. It renews the // the port mapping in mapUpdateTimeout. -func (dev *Router) keepPortMapping(mappedPort chan<- uint16, protocol string, +func (dev *Mapper) keepPortMapping(mappedPort chan<- uint16, protocol string, intport, extport uint16, desc string) { updateTimer := time.NewTimer(mapUpdateTimeout) var port uint16 = 0 @@ -82,8 +87,9 @@ func (dev *Router) keepPortMapping(mappedPort chan<- uint16, protocol string, for i := 0; i < maxRetries; i++ { port = extport + uint16(i) - if dev.r.IsMapped(port, protocol) { - dev.log.Info("Port %d is occupied, retry with the next port", port) + if intaddr, intport, desc, err := dev.r.GetPortMappingEntry(port, protocol); err == nil { + dev.log.Info("Port %d is mapped to %s:%d: %s, retry with the next port", + port, intaddr, intport, desc) continue } if err := dev.r.MapPort(protocol, intport, port, desc, mapTimeout); err != nil { @@ -124,7 +130,7 @@ func (dev *Router) keepPortMapping(mappedPort chan<- uint16, protocol string, } } -func (dev *Router) UnmapAllPorts() error { +func (dev *Mapper) UnmapAllPorts() error { close(dev.closer) dev.wg.Wait() dev.log.Info("Unmapped all ports") diff --git a/nat/no_router.go b/nat/no_router.go index d09731b..07ac025 100644 --- a/nat/no_router.go +++ b/nat/no_router.go @@ -27,8 +27,8 @@ func (r noRouter) ExternalIP() (net.IP, error) { return r.ip, nil } -func (noRouter) IsMapped(uint16, string) bool { - return false +func (noRouter) GetPortMappingEntry(uint16, string) (string, uint16, string, error) { + return "", 0, "", nil } func getOutboundIP() (net.IP, error) { diff --git a/nat/upnp.go b/nat/upnp.go index 943d2e4..4e122f4 100644 --- a/nat/upnp.go +++ b/nat/upnp.go @@ -62,7 +62,7 @@ type upnpRouter struct { func (r *upnpRouter) localIP() (net.IP, error) { // attempt to get an address on the router - deviceAddr, err := net.ResolveUDPAddr("udp4", r.dev.URLBase.Host) + deviceAddr, err := net.ResolveUDPAddr("udp", r.dev.URLBase.Host) if err != nil { return nil, err } @@ -73,7 +73,7 @@ func (r *upnpRouter) localIP() (net.IP, error) { return nil, err } - // attempt to find one of my ips that the router would know about + // attempt to find one of my IPs that matches router's record for _, netInterface := range netInterfaces { addrs, err := netInterface.Addrs() if err != nil { @@ -123,102 +123,62 @@ func (r *upnpRouter) UnmapPort(protocol string, extport uint16) error { return r.client.DeletePortMapping("", extport, protocol) } -func (r *upnpRouter) IsMapped(extport uint16, protocol string) bool { - _, _, enabled, _, _, _ := r.client.GetSpecificPortMappingEntry("", extport, protocol) - return enabled +func (r *upnpRouter) GetPortMappingEntry(extport uint16, protocol string) (string, uint16, string, error) { + intport, intaddr, _, desc, _, err := r.client.GetSpecificPortMappingEntry("", extport, protocol) + return intaddr, intport, desc, err } -func gateway1(client goupnp.ServiceClient) upnpClient { +// create UPnP SOAP service client with URN +func getUPnPClient(client goupnp.ServiceClient) upnpClient { switch client.Service.ServiceType { case internetgateway1.URN_WANIPConnection_1: return &internetgateway1.WANIPConnection1{ServiceClient: client} case internetgateway1.URN_WANPPPConnection_1: return &internetgateway1.WANPPPConnection1{ServiceClient: client} - default: - return nil - } -} - -func gateway2(client goupnp.ServiceClient) upnpClient { - switch client.Service.ServiceType { - case internetgateway2.URN_WANIPConnection_1: - return &internetgateway2.WANIPConnection1{ServiceClient: client} case internetgateway2.URN_WANIPConnection_2: return &internetgateway2.WANIPConnection2{ServiceClient: client} - case internetgateway2.URN_WANPPPConnection_1: - return &internetgateway2.WANPPPConnection1{ServiceClient: client} default: return nil } } -func getUPnPClient(client goupnp.ServiceClient) upnpClient { - c := gateway1(client) - if c != nil { - return c - } - return gateway2(client) -} - -func getRootDevice(dev *goupnp.MaybeRootDevice) *upnpRouter { - var router *upnpRouter - dev.Root.Device.VisitServices(func(service *goupnp.Service) { - c := goupnp.ServiceClient{ - SOAPClient: service.NewSOAPClient(), - RootDevice: dev.Root, - Location: dev.Location, - Service: service, - } - c.SOAPClient.HTTPClient.Timeout = soapRequestTimeout - client := getUPnPClient(c) - if client == nil { - return - } - router = &upnpRouter{dev.Root, client} - if router == nil { - return - } - if _, nat, err := router.client.GetNATRSIPStatus(); err != nil || !nat { - router = nil - return - } - }) - return router -} - +// discover() tries to find gateway device func discover(target string) *upnpRouter { devs, err := goupnp.DiscoverDevices(target) if err != nil { return nil } + + router := make(chan *upnpRouter) for i := 0; i < len(devs); i++ { if devs[i].Root == nil { continue } - u := getRootDevice(&devs[i]) - if u != nil { - return u - } - } - return gateway2(client) -} - -func getUPnPRouter() *upnpRouter { - targets := []string{ - internetgateway1.URN_WANConnectionDevice_1, - internetgateway2.URN_WANConnectionDevice_2, + go func(dev *goupnp.MaybeRootDevice) { + var r *upnpRouter = nil + dev.Root.Device.VisitServices(func(service *goupnp.Service) { + c := goupnp.ServiceClient{ + SOAPClient: service.NewSOAPClient(), + RootDevice: dev.Root, + Location: dev.Location, + Service: service, + } + c.SOAPClient.HTTPClient.Timeout = soapRequestTimeout + client := getUPnPClient(c) + if client == nil { + return + } + if _, nat, err := client.GetNATRSIPStatus(); err != nil || !nat { + return + } + r = &upnpRouter{dev.Root, client} + }) + router <- r + }(&devs[i]) } - routers := make(chan *upnpRouter, len(targets)) - - for _, urn := range targets { - go func(urn string) { - routers <- discover(urn) - }(urn) - } - - for i := 0; i < len(targets); i++ { - if r := <-routers; r != nil { + for i := 0; i < len(devs); i++ { + if r := <-router; r != nil { return r } } @@ -234,7 +194,7 @@ func getUPnPRouter() *upnpRouter { internetgateway2.URN_WANConnectionDevice_2, } - routers := make(chan *upnpRouter, len(targets)) + routers := make(chan *upnpRouter) for _, urn := range targets { go func(urn string) { diff --git a/node/config.go b/node/config.go index aaadb7a..e8db1fb 100644 --- a/node/config.go +++ b/node/config.go @@ -33,11 +33,12 @@ type Config struct { DB database.Database // Staking configuration - StakingIP utils.IPDesc - EnableP2PTLS bool - EnableStaking bool - StakingKeyFile string - StakingCertFile string + StakingIP utils.IPDesc + StakingLocalPort uint16 + EnableP2PTLS bool + EnableStaking bool + StakingKeyFile string + StakingCertFile string // Bootstrapping configuration BootstrapPeers []*Peer diff --git a/node/node.go b/node/node.go index 5e817fa..31577ad 100644 --- a/node/node.go +++ b/node/node.go @@ -112,7 +112,7 @@ type Node struct { */ func (n *Node) initNetworking() error { - listener, err := net.Listen(TCP, n.Config.StakingIP.PortString()) + listener, err := net.Listen(TCP, fmt.Sprintf(":%d", n.Config.StakingLocalPort)) if err != nil { return err } From fb51e6a443fefb1192410c2a1fa5dad37da04598 Mon Sep 17 00:00:00 2001 From: Hongbo Zhang Date: Sun, 14 Jun 2020 16:08:09 -0400 Subject: [PATCH 08/12] check failed port mapping handle failed mapping set retry to 20 --- main/main.go | 9 +++-- nat/nat.go | 98 +++++++++++++++++++++++++--------------------------- nat/upnp.go | 14 ++++---- 3 files changed, 59 insertions(+), 62 deletions(-) diff --git a/main/main.go b/main/main.go index 1a5e8d4..97829c1 100644 --- a/main/main.go +++ b/main/main.go @@ -40,10 +40,6 @@ func main() { defer log.StopOnPanic() defer Config.DB.Close() - if Config.StakingIP.IsZero() { - log.Warn("NAT traversal has failed. It will be able to connect to less nodes.") - } - // Track if sybil control is enforced if !Config.EnableStaking && Config.EnableP2PTLS { log.Warn("Staking is disabled. Sybil control is not enforced.") @@ -72,7 +68,10 @@ func main() { defer mapper.UnmapAllPorts() Config.StakingIP.Port = mapper.Map("TCP", Config.StakingLocalPort, Config.StakingIP.Port, "gecko") - Config.HTTPPort = mapper.Map("TCP", Config.HTTPPort, Config.HTTPPort, "gecko http") + + if Config.StakingIP.IsZero() { + log.Warn("NAT traversal has failed. It will be able to connect to less nodes.") + } node := node.Node{} diff --git a/nat/nat.go b/nat/nat.go index 6c30808..76beb2b 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -16,10 +16,10 @@ const ( ) type NATRouter interface { - MapPort(protocol string, intport, extport uint16, desc string, duration time.Duration) error - UnmapPort(protocol string, extport uint16) error + MapPort(protocol string, intPort, extPort uint16, desc string, duration time.Duration) error + UnmapPort(protocol string, extPort uint16) error ExternalIP() (net.IP, error) - GetPortMappingEntry(extport uint16, protocol string) ( + GetPortMappingEntry(extPort uint16, protocol string) ( InternalIP string, InternalPort uint16, Description string, @@ -56,11 +56,10 @@ func NewPortMapper(log logging.Logger, r NATRouter) Mapper { // Map sets up port mapping using given protocol, internal and external ports // and returns the final port mapped. It returns 0 if mapping failed after the // maximun number of retries -func (dev *Mapper) Map(protocol string, intport, extport uint16, desc string) uint16 { +func (dev *Mapper) Map(protocol string, intPort, extPort uint16, desc string) uint16 { mappedPort := make(chan uint16) - dev.wg.Add(1) - go dev.keepPortMapping(mappedPort, protocol, intport, extport, desc) + go dev.keepPortMapping(mappedPort, protocol, intPort, extPort, desc) return <-mappedPort } @@ -68,66 +67,65 @@ func (dev *Mapper) Map(protocol string, intport, extport uint16, desc string) ui // keepPortMapping runs in the background to keep a port mapped. It renews the // the port mapping in mapUpdateTimeout. func (dev *Mapper) keepPortMapping(mappedPort chan<- uint16, protocol string, - intport, extport uint16, desc string) { + intPort, extPort uint16, desc string) { updateTimer := time.NewTimer(mapUpdateTimeout) - var port uint16 = 0 - defer func() { - updateTimer.Stop() - - dev.log.Info("Unmap protocol %s external port %d", protocol, port) - if port > 0 { - dev.errLock.Lock() - dev.errs.Add(dev.r.UnmapPort(protocol, port)) - dev.errLock.Unlock() - } - - dev.wg.Done() - }() - - for i := 0; i < maxRetries; i++ { - port = extport + uint16(i) - if intaddr, intport, desc, err := dev.r.GetPortMappingEntry(port, protocol); err == nil { + for i := 0; i <= maxRetries; i++ { + port := extPort + uint16(i) + if intaddr, intPort, desc, err := dev.r.GetPortMappingEntry(port, protocol); err == nil { dev.log.Info("Port %d is mapped to %s:%d: %s, retry with the next port", - port, intaddr, intport, desc) + port, intaddr, intPort, desc) + port = 0 continue } - if err := dev.r.MapPort(protocol, intport, port, desc, mapTimeout); err != nil { + if err := dev.r.MapPort(protocol, intPort, port, desc, mapTimeout); err != nil { dev.log.Error("Map port failed. Protocol %s Internal %d External %d. %s", - protocol, intport, port, err) + protocol, intPort, port, err) dev.errLock.Lock() dev.errs.Add(err) dev.errLock.Unlock() } else { dev.log.Info("Mapped Protocol %s Internal %d External %d.", protocol, - intport, port) + intPort, port) mappedPort <- port + + dev.wg.Add(1) + + defer func(port uint16) { + updateTimer.Stop() + + dev.log.Info("Unmap protocol %s external port %d", protocol, port) + if port > 0 { + dev.errLock.Lock() + dev.errs.Add(dev.r.UnmapPort(protocol, port)) + dev.errLock.Unlock() + } + + dev.wg.Done() + }(port) + + for { + select { + case <-updateTimer.C: + if err := dev.r.MapPort(protocol, intPort, port, desc, mapTimeout); err != nil { + dev.log.Error("Renew port mapping failed. Protocol %s Internal %d External %d. %s", + protocol, intPort, port, err) + } else { + dev.log.Info("Renew port mapping Protocol %s Internal %d External %d.", protocol, + intPort, port) + } + + updateTimer.Reset(mapUpdateTimeout) + case _, _ = <-dev.closer: + return + } + } break } } - if port == 0 { - dev.log.Error("Unable to map port %d", extport) - mappedPort <- port - return - } - - for { - select { - case <-updateTimer.C: - if err := dev.r.MapPort(protocol, intport, port, desc, mapTimeout); err != nil { - dev.log.Error("Renew port mapping failed. Protocol %s Internal %d External %d. %s", - protocol, intport, port, err) - } else { - dev.log.Info("Renew port mapping Protocol %s Internal %d External %d.", protocol, - intport, port) - } - - updateTimer.Reset(mapUpdateTimeout) - case _, _ = <-dev.closer: - return - } - } + dev.log.Warn("Unable to map port %d~%d", extPort, extPort+maxRetries) + mappedPort <- 0 } func (dev *Mapper) UnmapAllPorts() error { diff --git a/nat/upnp.go b/nat/upnp.go index 4e122f4..863544e 100644 --- a/nat/upnp.go +++ b/nat/upnp.go @@ -107,7 +107,7 @@ func (r *upnpRouter) ExternalIP() (net.IP, error) { return ip, nil } -func (r *upnpRouter) MapPort(protocol string, intport, extport uint16, +func (r *upnpRouter) MapPort(protocol string, intPort, extPort uint16, desc string, duration time.Duration) error { ip, err := r.localIP() if err != nil { @@ -115,17 +115,17 @@ func (r *upnpRouter) MapPort(protocol string, intport, extport uint16, } lifetime := uint32(duration / time.Second) - return r.client.AddPortMapping("", extport, protocol, intport, + return r.client.AddPortMapping("", extPort, protocol, intPort, ip.String(), true, desc, lifetime) } -func (r *upnpRouter) UnmapPort(protocol string, extport uint16) error { - return r.client.DeletePortMapping("", extport, protocol) +func (r *upnpRouter) UnmapPort(protocol string, extPort uint16) error { + return r.client.DeletePortMapping("", extPort, protocol) } -func (r *upnpRouter) GetPortMappingEntry(extport uint16, protocol string) (string, uint16, string, error) { - intport, intaddr, _, desc, _, err := r.client.GetSpecificPortMappingEntry("", extport, protocol) - return intaddr, intport, desc, err +func (r *upnpRouter) GetPortMappingEntry(extPort uint16, protocol string) (string, uint16, string, error) { + intPort, intAddr, _, desc, _, err := r.client.GetSpecificPortMappingEntry("", extPort, protocol) + return intAddr, intPort, desc, err } // create UPnP SOAP service client with URN From 571b6e597be88eced0ff0b373117c01d4475d397 Mon Sep 17 00:00:00 2001 From: Hongbo Zhang Date: Mon, 15 Jun 2020 15:16:24 -0400 Subject: [PATCH 09/12] bring pmp back --- nat/nat.go | 20 +++++++------ nat/no_router.go | 11 ++++--- nat/pmp.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ nat/upnp.go | 5 +++- 4 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 nat/pmp.go diff --git a/nat/nat.go b/nat/nat.go index 76beb2b..9fe97f6 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -1,3 +1,6 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + package nat import ( @@ -10,14 +13,14 @@ import ( ) const ( - mapTimeout = 30 * time.Minute + mapTimeout = 30 * time.Second mapUpdateTimeout = mapTimeout / 2 maxRetries = 20 ) type NATRouter interface { MapPort(protocol string, intPort, extPort uint16, desc string, duration time.Duration) error - UnmapPort(protocol string, extPort uint16) error + UnmapPort(protocol string, intPort, extPort uint16) error ExternalIP() (net.IP, error) GetPortMappingEntry(extPort uint16, protocol string) ( InternalIP string, @@ -28,10 +31,12 @@ type NATRouter interface { } func GetNATRouter() NATRouter { - //TODO add PMP support if r := getUPnPRouter(); r != nil { return r } + if r := getPMPRouter(); r != nil { + return r + } return NewNoRouter() } @@ -95,11 +100,9 @@ func (dev *Mapper) keepPortMapping(mappedPort chan<- uint16, protocol string, updateTimer.Stop() dev.log.Info("Unmap protocol %s external port %d", protocol, port) - if port > 0 { - dev.errLock.Lock() - dev.errs.Add(dev.r.UnmapPort(protocol, port)) - dev.errLock.Unlock() - } + dev.errLock.Lock() + dev.errs.Add(dev.r.UnmapPort(protocol, intPort, port)) + dev.errLock.Unlock() dev.wg.Done() }(port) @@ -120,7 +123,6 @@ func (dev *Mapper) keepPortMapping(mappedPort chan<- uint16, protocol string, return } } - break } } diff --git a/nat/no_router.go b/nat/no_router.go index 07ac025..7a15601 100644 --- a/nat/no_router.go +++ b/nat/no_router.go @@ -1,3 +1,6 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + package nat import ( @@ -12,14 +15,14 @@ type noRouter struct { ip net.IP } -func (noRouter) MapPort(protocol string, intport, extport uint16, desc string, duration time.Duration) error { - if intport != extport { - return fmt.Errorf("cannot map port %d to %d", intport, extport) +func (noRouter) MapPort(_ string, intPort, extPort uint16, _ string, _ time.Duration) error { + if intPort != extPort { + return fmt.Errorf("cannot map port %d to %d", intPort, extPort) } return nil } -func (noRouter) UnmapPort(protocol string, extport uint16) error { +func (noRouter) UnmapPort(string, uint16, uint16) error { return nil } diff --git a/nat/pmp.go b/nat/pmp.go new file mode 100644 index 0000000..7c5a800 --- /dev/null +++ b/nat/pmp.go @@ -0,0 +1,78 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package nat + +import ( + "fmt" + "net" + "time" + + "github.com/jackpal/gateway" + "github.com/jackpal/go-nat-pmp" +) + +var ( + pmpClientTimeout = 500 * time.Millisecond +) + +// natPMPClient adapts the NAT-PMP protocol implementation so it conforms to +// the common interface. +type pmpRouter struct { + client *natpmp.Client +} + +func (pmp *pmpRouter) MapPort( + networkProtocol string, + newInternalPort uint16, + newExternalPort uint16, + mappingName string, + mappingDuration time.Duration) error { + protocol := string(networkProtocol) + internalPort := int(newInternalPort) + externalPort := int(newExternalPort) + // go-nat-pmp uses seconds to denote their lifetime + lifetime := int(mappingDuration / time.Second) + + _, err := pmp.client.AddPortMapping(protocol, internalPort, externalPort, lifetime) + return err +} + +func (pmp *pmpRouter) UnmapPort( + networkProtocol string, + internalPort uint16, + _ uint16) error { + protocol := string(networkProtocol) + internalPortInt := int(internalPort) + + _, err := pmp.client.AddPortMapping(protocol, internalPortInt, 0, 0) + return err +} + +func (pmp *pmpRouter) ExternalIP() (net.IP, error) { + response, err := pmp.client.GetExternalAddress() + if err != nil { + return nil, err + } + return response.ExternalIPAddress[:], nil +} + +// go-nat-pmp does not support port mapping entry query +func (pmp *pmpRouter) GetPortMappingEntry(externalPort uint16, protocol string) ( + string, uint16, string, error) { + return "", 0, "", fmt.Errorf("port mapping entry not found") +} + +func getPMPRouter() *pmpRouter { + gatewayIP, err := gateway.DiscoverGateway() + if err != nil { + return nil + } + + pmp := &pmpRouter{natpmp.NewClientWithTimeout(gatewayIP, pmpClientTimeout)} + if _, err := pmp.ExternalIP(); err != nil { + return nil + } + + return pmp +} diff --git a/nat/upnp.go b/nat/upnp.go index 863544e..3191bc8 100644 --- a/nat/upnp.go +++ b/nat/upnp.go @@ -1,3 +1,6 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + package nat import ( @@ -119,7 +122,7 @@ func (r *upnpRouter) MapPort(protocol string, intPort, extPort uint16, ip.String(), true, desc, lifetime) } -func (r *upnpRouter) UnmapPort(protocol string, extPort uint16) error { +func (r *upnpRouter) UnmapPort(protocol string, _, extPort uint16) error { return r.client.DeletePortMapping("", extPort, protocol) } From 210ad164f3af42f1f41746833a9d5cd7334ef329 Mon Sep 17 00:00:00 2001 From: Hongbo Zhang Date: Tue, 16 Jun 2020 11:29:50 -0400 Subject: [PATCH 10/12] resolve comments for PR 71; change log leves; type check type check ... --- main/main.go | 2 +- main/params.go | 2 +- nat/nat.go | 54 +++++++++++++++++++++++------------------------- nat/no_router.go | 6 +++++- node/config.go | 2 +- 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/main/main.go b/main/main.go index 97829c1..d95b8c7 100644 --- a/main/main.go +++ b/main/main.go @@ -67,7 +67,7 @@ func main() { mapper := nat.NewPortMapper(log, Config.Nat) defer mapper.UnmapAllPorts() - Config.StakingIP.Port = mapper.Map("TCP", Config.StakingLocalPort, Config.StakingIP.Port, "gecko") + Config.StakingIP.Port = mapper.Map("TCP", Config.StakingLocalPort, "gecko") if Config.StakingIP.IsZero() { log.Warn("NAT traversal has failed. It will be able to connect to less nodes.") diff --git a/main/params.go b/main/params.go index b339585..e53ed25 100644 --- a/main/params.go +++ b/main/params.go @@ -281,7 +281,7 @@ func init() { Config.DB = memdb.New() } - Config.Nat = nat.GetNATRouter() + Config.Nat = nat.GetRouter() var ip net.IP // If public IP is not specified, get it using shell command dig diff --git a/nat/nat.go b/nat/nat.go index 9fe97f6..f8f5ca7 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -18,7 +18,7 @@ const ( maxRetries = 20 ) -type NATRouter interface { +type Router interface { MapPort(protocol string, intPort, extPort uint16, desc string, duration time.Duration) error UnmapPort(protocol string, intPort, extPort uint16) error ExternalIP() (net.IP, error) @@ -30,7 +30,7 @@ type NATRouter interface { ) } -func GetNATRouter() NATRouter { +func GetRouter() Router { if r := getUPnPRouter(); r != nil { return r } @@ -43,14 +43,14 @@ func GetNATRouter() NATRouter { type Mapper struct { log logging.Logger - r NATRouter + r Router closer chan struct{} wg sync.WaitGroup errLock sync.Mutex errs wrappers.Errs } -func NewPortMapper(log logging.Logger, r NATRouter) Mapper { +func NewPortMapper(log logging.Logger, r Router) Mapper { return Mapper{ log: log, r: r, @@ -61,10 +61,10 @@ func NewPortMapper(log logging.Logger, r NATRouter) Mapper { // Map sets up port mapping using given protocol, internal and external ports // and returns the final port mapped. It returns 0 if mapping failed after the // maximun number of retries -func (dev *Mapper) Map(protocol string, intPort, extPort uint16, desc string) uint16 { +func (dev *Mapper) Map(protocol string, intPort uint16, desc string) uint16 { mappedPort := make(chan uint16) - go dev.keepPortMapping(mappedPort, protocol, intPort, extPort, desc) + go dev.keepPortMapping(mappedPort, protocol, intPort, desc) return <-mappedPort } @@ -72,50 +72,48 @@ func (dev *Mapper) Map(protocol string, intPort, extPort uint16, desc string) ui // keepPortMapping runs in the background to keep a port mapped. It renews the // the port mapping in mapUpdateTimeout. func (dev *Mapper) keepPortMapping(mappedPort chan<- uint16, protocol string, - intPort, extPort uint16, desc string) { + intPort uint16, desc string) { updateTimer := time.NewTimer(mapUpdateTimeout) for i := 0; i <= maxRetries; i++ { - port := extPort + uint16(i) - if intaddr, intPort, desc, err := dev.r.GetPortMappingEntry(port, protocol); err == nil { - dev.log.Info("Port %d is mapped to %s:%d: %s, retry with the next port", - port, intaddr, intPort, desc) - port = 0 + extPort := intPort + uint16(i) + if intaddr, intPort, desc, err := dev.r.GetPortMappingEntry(extPort, protocol); err == nil { + dev.log.Debug("Port %d is taken by %s:%d: %s, retry with the next port", + extPort, intaddr, intPort, desc) continue } - if err := dev.r.MapPort(protocol, intPort, port, desc, mapTimeout); err != nil { + if err := dev.r.MapPort(protocol, intPort, extPort, desc, mapTimeout); err != nil { dev.log.Error("Map port failed. Protocol %s Internal %d External %d. %s", - protocol, intPort, port, err) + protocol, intPort, extPort, err) dev.errLock.Lock() dev.errs.Add(err) dev.errLock.Unlock() } else { dev.log.Info("Mapped Protocol %s Internal %d External %d.", protocol, - intPort, port) - mappedPort <- port + intPort, extPort) dev.wg.Add(1) - defer func(port uint16) { + mappedPort <- extPort + + defer func(extPort uint16) { updateTimer.Stop() - dev.log.Info("Unmap protocol %s external port %d", protocol, port) - dev.errLock.Lock() - dev.errs.Add(dev.r.UnmapPort(protocol, intPort, port)) - dev.errLock.Unlock() + dev.log.Debug("Unmap protocol %s external port %d", protocol, extPort) + dev.r.UnmapPort(protocol, intPort, extPort) dev.wg.Done() - }(port) + }(extPort) for { select { case <-updateTimer.C: - if err := dev.r.MapPort(protocol, intPort, port, desc, mapTimeout); err != nil { - dev.log.Error("Renew port mapping failed. Protocol %s Internal %d External %d. %s", - protocol, intPort, port, err) + if err := dev.r.MapPort(protocol, intPort, extPort, desc, mapTimeout); err != nil { + dev.log.Error("Renewing port mapping from external port %d to internal port %d failed with %s", + intPort, extPort, err) } else { - dev.log.Info("Renew port mapping Protocol %s Internal %d External %d.", protocol, - intPort, port) + dev.log.Info("Renewed port mapping from external port %d to internal port %d.", + intPort, extPort) } updateTimer.Reset(mapUpdateTimeout) @@ -126,7 +124,7 @@ func (dev *Mapper) keepPortMapping(mappedPort chan<- uint16, protocol string, } } - dev.log.Warn("Unable to map port %d~%d", extPort, extPort+maxRetries) + dev.log.Debug("Unable to map port %d~%d", intPort, intPort+maxRetries) mappedPort <- 0 } diff --git a/nat/no_router.go b/nat/no_router.go index 7a15601..7b7c8d7 100644 --- a/nat/no_router.go +++ b/nat/no_router.go @@ -41,7 +41,11 @@ func getOutboundIP() (net.IP, error) { } defer conn.Close() - return conn.LocalAddr().(*net.UDPAddr).IP, nil + if udpAddr, ok := conn.LocalAddr().(*net.UDPAddr); ok { + return udpAddr.IP, conn.Close() + } + + return nil, fmt.Errorf("getting outbound IP failed") } func NewNoRouter() *noRouter { diff --git a/node/config.go b/node/config.go index e8db1fb..69412a6 100644 --- a/node/config.go +++ b/node/config.go @@ -15,7 +15,7 @@ import ( // Config contains all of the configurations of an Ava node. type Config struct { // protocol to use for opening the network interface - Nat nat.NATRouter + Nat nat.Router // ID of the network this node should connect to NetworkID uint32 From 20637a0f2328c671e6118f35c14bfe67a4d27ae1 Mon Sep 17 00:00:00 2001 From: Hongbo Zhang Date: Wed, 17 Jun 2020 17:13:35 -0400 Subject: [PATCH 11/12] dropped error msg from unmapallport --- nat/nat.go | 22 +++++++--------------- nat/no_router.go | 2 +- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/nat/nat.go b/nat/nat.go index f8f5ca7..8aa9d9e 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -9,7 +9,6 @@ import ( "time" "github.com/ava-labs/gecko/utils/logging" - "github.com/ava-labs/gecko/utils/wrappers" ) const ( @@ -42,12 +41,10 @@ func GetRouter() Router { } type Mapper struct { - log logging.Logger - r Router - closer chan struct{} - wg sync.WaitGroup - errLock sync.Mutex - errs wrappers.Errs + log logging.Logger + r Router + closer chan struct{} + wg sync.WaitGroup } func NewPortMapper(log logging.Logger, r Router) Mapper { @@ -81,13 +78,9 @@ func (dev *Mapper) keepPortMapping(mappedPort chan<- uint16, protocol string, dev.log.Debug("Port %d is taken by %s:%d: %s, retry with the next port", extPort, intaddr, intPort, desc) continue - } - if err := dev.r.MapPort(protocol, intPort, extPort, desc, mapTimeout); err != nil { - dev.log.Error("Map port failed. Protocol %s Internal %d External %d. %s", + } else if err := dev.r.MapPort(protocol, intPort, extPort, desc, mapTimeout); err != nil { + dev.log.Debug("Map port failed. Protocol %s Internal %d External %d. %s", protocol, intPort, extPort, err) - dev.errLock.Lock() - dev.errs.Add(err) - dev.errLock.Unlock() } else { dev.log.Info("Mapped Protocol %s Internal %d External %d.", protocol, intPort, extPort) @@ -128,9 +121,8 @@ func (dev *Mapper) keepPortMapping(mappedPort chan<- uint16, protocol string, mappedPort <- 0 } -func (dev *Mapper) UnmapAllPorts() error { +func (dev *Mapper) UnmapAllPorts() { close(dev.closer) dev.wg.Wait() dev.log.Info("Unmapped all ports") - return dev.errs.Err } diff --git a/nat/no_router.go b/nat/no_router.go index 7b7c8d7..364c338 100644 --- a/nat/no_router.go +++ b/nat/no_router.go @@ -39,12 +39,12 @@ func getOutboundIP() (net.IP, error) { if err != nil { return nil, err } - defer conn.Close() if udpAddr, ok := conn.LocalAddr().(*net.UDPAddr); ok { return udpAddr.IP, conn.Close() } + conn.Close() return nil, fmt.Errorf("getting outbound IP failed") } From a1b1ad2da449435098deec8e3dfebea777773793 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Wed, 17 Jun 2020 22:33:41 -0400 Subject: [PATCH 12/12] address golint errors --- nat/nat.go | 8 +++++++- nat/no_router.go | 3 ++- nat/pmp.go | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/nat/nat.go b/nat/nat.go index 8aa9d9e..8c351c7 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -17,6 +17,8 @@ const ( maxRetries = 20 ) +// Router describes the functionality that a network device must support to be +// able to open ports to an external IP. type Router interface { MapPort(protocol string, intPort, extPort uint16, desc string, duration time.Duration) error UnmapPort(protocol string, intPort, extPort uint16) error @@ -29,6 +31,7 @@ type Router interface { ) } +// GetRouter returns a router on the current network. func GetRouter() Router { if r := getUPnPRouter(); r != nil { return r @@ -40,6 +43,7 @@ func GetRouter() Router { return NewNoRouter() } +// Mapper attempts to open a set of ports on a router type Mapper struct { log logging.Logger r Router @@ -47,6 +51,7 @@ type Mapper struct { wg sync.WaitGroup } +// NewPortMapper returns an initialized mapper func NewPortMapper(log logging.Logger, r Router) Mapper { return Mapper{ log: log, @@ -77,7 +82,6 @@ func (dev *Mapper) keepPortMapping(mappedPort chan<- uint16, protocol string, if intaddr, intPort, desc, err := dev.r.GetPortMappingEntry(extPort, protocol); err == nil { dev.log.Debug("Port %d is taken by %s:%d: %s, retry with the next port", extPort, intaddr, intPort, desc) - continue } else if err := dev.r.MapPort(protocol, intPort, extPort, desc, mapTimeout); err != nil { dev.log.Debug("Map port failed. Protocol %s Internal %d External %d. %s", protocol, intPort, extPort, err) @@ -121,6 +125,8 @@ func (dev *Mapper) keepPortMapping(mappedPort chan<- uint16, protocol string, mappedPort <- 0 } +// UnmapAllPorts stops mapping all ports from this mapper and attempts to unmap +// them. func (dev *Mapper) UnmapAllPorts() { close(dev.closer) dev.wg.Wait() diff --git a/nat/no_router.go b/nat/no_router.go index 364c338..0971b46 100644 --- a/nat/no_router.go +++ b/nat/no_router.go @@ -48,7 +48,8 @@ func getOutboundIP() (net.IP, error) { return nil, fmt.Errorf("getting outbound IP failed") } -func NewNoRouter() *noRouter { +// NewNoRouter returns a router that assumes the network is public +func NewNoRouter() Router { ip, err := getOutboundIP() if err != nil { return nil diff --git a/nat/pmp.go b/nat/pmp.go index 7c5a800..ce40362 100644 --- a/nat/pmp.go +++ b/nat/pmp.go @@ -9,7 +9,8 @@ import ( "time" "github.com/jackpal/gateway" - "github.com/jackpal/go-nat-pmp" + + natpmp "github.com/jackpal/go-nat-pmp" ) var (