Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

p2p: support external ip and host #333

Merged
merged 3 commits into from
Apr 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 56 additions & 16 deletions app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,39 +57,68 @@ func TestPingCluster(t *testing.T) {
})
})

// Nodes bind to non-ENR addresses, with only single external bootnode.
// Discv5 will resolve peers via external node.
t.Run("exteral_bootnode_only", func(t *testing.T) {
// Nodes bind to random localhost ports (not the manifest ENRs), with only single bootnode.
// Discv5 will resolve peers via bootnode.
t.Run("bootnode_only", func(t *testing.T) {
bootnode := startExtBootnode(t)

pingCluster(t, pingTest{
BindLocalhost: true,
BootManifest: false,
Bootnodes: []string{bootnode.URLv4()},
})
})

// Nodes bind to random 0.0.0.0 ports (but use 127.0.0.1 as external IP), with only single bootnode.
// Discv5 will resolve peers via bootnode and external IP.
t.Run("external_ip", func(t *testing.T) {
bootnode := startExtBootnode(t)

pingCluster(t, pingTest{
ExternalIP: "127.0.0.1",
BindZero: true,
BootManifest: false,
Bootnodes: []string{bootnode.URLv4()},
})
})

// Nodes bind to random 0.0.0.0 ports (but use localhost as external host), with only single bootnode.
// Discv5 will resolve peers via bootnode and external host.
t.Run("external_host", func(t *testing.T) {
external := startExtBootnode(t)

pingCluster(t, pingTest{
Slow: false,
BindENRAddrs: false,
ExternalHost: "localhost",
BindZero: true,
BootManifest: false,
Bootnodes: []string{external.URLv4()},
})
})

// Nodes bind to non-ENR addresses, with external bootnode AS WELL AS stale ENRs.
// Nodes bind to non-ENR addresses, with single bootnode AS WELL AS stale ENRs.
// Discv5 times out resolving stale ENRs, then resolves peers via external node.
// This is slow due to discv5 internal timeouts, run with -slow.
t.Run("external_and_stale_enrs", func(t *testing.T) {
t.Run("bootnode_and_stale_enrs", func(t *testing.T) {
external := startExtBootnode(t)

pingCluster(t, pingTest{
Slow: true,
BindENRAddrs: false,
BootManifest: true,
Bootnodes: []string{external.URLv4()},
Slow: true,
BindLocalhost: true,
BootManifest: true,
Bootnodes: []string{external.URLv4()},
})
})
}

type pingTest struct {
Slow bool
BindENRAddrs bool
BootManifest bool
Bootnodes []string
Slow bool
BindENRAddrs bool
BindLocalhost bool
BindZero bool
BootManifest bool
Bootnodes []string
ExternalIP string
ExternalHost string
}

func pingCluster(t *testing.T, test pingTest) {
Expand Down Expand Up @@ -131,16 +160,27 @@ func pingCluster(t *testing.T, test pingTest) {
P2P: p2p.Config{
UDPBootnodes: test.Bootnodes,
UDPBootManifest: test.BootManifest,
ExteranlHost: test.ExternalHost,
ExternalIP: test.ExternalIP,
},
}

// Either bind to ENR addresses, or bind to random address resulting in stale ENRs
if test.BindENRAddrs {
conf.P2P.TCPAddrs = []string{tcpAddrFromENR(t, manifest.Peers[i].ENR)}
conf.P2P.UDPAddr = udpAddrFromENR(t, manifest.Peers[i].ENR)
} else {
} else if test.BindLocalhost {
conf.P2P.TCPAddrs = []string{testutil.AvailableAddr(t).String()}
conf.P2P.UDPAddr = testutil.AvailableAddr(t).String()
} else if test.BindZero {
addr1 := testutil.AvailableAddr(t)
addr2 := testutil.AvailableAddr(t)
addr1.IP = net.IPv4zero
addr2.IP = net.IPv4zero
conf.P2P.TCPAddrs = []string{addr1.String()}
conf.P2P.UDPAddr = addr2.String()
} else {
require.Fail(t, "no bind flag set")
}

eg.Go(func() error {
Expand Down
10 changes: 6 additions & 4 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ func bindBootnodeFlag(flags *pflag.FlagSet, httpAddr *string) {

func bindP2PFlags(flags *pflag.FlagSet, config *p2p.Config) {
flags.StringVar(&config.DBPath, "p2p-peerdb", "", "Path to store a discv5 peer database. Empty default results in in-memory database.")
flags.StringSliceVar(&config.UDPBootnodes, "p2p-bootnodes", nil, "Comma-separated list of discv5 bootnode URLs or ENRs. Manifest ENRs are used if empty. Example URL: enode://<hex node id>@10.3.58.6:30303?discport=30301")
flags.BoolVar(&config.UDPBootManifest, "p2p-bootmanifest", false, "Enables using manifest ENRs as discv5 boot nodes. Allows skipping explicit bootnodes if key generation ceremony included correct IPs")
flags.StringVar(&config.UDPAddr, "p2p-udp-address", "127.0.0.1:30309", "Listening UDP address (ip and port) for Discv5 discovery")
flags.StringSliceVar(&config.TCPAddrs, "p2p-tcp-address", []string{"127.0.0.1:13900"}, "Comma-separated list of listening TCP addresses (ip and port) for LibP2P traffic")
flags.StringSliceVar(&config.UDPBootnodes, "p2p-bootnodes", nil, "Comma-separated list of discv5 bootnode URLs or ENRs. Manifest ENRs are used if empty. Example URL: enode://<hex node id>@10.3.58.6:30303?discport=30301.")
flags.BoolVar(&config.UDPBootManifest, "p2p-bootmanifest", false, "Enables using manifest ENRs as discv5 boot nodes. Allows skipping explicit bootnodes if key generation ceremony included correct IPs.")
flags.StringVar(&config.UDPAddr, "p2p-udp-address", "127.0.0.1:30309", "Listening UDP address (ip and port) for Discv5 discovery.")
flags.StringVar(&config.ExternalIP, "p2p-external-ip", "", "The IP address advertised by libp2p. This may be used to advertise an external IP.")
flags.StringVar(&config.ExteranlHost, "p2p-external-host", "", "The DNS address advertised by libp2p. This may be used to advertise an external DNS.")
flags.StringSliceVar(&config.TCPAddrs, "p2p-tcp-address", []string{"127.0.0.1:13900"}, "Comma-separated list of listening TCP addresses (ip and port) for LibP2P traffic.")
flags.StringVar(&config.Allowlist, "p2p-allowlist", "", "Comma-separated list of CIDR subnets for allowing only certain peer connections. Example: 192.168.0.0/16 would permit connections to peers on your local network only. The default is to accept all connections.")
flags.StringVar(&config.Denylist, "p2p-denylist", "", "Comma-separated list of CIDR subnets for disallowing certain peer connections. Example: 192.168.0.0/16 would disallow connections to peers on your local network. The default is to accept all connections.")
}
10 changes: 6 additions & 4 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ Flags:
--manifest-file string The path to the manifest file defining distributed validator cluster (default "./charon/manifest.json")
--monitoring-address string Listening address (ip and port) for the monitoring API (prometheus, pprof) (default "127.0.0.1:8088")
--p2p-allowlist string Comma-separated list of CIDR subnets for allowing only certain peer connections. Example: 192.168.0.0/16 would permit connections to peers on your local network only. The default is to accept all connections.
--p2p-bootmanifest Enables using manifest ENRs as discv5 boot nodes. Allows skipping explicit bootnodes if key generation ceremony included correct IPs
--p2p-bootnodes strings Comma-separated list of discv5 bootnode URLs or ENRs. Manifest ENRs are used if empty. Example URL: enode://<hex node id>@10.3.58.6:30303?discport=30301
--p2p-bootmanifest Enables using manifest ENRs as discv5 boot nodes. Allows skipping explicit bootnodes if key generation ceremony included correct IPs.
--p2p-bootnodes strings Comma-separated list of discv5 bootnode URLs or ENRs. Manifest ENRs are used if empty. Example URL: enode://<hex node id>@10.3.58.6:30303?discport=30301.
--p2p-denylist string Comma-separated list of CIDR subnets for disallowing certain peer connections. Example: 192.168.0.0/16 would disallow connections to peers on your local network. The default is to accept all connections.
--p2p-external-host string The DNS address advertised by libp2p. This may be used to advertise an external DNS.
--p2p-external-ip string The IP address advertised by libp2p. This may be used to advertise an external IP.
--p2p-peerdb string Path to store a discv5 peer database. Empty default results in in-memory database.
--p2p-tcp-address strings Comma-separated list of listening TCP addresses (ip and port) for LibP2P traffic (default [127.0.0.1:13900])
--p2p-udp-address string Listening UDP address (ip and port) for Discv5 discovery (default "127.0.0.1:30309")
--p2p-tcp-address strings Comma-separated list of listening TCP addresses (ip and port) for LibP2P traffic. (default [127.0.0.1:13900])
--p2p-udp-address string Listening UDP address (ip and port) for Discv5 discovery. (default "127.0.0.1:30309")
--simnet-beacon-mock Enables an internal mock beacon node for running a simnet.
--simnet-validator-mock Enables an internal mock validator client when running a simnet. Requires simnet-beacon-mock.
--validator-api-address string Listening address (ip and port) for validator-facing traffic proxying the beacon-node API (default "127.0.0.1:3500")
Expand Down
4 changes: 4 additions & 0 deletions p2p/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ type Config struct {
UDPBootManifest bool
// UDPAddr defines the discv5 udp listen address.
UDPAddr string
// ExternalIP is the IP advertised by libp2p.
ExternalIP string
// ExternalHost is the DNS hostname advertised by libp2p.
ExteranlHost string
// TCPAddrs defines the lib-p2p tcp listen addresses.
TCPAddrs []string
// Allowlist defines csv CIDR blocks for lib-p2p allowed connections.
Expand Down
26 changes: 26 additions & 0 deletions p2p/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,31 @@ func NewLocalEnode(config Config, key *ecdsa.PrivateKey) (*enode.LocalNode, *eno
node.SetFallbackIP(udpAddr.IP)
node.SetFallbackUDP(udpAddr.Port)

if config.ExternalIP != "" {
ip := net.ParseIP(config.ExternalIP)
if ip.To4() == nil && ip.To16() == nil {
return nil, nil, errors.New("invalid p2p external ip")
}

node.SetFallbackIP(ip)
node.SetStaticIP(ip)
}

if config.ExteranlHost != "" {
ips, err := net.LookupIP(config.ExteranlHost)
if err != nil || len(ips) == 0 {
return nil, nil, errors.Wrap(err, "could not resolve p2p external host")
}

// Use first IPv4 returned from the resolver.
// TODO(corver): Figure out how to get ipv6 to work
for _, ip := range ips {
if ip.To4() == nil {
continue
}
node.SetFallbackIP(ip)
}
}

return node, db, nil
}
56 changes: 56 additions & 0 deletions p2p/discovery_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright © 2021 Obol Technologies Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package p2p_test

import (
"crypto/ecdsa"
"fmt"
"math/rand"
"strings"
"testing"

"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"

"github.com/obolnetwork/charon/p2p"
"github.com/obolnetwork/charon/testutil"
)

func TestExternalHost(t *testing.T) {
p2pKey, err := ecdsa.GenerateKey(crypto.S256(), rand.New(rand.NewSource(0)))
require.NoError(t, err)

addr1 := testutil.AvailableAddr(t)
addr2 := testutil.AvailableAddr(t)

config := p2p.Config{
UDPAddr: fmt.Sprintf("0.0.0.0:%d", addr1.Port),
ExteranlHost: "localhost",
TCPAddrs: []string{fmt.Sprintf("0.0.0.0:%d", addr2.Port)},
}

localNode, db, err := p2p.NewLocalEnode(config, p2pKey)
require.NoError(t, err)
defer db.Close()

udpNode, err := p2p.NewUDPNode(config, localNode, p2pKey, nil)
if err != nil && strings.Contains(err.Error(), "bind: address already in use") {
// This sometimes happens, not sure how to lock available ports...
t.Skip("couldn't bind to available port")
return
}
require.NoError(t, err)
defer udpNode.Close()
}