diff --git a/dnsseed/setup.go b/dnsseed/setup.go index 8cd90f2..808eeec 100644 --- a/dnsseed/setup.go +++ b/dnsseed/setup.go @@ -1,8 +1,8 @@ package dnsseed import ( - "fmt" "net" + "net/url" "time" "github.com/caddyserver/caddy" @@ -14,62 +14,59 @@ import ( "github.com/zcashfoundation/dnsseeder/zcash/network" ) +const pluginName = "dnsseed" + var ( - log = clog.NewWithPlugin("dnsseed") + log = clog.NewWithPlugin(pluginName) 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 -// for parsing any extra options the example plugin may have. The first token this function sees is "dnsseed". +// 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 { - var rootArg, networkArg, hostArg string - - 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) + // Automatically configure responsive zone + zone, err := url.Parse(c.Key) 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) if err != nil { - return plugin.Error("dnsseed", err) + return plugin.Error(pluginName, err) } // TODO load from storage if we already know some peers - // 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. + log.Infof("Getting addresses from bootstrap peers %v", bootstrap) - 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 - err = seeder.Connect(address, port) - if err != nil { - return plugin.Error("dnsseed", err) + // Connect to the bootstrap peer + err = seeder.Connect(address, port) + if err != nil { + 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.DisconnectAllPeers() @@ -88,7 +85,7 @@ func setup(c *caddy.Controller) error { dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { return ZcashSeeder{ Next: next, - Zones: []string{zone}, + Zones: []string{zone.Hostname()}, seeder: seeder, } }) @@ -97,6 +94,43 @@ func setup(c *caddy.Controller) error { 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) { log.Infof("[%s] Beginning crawl", time.Now().Format("2006/01/02 15:04:05")) start := time.Now() diff --git a/dnsseed/setup_test.go b/dnsseed/setup_test.go index d8edf34..89a4040 100644 --- a/dnsseed/setup_test.go +++ b/dnsseed/setup_test.go @@ -1,26 +1,55 @@ package dnsseed import ( - "strings" "testing" + "time" "github.com/caddyserver/caddy" + "github.com/zcashfoundation/dnsseeder/zcash/network" ) // TestSetup tests the various things that should be parsed by setup. func TestSetup(t *testing.T) { - c := caddy.NewTestController("dns", "dnsseed yolo.money mainnet 127.0.0.1:8233") - if err := setup(c); err != nil { - if strings.Contains(err.Error(), "connection refused") { - // No local peer running - // TODO: mock a local peer, which will be easier with an API rework in the zcash package - t.SkipNow() - } - t.Fatalf("Expected no errors, but got: %v", err) + tt := []struct { + config string + isCorrect bool + magic network.Network + interval string + bootstrap []string + }{ + {`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") - if err := setup(c); err == nil { - t.Fatalf("Expected errors, but got: %v", err) + for _, test := range tt { + c := caddy.NewTestController("dns", test.config) + 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) + } } -} +} \ No newline at end of file