dnsseed: add record_ttl config option and improve config testing

This commit is contained in:
George Tankersley 2020-05-25 17:39:20 -04:00
parent bf13b85d1c
commit be7ce4daad
3 changed files with 117 additions and 43 deletions

View File

@ -15,6 +15,7 @@ type ZcashSeeder struct {
Next plugin.Handler
Zones []string
seeder *zcash.Seeder
opts *options
}
// Name satisfies the Handler interface.
@ -55,11 +56,11 @@ func (zs ZcashSeeder) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns
if peerIPs[i].To4() == nil {
rr = new(dns.AAAA)
rr.(*dns.AAAA).Hdr = dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeAAAA, Ttl: 3600, Class: state.QClass()}
rr.(*dns.AAAA).Hdr = dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeAAAA, Ttl: zs.opts.recordTTL, Class: state.QClass()}
rr.(*dns.AAAA).AAAA = peerIPs[i]
} else {
rr = new(dns.A)
rr.(*dns.A).Hdr = dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeA, Ttl: 3600, Class: state.QClass()}
rr.(*dns.A).Hdr = dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeA, Ttl: zs.opts.recordTTL, Class: state.QClass()}
rr.(*dns.A).A = peerIPs[i]
}

View File

@ -2,8 +2,10 @@ package dnsseed
import (
crypto_rand "crypto/rand"
"math"
"net"
"net/url"
"strconv"
"time"
"github.com/caddyserver/caddy"
@ -18,12 +20,21 @@ import (
const pluginName = "dnsseed"
var (
log = clog.NewWithPlugin(pluginName)
updateInterval = 15 * time.Minute
log = clog.NewWithPlugin(pluginName)
defaultUpdateInterval = 15 * time.Minute
defaultTTL uint32 = 3600
)
func init() { plugin.Register(pluginName, setup) }
type options struct {
networkName string
networkMagic network.Network
updateInterval time.Duration
recordTTL uint32
bootstrapPeers []string
}
// 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(pluginName.
func setup(c *caddy.Controller) error {
@ -33,27 +44,26 @@ func setup(c *caddy.Controller) error {
return c.Errf("couldn't parse zone from block identifer: %s", c.Key)
}
magic, interval, bootstrap, err := parseConfig(c)
opts, err := parseConfig(c)
if err != nil {
return err
}
if interval != 0 {
updateInterval = interval
if len(opts.bootstrapPeers) == 0 {
// TODO alternatively, load from storage if we already know some peers
return plugin.Error(pluginName, c.Err("config supplied no bootstrap peers"))
}
// TODO If we wanted to register Prometheus metrics, this would be the place.
seeder, err := zcash.NewSeeder(magic)
seeder, err := zcash.NewSeeder(opts.networkMagic)
if err != nil {
return plugin.Error(pluginName, err)
}
// TODO load from storage if we already know some peers
log.Infof("Getting addresses from bootstrap peers %v", opts.bootstrapPeers)
log.Infof("Getting addresses from bootstrap peers %v", bootstrap)
for _, s := range bootstrap {
for _, s := range opts.bootstrapPeers {
address, port, err := net.SplitHostPort(s)
if err != nil {
return plugin.Error(pluginName, c.Errf("config error: expected 'host:port', got %s", s))
@ -76,11 +86,11 @@ func setup(c *caddy.Controller) error {
// Start the update timer
go func() {
log.Infof("Starting update timer. Will crawl every %.0f minutes.", updateInterval.Minutes())
log.Infof("Starting update timer. Will crawl every %.1f minutes.", opts.updateInterval.Minutes())
randByte := []byte{0}
for {
select {
case <-time.After(updateInterval):
case <-time.After(opts.updateInterval):
runCrawl(seeder)
crypto_rand.Read(randByte[:])
if randByte[0] >= byte(192) {
@ -99,6 +109,7 @@ func setup(c *caddy.Controller) error {
Next: next,
Zones: []string{zone.Hostname()},
seeder: seeder,
opts: opts,
}
})
@ -106,45 +117,66 @@ func setup(c *caddy.Controller) error {
return nil
}
func parseConfig(c *caddy.Controller) (magic network.Network, interval time.Duration, bootstrap []string, err error) {
func parseConfig(c *caddy.Controller) (*options, error) {
opts := &options{
updateInterval: defaultUpdateInterval,
recordTTL: defaultTTL,
}
c.Next() // skip the string "dnsseed"
for c.NextBlock() {
if !c.NextBlock() {
return nil, plugin.Error(pluginName, c.SyntaxErr("expected config block"))
}
for loaded := true; loaded; loaded = c.NextBlock() {
switch c.Val() {
case "network":
if !c.NextArg() {
return 0, 0, nil, plugin.Error(pluginName, c.SyntaxErr("no network specified"))
return nil, plugin.Error(pluginName, c.SyntaxErr("no network specified"))
}
switch c.Val() {
case "mainnet":
magic = network.Mainnet
opts.networkName = "mainnet"
opts.networkMagic = network.Mainnet
case "testnet":
magic = network.Testnet
opts.networkName = "testnet"
opts.networkMagic = network.Testnet
default:
return 0, 0, nil, plugin.Error(pluginName, c.SyntaxErr("networks are {mainnet, testnet}"))
return 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"))
return nil, plugin.Error(pluginName, c.SyntaxErr("no crawl interval specified"))
}
interval, err = time.ParseDuration(c.Val())
interval, err := time.ParseDuration(c.Val())
if err != nil || interval == 0 {
return 0, 0, nil, plugin.Error(pluginName, c.SyntaxErr("bad crawl_interval duration"))
return nil, plugin.Error(pluginName, c.SyntaxErr("bad crawl_interval duration"))
}
opts.updateInterval = interval
case "bootstrap_peers":
bootstrap = c.RemainingArgs()
bootstrap := c.RemainingArgs()
if len(bootstrap) == 0 {
plugin.Error(pluginName, c.SyntaxErr("no bootstrap peers specified"))
return nil, plugin.Error(pluginName, c.SyntaxErr("no bootstrap peers specified"))
}
opts.bootstrapPeers = bootstrap
case "record_ttl":
if !c.NextArg() {
return nil, plugin.Error(pluginName, c.SyntaxErr("no ttl specified"))
}
ttl, err := strconv.Atoi(c.Val())
if err != nil || ttl <= 0 || ttl > math.MaxUint32 {
return nil, plugin.Error(pluginName, c.SyntaxErr("bad ttl"))
}
opts.recordTTL = uint32(ttl)
default:
return 0, 0, nil, plugin.Error(pluginName, c.SyntaxErr("unsupported option"))
return nil, plugin.Error(pluginName, c.SyntaxErr("unsupported option"))
}
}
return
return opts, nil
}
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()
// Slow motion crawl: we'll get them all eventually!
@ -163,7 +195,7 @@ func runCrawl(seeder *zcash.Seeder) {
elapsed := time.Now().Sub(start).Truncate(time.Second).Seconds()
log.Infof(
"[%s] Crawl complete, met %d new peers of %d in %.2f seconds",
"[%s] Crawl complete, met %d new peers of %d in %.0f seconds",
time.Now().Format("2006/01/02 15:04:05"),
newPeerCount,
seeder.GetPeerCount(),

View File

@ -12,44 +12,85 @@ import (
func TestSetup(t *testing.T) {
tt := []struct {
config string
isCorrect bool
validConfig bool
magic network.Network
interval string
interval time.Duration
bootstrap []string
ttl uint32
}{
{`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`, false, 0, 0, []string{}, 0},
{`dnsseed mainnet`, false, 0, 0, []string{}, 0},
{`dnsseed { }`, false, 0, 0, []string{}, 0},
{`dnsseed { network }`, false, 0, 0, []string{}, 0},
{`dnsseed { network mainnet }`, true, network.Mainnet, defaultUpdateInterval, []string{}, defaultTTL},
{`dnsseed {
network testnet
crawl_interval 15s
bootstrap_peers
}`, true, network.Testnet, (time.Duration(15) * time.Second).String(), []string{},
}`,
false, 0, 0, []string{}, 0,
},
{`dnsseed {
network testnet
crawl_interval
bootstrap_peers 127.0.0.1:8233
}`,
false, 0, 0, []string{}, 0,
},
{`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"},
}`,
true, network.Testnet, time.Duration(15) * time.Second, []string{"127.0.0.1:8233"}, defaultTTL,
},
{`dnsseed {
network testnet
crawl_interval 15s
bootstrap_peers 127.0.0.1:8233
boop snoot every 15s
}`,
false, 0, 0, []string{}, 0,
},
{`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"},
record_ttl 300
}`,
true, network.Mainnet, time.Duration(30) * time.Minute, []string{"127.0.0.1:8233", "127.0.0.2:8233"}, 300,
},
}
for _, test := range tt {
c := caddy.NewTestController("dns", test.config)
magic, interval, bootstrap, err := parseConfig(c)
if err != nil && test.isCorrect {
opts, err := parseConfig(c)
if (err == nil) != test.validConfig {
t.Errorf("Unexpected error in test case `%s`: %v", test.config, err)
t.FailNow()
}
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)
if err != nil && !test.validConfig {
// bad parse, as expected
continue
}
if opts.networkMagic != test.magic {
t.Errorf("Input: %s wrong network magic", test.config)
}
if opts.updateInterval != test.interval {
t.Errorf("Input: %s wrong update interval", test.config)
}
for i, s := range opts.bootstrapPeers {
if s != test.bootstrap[i] {
t.Errorf("Input: %s wrong bootstrap peer", test.config)
}
}
if opts.recordTTL != test.ttl {
t.Errorf("Input: %s wrong TTL", test.config)
}
}
}