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..0780891ef 100644 --- a/node/node.go +++ b/node/node.go @@ -426,8 +426,11 @@ 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.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 cd548866b..3509ec69c 100644 --- a/p2p/listener.go +++ b/p2p/listener.go @@ -7,9 +7,9 @@ import ( "strings" "time" - "github.com/tendermint/tendermint/p2p/upnp" 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 +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(protocol string, lAddr string, UPNP bool, logger log.Logger) Listener { - // Local listen IP & port +func NewDefaultListener( + fullListenAddrString string, + externalAddrString string, + useUPnP bool, + logger log.Logger) Listener { + + // Split protocol, address, and port. + protocol, lAddr := cmn.ProtocolAndAddress(fullListenAddrString) lAddrIP, lAddrPort := splitHostPort(lAddr) // Create listener @@ -88,17 +94,28 @@ func NewDefaultListener(protocol string, lAddr string, UPNP bool, logger log.Log panic(err) } - // Determine external address... + inAddrAny := lAddrIP == "" || lAddrIP == "0.0.0.0" + + // Determine external address. var extAddr *NetAddress - if UPNP { - // If the lAddrIP is INADDR_ANY, try UPnP - if lAddrIP == "" || lAddrIP == "0.0.0.0" { - extAddr = getUPNPExternalAddress(lAddrPort, listenerPort, logger) + + if externalAddrString != "" { + var err error + 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 { - extAddr = getNaiveExternalAddress(listenerPort, false, logger) + defaultToIPv4 := inAddrAny + extAddr = getNaiveExternalAddress(defaultToIPv4, listenerPort, false, logger) } if extAddr == nil { panic("Could not determine external address!") @@ -237,7 +254,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 +265,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 +280,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..f87b5d6f5 100644 --- a/p2p/listener_test.go +++ b/p2p/listener_test.go @@ -2,14 +2,17 @@ package p2p import ( "bytes" + "net" + "strings" "testing" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/log" ) func TestListener(t *testing.T) { // Create a listener - l := NewDefaultListener("tcp", ":8001", false, log.TestingLogger()) + l := NewDefaultListener("tcp://:8001", "", false, log.TestingLogger()) // Dial the listener lAddr := l.ExternalAddress() @@ -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()) }) + } +} diff --git a/p2p/pex/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go index cdef5440a..6d6e91c38 100644 --- a/p2p/pex/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -109,7 +109,7 @@ 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))) + sw.AddListener(p2p.NewDefaultListener("tcp://"+sw.NodeInfo().ListenAddr, "", false, logger.With("pex", i))) err := sw.Start() // start switch and reactors require.Nil(t, err) @@ -229,12 +229,7 @@ func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { }, ) seed.AddListener( - p2p.NewDefaultListener( - "tcp", - seed.NodeInfo().ListenAddr, - false, - log.TestingLogger(), - ), + p2p.NewDefaultListener("tcp://"+seed.NodeInfo().ListenAddr, "", false, log.TestingLogger()), ) require.Nil(t, seed.Start()) defer seed.Stop()