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

DNS support for static nodes and discovery #885

Merged
merged 30 commits into from
Nov 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c15fb0a
Update raft to accept a hostname instead of only an IP address
prd-fox Jul 3, 2019
1bb23fe
Export struct members so they can be RLP decoded.
prd-fox Nov 6, 2019
ec1264b
Fix doc typo and make doc use clearer language.
prd-fox Nov 6, 2019
6399709
Add hostname parameter to TOML file and run goimports on badly formatted
prd-fox Nov 7, 2019
b5d5572
Review feedback:
prd-fox Nov 13, 2019
8794b6e
Review feedback:
prd-fox Nov 13, 2019
ed468ab
Review feedback:
prd-fox Nov 13, 2019
1f9160e
Review feedback:
prd-fox Nov 13, 2019
3b2ab89
Fallback to IP address if no hostname specified for discovered nodes.
prd-fox Nov 14, 2019
115a61c
Add document describing DNS in more detail.
prd-fox Nov 18, 2019
5575c0a
Remove debug line and expand document explaining the features of DNS
prd-fox Nov 19, 2019
b412f3f
Rearrange document to make it clearer why DNS is always enabled for
prd-fox Nov 19, 2019
1b85903
Include DNS document in root tree so it appears on the docs site.
prd-fox Nov 19, 2019
cd5dbc9
Merge branch 'master' into dns-support
Krish1979 Nov 19, 2019
854818f
Move out common functionality between enode object creation (IP vs
prd-fox Nov 20, 2019
5004609
Remove commandline page since it only contains changes related to DNS.
prd-fox Nov 20, 2019
1209968
Merge branch 'dns-support' of https://github.com/prd-fox/quorum into …
prd-fox Nov 20, 2019
1750db9
p2p: use the same interface to avoid duplication
trung Nov 20, 2019
6138aca
Remove discovery based DNS support.
prd-fox Nov 22, 2019
7dc874b
Fix newline formatting and update documentation for the removal of
prd-fox Nov 22, 2019
ce3dbf6
Merge branch 'master' into dns-support
prd-fox Nov 22, 2019
987f0c5
Update node parsing tests to include hostnames.
prd-fox Nov 22, 2019
2dcb51c
Merge remote-tracking branch 'prd-fox/dns-support' into dns-support
prd-fox Nov 22, 2019
bfb0781
Removed hostname from discovery test.
prd-fox Nov 22, 2019
a252071
Allow continuous hostname resolution for static nodes, not just at boot
prd-fox Nov 22, 2019
1ff3b3b
Moved a test to not be included in the full suite. This is because the
prd-fox Nov 22, 2019
f18a70e
Update documentation to reflect the fact that defined bootnodes will
prd-fox Nov 25, 2019
2e925bf
Make snapshot struct names clearer as to their contents, instead of "…
prd-fox Nov 27, 2019
04605ce
Add trace log for when hostname resolution fails, letting the user know
prd-fox Nov 27, 2019
883c9e1
Merge branch 'master' into dns-support
jpmsam Nov 27, 2019
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
3 changes: 2 additions & 1 deletion cmd/geth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ func RegisterRaftService(stack *node.Node, ctx *cli.Context, cfg gethConfig, eth
blockTimeMillis := ctx.GlobalInt(utils.RaftBlockTimeFlag.Name)
datadir := ctx.GlobalString(utils.DataDirFlag.Name)
joinExistingId := ctx.GlobalInt(utils.RaftJoinExistingFlag.Name)
useDns := ctx.GlobalBool(utils.RaftDNSEnabledFlag.Name)

raftPort := uint16(ctx.GlobalInt(utils.RaftPortFlag.Name))

Expand Down Expand Up @@ -255,7 +256,7 @@ func RegisterRaftService(stack *node.Node, ctx *cli.Context, cfg gethConfig, eth
}

ethereum := <-ethChan
return raft.New(ctx, ethereum.ChainConfig(), myId, raftPort, joinExisting, blockTimeNanos, ethereum, peers, datadir)
return raft.New(ctx, ethereum.ChainConfig(), myId, raftPort, joinExisting, blockTimeNanos, ethereum, peers, datadir, useDns)
}); err != nil {
utils.Fatalf("Failed to register the Raft service: %v", err)
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,17 @@ var (
utils.EWASMInterpreterFlag,
utils.EVMInterpreterFlag,
configFileFlag,
// Quorum
utils.EnableNodePermissionFlag,
utils.RaftModeFlag,
utils.RaftBlockTimeFlag,
utils.RaftJoinExistingFlag,
utils.RaftPortFlag,
utils.RaftDNSEnabledFlag,
utils.EmitCheckpointsFlag,
utils.IstanbulRequestTimeoutFlag,
utils.IstanbulBlockPeriodFlag,
// End-Quorum
}

rpcFlags = []cli.Flag{
Expand Down
1 change: 1 addition & 0 deletions cmd/geth/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ var AppHelpFlagGroups = []flagGroup{
utils.RaftBlockTimeFlag,
utils.RaftJoinExistingFlag,
utils.RaftPortFlag,
utils.RaftDNSEnabledFlag,
},
},
{
Expand Down
4 changes: 4 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,10 @@ var (
Usage: "The port to bind for the raft transport",
Value: 50400,
}
RaftDNSEnabledFlag = cli.BoolFlag{
Name: "raftdnsenable",
Usage: "Enable DNS resolution of peers",
}

// Quorum
EnableNodePermissionFlag = cli.BoolFlag{
Expand Down
23 changes: 23 additions & 0 deletions docs/Features/dns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# DNS for Quorum

DNS support in Quorum has two distinct areas, usage in the static nodes file and usage in the
node discovery protocol. You are free to use one and not the other, or to mix them as the use case
requires.

## Static nodes

Static nodes are nodes we keep reference to even if the node is not alive, so that is the nodes comes alive,
then we can connect to it. Hostnames are permitted here, and are resolved once at startup. If a static peer goes offline
and its IP address changes, then it is expected that that peer would re-establish the connection in a fully static
network, or have discovery enabled.

## Discovery

DNS is not supported for the discovery protocol. Use a bootnode instead, which can use a DNS name that is repeatedly
resolved.

## Compatibility
For Raft, the whole network must be on version 2.3.1 of Quorum for DNS to function properly; because of this, DNS must
be explicitly enabled using the `--raftdnsenable` flag.
The network will support older nodes mixed with newer nodes if DNS is not enabled via this flag, and it is safe to
enable DNS only on some nodes if all nodes are on at least version 2.3.1. This allows for a clear upgrade path.
4 changes: 3 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ nav:
- Overview: Cakeshop/Overview.md
- Getting Started: Cakeshop/Getting started.md
- Cakeshop FAQ: Cakeshop/Cakeshop FAQ.md
- Product Roadmap: roadmap.md
- Quorum Features:
trung marked this conversation as resolved.
Show resolved Hide resolved
- DNS: Features/dns.md
- Product Roadmap: roadmap.md
- FAQ: FAQ.md

theme:
Expand Down
1 change: 1 addition & 0 deletions p2p/discover/udp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ func TestUDP_findnodeMultiReply(t *testing.T) {
rpclist := make([]rpcNode, len(list))
for i := range list {
rpclist[i] = nodeToRPC(list[i])
list[i] = wrapNode(enode.NewV4(list[i].Pubkey(), list[i].IP(), list[i].TCP(), list[i].UDP(), 0))
}
test.packetIn(nil, neighborsPacket, &neighbors{Expiration: futureExp, Nodes: rpclist[:2]})
test.packetIn(nil, neighborsPacket, &neighbors{Expiration: futureExp, Nodes: rpclist[2:]})
Expand Down
26 changes: 26 additions & 0 deletions p2p/enode/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/log"
"math/bits"
"math/rand"
"net"
Expand Down Expand Up @@ -70,11 +71,36 @@ func (n *Node) Load(k enr.Entry) error {

// IP returns the IP address of the node.
func (n *Node) IP() net.IP {
// QUORUM
// no host is set, so use the IP directly
if n.Host() == "" {
return n.loadIP()
}
// attempt to look up IP addresses if host is a FQDN
lookupIPs, err := net.LookupIP(n.Host())
if err != nil {
log.Debug("hostname couldn't resolve, using IP instead", "hostname", n.Host(), "err", err.Error())
return n.loadIP()
}
// set to first ip by default
return lookupIPs[0]
// END QUORUM
}

func (n *Node) loadIP() net.IP {
var ip net.IP
n.Load((*enr.IP)(&ip))
return ip
}

// Quorum
func (n *Node) Host() string {
var hostname string
n.Load((*enr.Hostname)(&hostname))
return hostname
}
// End-Quorum

// UDP returns the UDP port of the node.
func (n *Node) UDP() int {
var port enr.UDP
Expand Down
46 changes: 28 additions & 18 deletions p2p/enode/urlv4.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,20 @@ func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp, raftPort int) *Node {
if ip != nil {
r.Set(enr.IP(ip))
}
return newV4(pubkey, r, tcp, udp, raftPort)
}

// broken out from `func NewV4` (above) same in upstream go-ethereum, but taken out
// to avoid code duplication b/t NewV4 and NewV4Hostname
func newV4(pubkey *ecdsa.PublicKey, r enr.Record, tcp, udp, raftPort int) *Node {
if udp != 0 {
r.Set(enr.UDP(udp))
}
if tcp != 0 {
r.Set(enr.TCP(tcp))
}

if raftPort != 0 {
if raftPort != 0 { // Quorum
r.Set(enr.RaftPort(raftPort))
}

Expand All @@ -103,10 +109,24 @@ func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp, raftPort int) *Node {
return n
}

// Quorum
prd-fox marked this conversation as resolved.
Show resolved Hide resolved

// NewV4Hostname creates a node from discovery v4 node information. The record
// contained in the node has a zero-length signature. It sets the hostname of
// the node instead of the IP address.
func NewV4Hostname(pubkey *ecdsa.PublicKey, hostname string, tcp, udp, raftPort int) *Node {
var r enr.Record
if hostname != "" {
r.Set(enr.Hostname(hostname))
}
return newV4(pubkey, r, tcp, udp, raftPort)
}

// End-Quorum

func parseComplete(rawurl string) (*Node, error) {
var (
id *ecdsa.PublicKey
ip net.IP
tcpPort, udpPort uint64
)
u, err := url.Parse(rawurl)
Expand All @@ -123,20 +143,8 @@ func parseComplete(rawurl string) (*Node, error) {
if id, err = parsePubkey(u.User.String()); err != nil {
return nil, fmt.Errorf("invalid node ID (%v)", err)
}
// Parse the IP address.
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
return nil, fmt.Errorf("invalid host: %v", err)
}
if ip = net.ParseIP(host); ip == nil {
return nil, errors.New("invalid IP address")
}
// Ensure the IP is 4 bytes long for IPv4 addresses.
if ipv4 := ip.To4(); ipv4 != nil {
ip = ipv4
}
// Parse the port numbers.
if tcpPort, err = strconv.ParseUint(port, 10, 16); err != nil {
if tcpPort, err = strconv.ParseUint(u.Port(), 10, 16); err != nil {
return nil, errors.New("invalid port")
}
udpPort = tcpPort
Expand All @@ -150,17 +158,19 @@ func parseComplete(rawurl string) (*Node, error) {

var node *Node

// Quorum
if qv.Get("raftport") != "" {
raftPort, err := strconv.ParseUint(qv.Get("raftport"), 10, 16)
if err != nil {
return nil, errors.New("invalid raftport in query")
}
node = NewV4(id, ip, int(tcpPort), int(udpPort), int(raftPort))
node = NewV4Hostname(id, u.Hostname(), int(tcpPort), int(udpPort), int(raftPort))
} else {
node = NewV4(id, ip, int(tcpPort), int(udpPort), 0)
node = NewV4Hostname(id, u.Hostname(), int(tcpPort), int(udpPort), 0)
}
return node, nil
// End-Quorum

return node, nil
}

func HexPubkey(h string) (*ecdsa.PublicKey, error) {
Expand Down
36 changes: 22 additions & 14 deletions p2p/enode/urlv4_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"bytes"
"crypto/ecdsa"
"math/big"
"net"
"reflect"
"strings"
"testing"
Expand All @@ -41,10 +40,6 @@ var parseNodeTests = []struct {
wantError: `invalid node ID (wrong length, want 128 hex chars)`,
},
// Complete nodes with IP address.
{
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3",
wantError: `invalid IP address`,
},
{
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo",
wantError: `invalid port`,
Expand All @@ -55,39 +50,39 @@ var parseNodeTests = []struct {
},
{
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150",
wantResult: NewV4(
wantResult: NewV4Hostname(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{0x7f, 0x0, 0x0, 0x1},
"127.0.0.1",
52150,
52150,
0,
),
jpmsam marked this conversation as resolved.
Show resolved Hide resolved
},
{
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150",
wantResult: NewV4(
wantResult: NewV4Hostname(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.ParseIP("::"),
"::",
52150,
52150,
0,
),
},
{
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150",
wantResult: NewV4(
wantResult: NewV4Hostname(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
"2001:db8:3c4d:15::abcd:ef12",
52150,
52150,
0,
),
},
{
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334",
wantResult: NewV4(
wantResult: NewV4Hostname(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{0x7f, 0x0, 0x0, 0x1},
"127.0.0.1",
52150,
22334,
0,
Expand Down Expand Up @@ -133,7 +128,20 @@ func hexPubkey(h string) *ecdsa.PublicKey {
}

func TestParseNode(t *testing.T) {
for _, test := range parseNodeTests {
extraTests := []struct {
rawurl string
wantError string
wantResult *Node
}{
{
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3",
wantResult: NewV4Hostname(hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), "hostname", 3, 3, 0, ),
},
}

testNodes := append(parseNodeTests, extraTests...)

for _, test := range testNodes {
n, err := ParseV4(test.rawurl)
if test.wantError != "" {
if err == nil {
Expand Down
4 changes: 4 additions & 0 deletions p2p/enr/entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ type RaftPort uint16

func (v RaftPort) ENRKey() string { return "raftport" }

type Hostname string

func (v Hostname) ENRKey() string { return "hostname" }

// EncodeRLP implements rlp.Encoder.
func (v IP) EncodeRLP(w io.Writer) error {
if ip4 := net.IP(v).To4(); ip4 != nil {
Expand Down
4 changes: 2 additions & 2 deletions raft/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type RaftService struct {
calcGasLimitFunc func(block *types.Block) uint64
}

func New(ctx *node.ServiceContext, chainConfig *params.ChainConfig, raftId, raftPort uint16, joinExisting bool, blockTime time.Duration, e *eth.Ethereum, startPeers []*enode.Node, datadir string) (*RaftService, error) {
func New(ctx *node.ServiceContext, chainConfig *params.ChainConfig, raftId, raftPort uint16, joinExisting bool, blockTime time.Duration, e *eth.Ethereum, startPeers []*enode.Node, datadir string, useDns bool) (*RaftService, error) {
service := &RaftService{
eventMux: ctx.EventMux,
chainDb: e.ChainDb(),
Expand All @@ -55,7 +55,7 @@ func New(ctx *node.ServiceContext, chainConfig *params.ChainConfig, raftId, raft
service.minter = newMinter(chainConfig, service, blockTime)

var err error
if service.raftProtocolManager, err = NewProtocolManager(raftId, raftPort, service.blockchain, service.eventMux, startPeers, joinExisting, datadir, service.minter, service.downloader); err != nil {
if service.raftProtocolManager, err = NewProtocolManager(raftId, raftPort, service.blockchain, service.eventMux, startPeers, joinExisting, datadir, service.minter, service.downloader, useDns); err != nil {
return nil, err
}

Expand Down
Loading