dnsseed: add record_ttl config option and improve config testing
This commit is contained in:
parent
bf13b85d1c
commit
be7ce4daad
|
@ -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]
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue