dnsseed: implement better config options (#1)

This commit is contained in:
George Tankersley 2020-05-24 21:36:16 -04:00 committed by GitHub
parent f6c1b71dd0
commit 4313ef4778
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 114 additions and 51 deletions

View File

@ -1,8 +1,8 @@
package dnsseed package dnsseed
import ( import (
"fmt"
"net" "net"
"net/url"
"time" "time"
"github.com/caddyserver/caddy" "github.com/caddyserver/caddy"
@ -14,62 +14,59 @@ import (
"github.com/zcashfoundation/dnsseeder/zcash/network" "github.com/zcashfoundation/dnsseeder/zcash/network"
) )
const pluginName = "dnsseed"
var ( var (
log = clog.NewWithPlugin("dnsseed") log = clog.NewWithPlugin(pluginName)
updateInterval = 15 * time.Minute updateInterval = 15 * time.Minute
) )
func init() { plugin.Register("dnsseed", setup) } func init() { plugin.Register(pluginName, setup) }
// setup is the function that gets called when the config parser see the token "dnsseed". Setup is responsible // setup is the function that gets called when the config parser see the token(pluginName. Setup is responsible
// for parsing any extra options the example plugin may have. The first token this function sees is "dnsseed". // for parsing any extra options the example plugin may have. The first token this function sees is(pluginName.
func setup(c *caddy.Controller) error { func setup(c *caddy.Controller) error {
var rootArg, networkArg, hostArg string // Automatically configure responsive zone
zone, err := url.Parse(c.Key)
c.Next() // Ignore "dnsseed" and give us the next token.
if !c.Args(&rootArg, &networkArg, &hostArg) {
return plugin.Error("dnsseed", c.ArgErr())
}
var magic network.Network
switch networkArg {
case "mainnet":
magic = network.Mainnet
case "testnet":
magic = network.Testnet
default:
return plugin.Error("dnsseed", c.Errf("Config error: expected {mainnet, testnet}, got %s", networkArg))
}
// Automatically configure the responsive zone by network
zone := fmt.Sprintf("seeder.%s.%s.", networkArg, rootArg)
address, port, err := net.SplitHostPort(hostArg)
if err != nil { if err != nil {
return plugin.Error("dnsseed", c.Errf("Config error: expected 'host:port', got %s", hostArg)) return c.Errf("couldn't parse zone from block identifer: %s", c.Key)
} }
// XXX: If we wanted to register Prometheus metrics, this would be the place. magic, interval, bootstrap, err := parseConfig(c)
if err != nil {
return err
}
if interval != 0 {
updateInterval = interval
}
// TODO If we wanted to register Prometheus metrics, this would be the place.
seeder, err := zcash.NewSeeder(magic) seeder, err := zcash.NewSeeder(magic)
if err != nil { if err != nil {
return plugin.Error("dnsseed", err) return plugin.Error(pluginName, err)
} }
// TODO load from storage if we already know some peers // TODO load from storage if we already know some peers
// Send the initial request for more addresses; spawns goroutines to process the responses. log.Infof("Getting addresses from bootstrap peers %v", bootstrap)
// Ready() will flip to true once we've received and confirmed at least 10 peers.
log.Infof("Getting addresses from bootstrap peer %s:%s", address, port) for _, s := range bootstrap {
address, port, err := net.SplitHostPort(s)
if err != nil {
return plugin.Error(pluginName, c.Errf("config error: expected 'host:port', got %s", s))
}
// Connect to the bootstrap peer // Connect to the bootstrap peer
err = seeder.Connect(address, port) err = seeder.Connect(address, port)
if err != nil { if err != nil {
return plugin.Error("dnsseed", err) return plugin.Error(pluginName, c.Errf("error connecting to %s:%s: %v", address, port, err))
}
} }
// Send the initial request for more addresses; spawns goroutines to process the responses.
// Ready() will flip to true once we've received and confirmed at least 10 peers.
seeder.RequestAddresses() seeder.RequestAddresses()
seeder.DisconnectAllPeers() seeder.DisconnectAllPeers()
@ -88,7 +85,7 @@ func setup(c *caddy.Controller) error {
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
return ZcashSeeder{ return ZcashSeeder{
Next: next, Next: next,
Zones: []string{zone}, Zones: []string{zone.Hostname()},
seeder: seeder, seeder: seeder,
} }
}) })
@ -97,6 +94,43 @@ func setup(c *caddy.Controller) error {
return nil return nil
} }
func parseConfig(c *caddy.Controller) (magic network.Network, interval time.Duration, bootstrap []string, err error) {
c.Next() // skip the string "dnsseed"
for c.NextBlock() {
switch c.Val() {
case "network":
if !c.NextArg() {
return 0, 0, nil, plugin.Error(pluginName, c.SyntaxErr("no network specified"))
}
switch c.Val() {
case "mainnet":
magic = network.Mainnet
case "testnet":
magic = network.Testnet
default:
return 0, 0, nil, plugin.Error(pluginName, c.SyntaxErr("networks are {mainnet, testnet}"))
}
case "crawl_interval":
if !c.NextArg() {
return 0, 0, nil, plugin.Error(pluginName, c.SyntaxErr("no crawl interval specified"))
}
interval, err = time.ParseDuration(c.Val())
if err != nil || interval == 0 {
return 0, 0, nil, plugin.Error(pluginName, c.SyntaxErr("bad crawl_interval duration"))
}
case "bootstrap_peers":
bootstrap = c.RemainingArgs()
if len(bootstrap) == 0 {
plugin.Error(pluginName, c.SyntaxErr("no bootstrap peers specified"))
}
default:
return 0, 0, nil, plugin.Error(pluginName, c.SyntaxErr("unsupported option"))
}
}
return
}
func runCrawl(seeder *zcash.Seeder) { func runCrawl(seeder *zcash.Seeder) {
log.Infof("[%s] Beginning crawl", time.Now().Format("2006/01/02 15:04:05")) log.Infof("[%s] Beginning crawl", time.Now().Format("2006/01/02 15:04:05"))
start := time.Now() start := time.Now()

View File

@ -1,26 +1,55 @@
package dnsseed package dnsseed
import ( import (
"strings"
"testing" "testing"
"time"
"github.com/caddyserver/caddy" "github.com/caddyserver/caddy"
"github.com/zcashfoundation/dnsseeder/zcash/network"
) )
// TestSetup tests the various things that should be parsed by setup. // TestSetup tests the various things that should be parsed by setup.
func TestSetup(t *testing.T) { func TestSetup(t *testing.T) {
c := caddy.NewTestController("dns", "dnsseed yolo.money mainnet 127.0.0.1:8233") tt := []struct {
if err := setup(c); err != nil { config string
if strings.Contains(err.Error(), "connection refused") { isCorrect bool
// No local peer running magic network.Network
// TODO: mock a local peer, which will be easier with an API rework in the zcash package interval string
t.SkipNow() bootstrap []string
} }{
t.Fatalf("Expected no errors, but got: %v", err) {`dnsseed`, false, 0, "0s", []string{}},
{`dnsseed mainnet`, false, 0, "0s", []string{}},
{`dnsseed { }`, false, 0, "0s", []string{}},
{`dnsseed { network }`, false, 0, "0s", []string{}},
{`dnsseed { network mainnet }`, false, network.Mainnet, "0s", []string{}},
{`dnsseed {
network testnet
crawl_interval 15s
bootstrap_peers
}`, true, network.Testnet, (time.Duration(15) * time.Second).String(), []string{},
},
{`dnsseed {
network testnet
crawl_interval 15s
bootstrap_peers 127.0.0.1:8233
}`, true, network.Testnet, (time.Duration(15) * time.Second).String(), []string{"127.0.0.1:8233"},
},
{`dnsseed {
network mainnet
crawl_interval 30m
bootstrap_peers 127.0.0.1:8233 127.0.0.2:8233
}`, true, network.Mainnet, (time.Duration(30) * time.Minute).String(), []string{"127.0.0.1:8233", "127.0.0.2:8233"},
},
} }
c = caddy.NewTestController("dns", "dnsseed boop snoot") for _, test := range tt {
if err := setup(c); err == nil { c := caddy.NewTestController("dns", test.config)
t.Fatalf("Expected errors, but got: %v", err) magic, interval, bootstrap, err := parseConfig(c)
if err != nil && test.isCorrect {
t.Errorf("Unexpected error in test case `%s`: %v", test.config, err)
}
if magic != test.magic || interval.String() != test.interval || len(test.bootstrap) != len(bootstrap) {
t.Errorf("Input: %s Results: %v, %s, %s, %v", test.config, magic, interval, bootstrap, err)
}
} }
} }