From 1c018d3fd238bf7b9606d5a20344b0545168c75c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 1 Jul 2018 22:21:29 -0400 Subject: [PATCH 1/3] p2p: external address * new config option for external address to advertise * if blank, defaults to best guess from listener * if laddr ip address is also blank, default to IPv4 --- CHANGELOG.md | 2 ++ config/config.go | 4 ++++ config/toml.go | 6 ++++++ node/node.go | 3 +-- p2p/listener.go | 33 ++++++++++++++++++++++++--------- p2p/listener_test.go | 6 +++++- p2p/pex/pex_reactor_test.go | 11 +++++++---- 7 files changed, 49 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d73c949a2..97acb5855 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ FEATURES [metrics](https://tendermint.readthedocs.io/projects/tools/en/develop/metrics.html) guide. - [p2p] Add IPv6 support to peering. +- [p2p] Add `external_address` to config to allow specifying the address for + peers to dial IMPROVEMENT - [rpc/client] Supports https and wss now. diff --git a/config/config.go b/config/config.go index e01819305..22cecf989 100644 --- a/config/config.go +++ b/config/config.go @@ -276,6 +276,9 @@ type P2PConfig struct { // Address to listen for incoming connections ListenAddress string `mapstructure:"laddr"` + // Address to advertise to peers for them to dial + ExternalAddress string `mapstructure:"external_address"` + // Comma separated list of seed nodes to connect to // We only use these if we can’t connect to peers in the addrbook Seeds string `mapstructure:"seeds"` @@ -340,6 +343,7 @@ type P2PConfig struct { func DefaultP2PConfig() *P2PConfig { return &P2PConfig{ ListenAddress: "tcp://0.0.0.0:26656", + ExternalAddress: "", UPNP: false, AddrBook: defaultAddrBookPath, AddrBookStrict: true, diff --git a/config/toml.go b/config/toml.go index 37ff4d7c1..084325baa 100644 --- a/config/toml.go +++ b/config/toml.go @@ -142,6 +142,12 @@ max_open_connections = {{ .RPC.MaxOpenConnections }} # Address to listen for incoming connections laddr = "{{ .P2P.ListenAddress }}" +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "{{ .P2P.ExternalAddress }}" + # Comma separated list of seed nodes to connect to seeds = "{{ .P2P.Seeds }}" diff --git a/node/node.go b/node/node.go index fc05fc32c..fa667d1dc 100644 --- a/node/node.go +++ b/node/node.go @@ -426,8 +426,7 @@ func (n *Node) OnStart() error { } // Create & add listener - protocol, address := cmn.ProtocolAndAddress(n.config.P2P.ListenAddress) - l := p2p.NewDefaultListener(protocol, address, n.config.P2P.UPNP, n.Logger.With("module", "p2p")) + l := p2p.NewDefaultListener(n.config.P2P, n.Logger.With("module", "p2p")) n.sw.AddListener(l) // Generate node PrivKey diff --git a/p2p/listener.go b/p2p/listener.go index cd548866b..339e26dd4 100644 --- a/p2p/listener.go +++ b/p2p/listener.go @@ -7,9 +7,10 @@ import ( "strings" "time" - "github.com/tendermint/tendermint/p2p/upnp" + "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/p2p/upnp" ) // Listener is a network listener for stream-oriented protocols, providing @@ -59,8 +60,10 @@ func splitHostPort(addr string) (host string, port int) { // NewDefaultListener creates a new DefaultListener on lAddr, optionally trying // to determine external address using UPnP. -func NewDefaultListener(protocol string, lAddr string, UPNP bool, logger log.Logger) Listener { - // Local listen IP & port +func NewDefaultListener(cfg *config.P2PConfig, logger log.Logger) Listener { + + // Split protocol, address, and port. + protocol, lAddr := cmn.ProtocolAndAddress(cfg.ListenAddress) lAddrIP, lAddrPort := splitHostPort(lAddr) // Create listener @@ -88,17 +91,29 @@ func NewDefaultListener(protocol string, lAddr string, UPNP bool, logger log.Log panic(err) } + inAddrAny := lAddrIP == "" || lAddrIP == "0.0.0.0" + // Determine external address... var extAddr *NetAddress - if UPNP { + if cfg.UPNP { // If the lAddrIP is INADDR_ANY, try UPnP - if lAddrIP == "" || lAddrIP == "0.0.0.0" { + if inAddrAny { extAddr = getUPNPExternalAddress(lAddrPort, listenerPort, logger) } } + + if cfg.ExternalAddress != "" { + var err error + extAddr, err = NewNetAddressStringWithOptionalID(cfg.ExternalAddress) + if err != nil { + panic(fmt.Sprintf("Error in ExternalAddress: %v", err)) + } + } + // Otherwise just use the local address... if extAddr == nil { - extAddr = getNaiveExternalAddress(listenerPort, false, logger) + defaultToIPv4 := inAddrAny + extAddr = getNaiveExternalAddress(defaultToIPv4, listenerPort, false, logger) } if extAddr == nil { panic("Could not determine external address!") @@ -237,7 +252,7 @@ func isIpv6(ip net.IP) bool { } // TODO: use syscalls: see issue #712 -func getNaiveExternalAddress(port int, settleForLocal bool, logger log.Logger) *NetAddress { +func getNaiveExternalAddress(defaultToIPv4 bool, port int, settleForLocal bool, logger log.Logger) *NetAddress { addrs, err := net.InterfaceAddrs() if err != nil { panic(cmn.Fmt("Could not fetch interface addresses: %v", err)) @@ -248,7 +263,7 @@ func getNaiveExternalAddress(port int, settleForLocal bool, logger log.Logger) * if !ok { continue } - if !isIpv6(ipnet.IP) { + if defaultToIPv4 || !isIpv6(ipnet.IP) { v4 := ipnet.IP.To4() if v4 == nil || (!settleForLocal && v4[0] == 127) { // loopback @@ -263,5 +278,5 @@ func getNaiveExternalAddress(port int, settleForLocal bool, logger log.Logger) * // try again, but settle for local logger.Info("Node may not be connected to internet. Settling for local address") - return getNaiveExternalAddress(port, true, logger) + return getNaiveExternalAddress(defaultToIPv4, port, true, logger) } diff --git a/p2p/listener_test.go b/p2p/listener_test.go index 3d8e40731..3b61c98dc 100644 --- a/p2p/listener_test.go +++ b/p2p/listener_test.go @@ -4,12 +4,16 @@ import ( "bytes" "testing" + "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/libs/log" ) func TestListener(t *testing.T) { // Create a listener - l := NewDefaultListener("tcp", ":8001", false, log.TestingLogger()) + cfg := &config.P2PConfig{ + ListenAddress: "tcp://:8001", + } + l := NewDefaultListener(cfg, log.TestingLogger()) // Dial the listener lAddr := l.ExternalAddress() diff --git a/p2p/pex/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go index cdef5440a..dddad8f10 100644 --- a/p2p/pex/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -109,7 +109,10 @@ func TestPEXReactorRunning(t *testing.T) { addOtherNodeAddrToAddrBook(2, 1) for i, sw := range switches { - sw.AddListener(p2p.NewDefaultListener("tcp", sw.NodeInfo().ListenAddr, false, logger.With("pex", i))) + cfg := &config.P2PConfig{ + ListenAddress: fmt.Sprintf("tcp://%v", sw.NodeInfo().ListenAddr), + } + sw.AddListener(p2p.NewDefaultListener(cfg, logger.With("pex", i))) err := sw.Start() // start switch and reactors require.Nil(t, err) @@ -230,9 +233,9 @@ func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { ) seed.AddListener( p2p.NewDefaultListener( - "tcp", - seed.NodeInfo().ListenAddr, - false, + &config.P2PConfig{ + ListenAddress: fmt.Sprintf("tcp://%v", seed.NodeInfo().ListenAddr), + }, log.TestingLogger(), ), ) From 737c5c065dd4af5c50938278e3f947ff537bd4a8 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 2 Jul 2018 12:18:17 -0400 Subject: [PATCH 2/3] fixes from review --- node/node.go | 6 +++++- p2p/listener.go | 28 +++++++++++++++------------- p2p/listener_test.go | 6 +----- p2p/pex/pex_reactor_test.go | 12 ++---------- 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/node/node.go b/node/node.go index fa667d1dc..0780891ef 100644 --- a/node/node.go +++ b/node/node.go @@ -426,7 +426,11 @@ func (n *Node) OnStart() error { } // Create & add listener - l := p2p.NewDefaultListener(n.config.P2P, n.Logger.With("module", "p2p")) + l := p2p.NewDefaultListener( + n.config.P2P.ListenAddress, + n.config.P2P.ExternalAddress, + n.config.P2P.UPNP, + n.Logger.With("module", "p2p")) n.sw.AddListener(l) // Generate node PrivKey diff --git a/p2p/listener.go b/p2p/listener.go index 339e26dd4..3509ec69c 100644 --- a/p2p/listener.go +++ b/p2p/listener.go @@ -7,7 +7,6 @@ import ( "strings" "time" - "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p/upnp" @@ -60,10 +59,14 @@ func splitHostPort(addr string) (host string, port int) { // NewDefaultListener creates a new DefaultListener on lAddr, optionally trying // to determine external address using UPnP. -func NewDefaultListener(cfg *config.P2PConfig, logger log.Logger) Listener { +func NewDefaultListener( + fullListenAddrString string, + externalAddrString string, + useUPnP bool, + logger log.Logger) Listener { // Split protocol, address, and port. - protocol, lAddr := cmn.ProtocolAndAddress(cfg.ListenAddress) + protocol, lAddr := cmn.ProtocolAndAddress(fullListenAddrString) lAddrIP, lAddrPort := splitHostPort(lAddr) // Create listener @@ -93,24 +96,23 @@ func NewDefaultListener(cfg *config.P2PConfig, logger log.Logger) Listener { inAddrAny := lAddrIP == "" || lAddrIP == "0.0.0.0" - // Determine external address... + // Determine external address. var extAddr *NetAddress - if cfg.UPNP { - // If the lAddrIP is INADDR_ANY, try UPnP - if inAddrAny { - extAddr = getUPNPExternalAddress(lAddrPort, listenerPort, logger) - } - } - if cfg.ExternalAddress != "" { + if externalAddrString != "" { var err error - extAddr, err = NewNetAddressStringWithOptionalID(cfg.ExternalAddress) + extAddr, err = NewNetAddressStringWithOptionalID(externalAddrString) if err != nil { panic(fmt.Sprintf("Error in ExternalAddress: %v", err)) } } - // Otherwise just use the local address... + // If the lAddrIP is INADDR_ANY, try UPnP. + if extAddr == nil && useUPnP && inAddrAny { + extAddr = getUPNPExternalAddress(lAddrPort, listenerPort, logger) + } + + // Otherwise just use the local address. if extAddr == nil { defaultToIPv4 := inAddrAny extAddr = getNaiveExternalAddress(defaultToIPv4, listenerPort, false, logger) diff --git a/p2p/listener_test.go b/p2p/listener_test.go index 3b61c98dc..c82ae3909 100644 --- a/p2p/listener_test.go +++ b/p2p/listener_test.go @@ -4,16 +4,12 @@ import ( "bytes" "testing" - "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/libs/log" ) func TestListener(t *testing.T) { // Create a listener - cfg := &config.P2PConfig{ - ListenAddress: "tcp://:8001", - } - l := NewDefaultListener(cfg, log.TestingLogger()) + l := NewDefaultListener("tcp://:8001", "", false, log.TestingLogger()) // Dial the listener lAddr := l.ExternalAddress() diff --git a/p2p/pex/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go index dddad8f10..6d6e91c38 100644 --- a/p2p/pex/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -109,10 +109,7 @@ func TestPEXReactorRunning(t *testing.T) { addOtherNodeAddrToAddrBook(2, 1) for i, sw := range switches { - cfg := &config.P2PConfig{ - ListenAddress: fmt.Sprintf("tcp://%v", sw.NodeInfo().ListenAddr), - } - sw.AddListener(p2p.NewDefaultListener(cfg, logger.With("pex", i))) + sw.AddListener(p2p.NewDefaultListener("tcp://"+sw.NodeInfo().ListenAddr, "", false, logger.With("pex", i))) err := sw.Start() // start switch and reactors require.Nil(t, err) @@ -232,12 +229,7 @@ func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { }, ) seed.AddListener( - p2p.NewDefaultListener( - &config.P2PConfig{ - ListenAddress: fmt.Sprintf("tcp://%v", seed.NodeInfo().ListenAddr), - }, - log.TestingLogger(), - ), + p2p.NewDefaultListener("tcp://"+seed.NodeInfo().ListenAddr, "", false, log.TestingLogger()), ) require.Nil(t, seed.Start()) defer seed.Stop() From c3504c111e409f9c7783a8ca6bddee218efa4ba1 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 2 Jul 2018 13:04:34 -0400 Subject: [PATCH 3/3] add test --- p2p/listener_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/p2p/listener_test.go b/p2p/listener_test.go index c82ae3909..f87b5d6f5 100644 --- a/p2p/listener_test.go +++ b/p2p/listener_test.go @@ -2,8 +2,11 @@ package p2p import ( "bytes" + "net" + "strings" "testing" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/log" ) @@ -45,3 +48,32 @@ func TestListener(t *testing.T) { // Close the server, no longer needed. l.Stop() } + +func TestExternalAddress(t *testing.T) { + { + // Create a listener with no external addr. Should default + // to local ipv4. + l := NewDefaultListener("tcp://:8001", "", false, log.TestingLogger()) + lAddr := l.ExternalAddress().String() + _, _, err := net.SplitHostPort(lAddr) + require.Nil(t, err) + spl := strings.Split(lAddr, ".") + require.Equal(t, len(spl), 4) + l.Stop() + } + + { + // Create a listener with set external ipv4 addr. + setExAddr := "8.8.8.8:8080" + l := NewDefaultListener("tcp://:8001", setExAddr, false, log.TestingLogger()) + lAddr := l.ExternalAddress().String() + require.Equal(t, lAddr, setExAddr) + l.Stop() + } + + { + // Invalid external addr causes panic + setExAddr := "awrlsckjnal:8080" + require.Panics(t, func() { NewDefaultListener("tcp://:8001", setExAddr, false, log.TestingLogger()) }) + } +}