dnsseed: connect ServeDNS to the Zcash seeder
This commit is contained in:
parent
f1e7d75e28
commit
77728106cd
|
@ -4,14 +4,15 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
|
"github.com/coredns/coredns/request"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/zcashfoundation/dnsseeder/zcash"
|
"github.com/zcashfoundation/dnsseeder/zcash"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ZcashSeeder discovers IP addresses by asking Zcash peers for them.
|
// ZcashSeeder discovers IP addresses by asking Zcash peers for them.
|
||||||
type ZcashSeeder struct {
|
type ZcashSeeder struct {
|
||||||
Next plugin.Handler
|
Next plugin.Handler
|
||||||
|
Zones []string
|
||||||
seeder *zcash.Seeder
|
seeder *zcash.Seeder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,5 +27,46 @@ func (zs ZcashSeeder) Ready() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (zs ZcashSeeder) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
func (zs ZcashSeeder) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||||
return plugin.NextOrFailure(zs.Name(), zs.Next, ctx, w, r)
|
// Check if it's a question for us
|
||||||
|
state := request.Request{W: w, Req: r}
|
||||||
|
zone := plugin.Zones(zs.Zones).Matches(state.Name())
|
||||||
|
if zone == "" {
|
||||||
|
return plugin.NextOrFailure(zs.Name(), zs.Next, ctx, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
peerIPs := zs.seeder.Addresses(25)
|
||||||
|
|
||||||
|
a := new(dns.Msg)
|
||||||
|
a.SetReply(r)
|
||||||
|
a.Authoritative = true
|
||||||
|
|
||||||
|
a.Extra = make([]dns.RR, 0, len(peerIPs))
|
||||||
|
|
||||||
|
for i := 0; i < len(peerIPs); i++ {
|
||||||
|
var rr dns.RR
|
||||||
|
|
||||||
|
ip := peerIPs[i]
|
||||||
|
|
||||||
|
switch state.QType() {
|
||||||
|
case dns.TypeA:
|
||||||
|
rr = new(dns.A)
|
||||||
|
rr.(*dns.A).Hdr = dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeA, Class: state.QClass()}
|
||||||
|
rr.(*dns.A).A = ip.To4()
|
||||||
|
case dns.TypeAAAA:
|
||||||
|
if ip.To4() != nil {
|
||||||
|
rr = new(dns.AAAA)
|
||||||
|
rr.(*dns.AAAA).Hdr = dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeAAAA, Class: state.QClass()}
|
||||||
|
rr.(*dns.AAAA).AAAA = ip
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return dns.RcodeNotImplemented, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: why don't we offer SRV records? Zcash has a configurable port.
|
||||||
|
|
||||||
|
a.Extra = append(a.Extra, rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteMsg(a)
|
||||||
|
return dns.RcodeSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package dnsseed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy"
|
"github.com/caddyserver/caddy"
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
|
@ -12,18 +13,21 @@ import (
|
||||||
"github.com/zcashfoundation/dnsseeder/zcash/network"
|
"github.com/zcashfoundation/dnsseeder/zcash/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = clog.NewWithPlugin("dnsseed")
|
var (
|
||||||
|
log = clog.NewWithPlugin("dnsseed")
|
||||||
|
updateInterval = 15 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
func init() { plugin.Register("dnsseed", setup) }
|
func init() { plugin.Register("dnsseed", 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 "dnsseed". 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 "dnsseed".
|
||||||
func setup(c *caddy.Controller) error {
|
func setup(c *caddy.Controller) error {
|
||||||
var networkArg, hostArg string
|
var zoneArg, networkArg, hostArg string
|
||||||
|
|
||||||
c.Next() // Ignore "dnsseed" and give us the next token.
|
c.Next() // Ignore "dnsseed" and give us the next token.
|
||||||
|
|
||||||
if !c.Args(&networkArg, &hostArg) {
|
if !c.Args(&zoneArg, &networkArg, &hostArg) {
|
||||||
return plugin.Error("dnsseed", c.ArgErr())
|
return plugin.Error("dnsseed", c.ArgErr())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,17 +53,37 @@ func setup(c *caddy.Controller) error {
|
||||||
return plugin.Error("dnsseed", err)
|
return plugin.Error("dnsseed", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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("dnsseed", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make initial addr request
|
// Send the initial request for more addresses; spawns goroutines to process the responses.
|
||||||
// TODO: begin update timer
|
// Ready() will flip to true once we've received and confirmed at least 10 peers.
|
||||||
|
seeder.RequestAddresses()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-time.After(updateInterval):
|
||||||
|
seeder.RequestAddresses()
|
||||||
|
err := seeder.WaitForAddresses(10, 30*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to refresh addresses: %v", err)
|
||||||
|
}
|
||||||
|
// XXX: If we wanted to crawl independently, this would be the place.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Add the Plugin to CoreDNS, so Servers can use it in their plugin chain.
|
// Add the Plugin to CoreDNS, so Servers can use it in their plugin chain.
|
||||||
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
||||||
return ZcashSeeder{Next: next, seeder: seeder}
|
return ZcashSeeder{
|
||||||
|
Next: next,
|
||||||
|
Zones: []string{zoneArg},
|
||||||
|
seeder: seeder,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// All OK, return a nil error.
|
// All OK, return a nil error.
|
||||||
|
|
|
@ -9,12 +9,12 @@ import (
|
||||||
|
|
||||||
// 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 mainnet 127.0.0.1:8233")
|
c := caddy.NewTestController("dns", "dnsseed mainnet.seeder.yolo.money mainnet 127.0.0.1:8233")
|
||||||
if err := setup(c); err != nil {
|
if err := setup(c); err != nil {
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
if strings.Contains(err.Error(), "connection refused") {
|
||||||
// No local peer running
|
// No local peer running
|
||||||
// TODO: mock a local peer, which will be easier with an API rework in the zcash package
|
// TODO: mock a local peer, which will be easier with an API rework in the zcash package
|
||||||
t.Skip()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
t.Fatalf("Expected no errors, but got: %v", err)
|
t.Fatalf("Expected no errors, but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package zcash
|
package zcash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
mrand "math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -170,7 +171,30 @@ func (bk *AddressBook) waitForAddresses(n int, done chan struct{}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetShuffledAddressList returns a slice of n valid addresses in random order.
|
// GetAddressList returns a slice of n valid addresses in random order.
|
||||||
// func (bk *AddressBook) GetShuffledAddressList(n int) []*Address {
|
// If there aren't enough known addresses, it returns as many as we have.
|
||||||
|
func (bk *AddressBook) shuffleAddressList(n int) []net.IP {
|
||||||
|
bk.addrState.RLock()
|
||||||
|
defer bk.addrState.RUnlock()
|
||||||
|
|
||||||
// }
|
resp := make([]net.IP, 0, len(bk.addrs))
|
||||||
|
|
||||||
|
for _, v := range bk.addrs {
|
||||||
|
if v.blacklisted {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = append(resp, v.netaddr.IP)
|
||||||
|
}
|
||||||
|
|
||||||
|
mrand.Seed(time.Now().UnixNano())
|
||||||
|
mrand.Shuffle(len(resp), func(i, j int) {
|
||||||
|
resp[i], resp[j] = resp[j], resp[i]
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(resp) > n {
|
||||||
|
return resp[:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
|
@ -358,3 +358,8 @@ func (s *Seeder) WaitForAddresses(n int, timeout time.Duration) error {
|
||||||
func (s *Seeder) Ready() bool {
|
func (s *Seeder) Ready() bool {
|
||||||
return s.WaitForAddresses(minimumReadyAddresses, 1*time.Millisecond) == nil
|
return s.WaitForAddresses(minimumReadyAddresses, 1*time.Millisecond) == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Addresses returns a slice of n addresses or as many as we have if it's less than that.
|
||||||
|
func (s *Seeder) Addresses(n int) []net.IP {
|
||||||
|
return s.addrBook.shuffleAddressList(n)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue