[NOD-495] Remove DNSSeeder to separate repository

This commit is contained in:
Mike Zak 2019-12-17 13:50:32 +02:00
parent 9b832997f8
commit b855f57371
9 changed files with 0 additions and 1116 deletions

View File

@ -1,15 +0,0 @@
ISC License
Copyright (c) 2018 The Decred developers
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -1,58 +0,0 @@
dnsseeder
=========
## Requirements
Latest version of [Go](http://golang.org) (currently 1.13)
## Getting Started
- Install Go according to the installation instructions here:
http://golang.org/doc/install
- Ensure Go was installed properly and is a supported version:
- Launch a kaspad node for the dnsseeder to connect to
```bash
$ go version
$ go env GOROOT GOPATH
```
NOTE: The `GOROOT` and `GOPATH` above must not be the same path. It is
recommended that `GOPATH` is set to a directory in your home directory such as
`~/dev/go` to avoid write permission issues. It is also recommended to add
`$GOPATH/bin` to your `PATH` at this point.
- Run the following commands to obtain dnsseeder, all dependencies, and install it:
```bash
$ git clone https://github.com/kaspanet/dnsseeder $GOPATH/src/github.com/kaspanet/dnsseeder
$ cd $GOPATH/src/github.com/kaspanet/dnsseeder
$ go install .
```
- dnsseeder will now be installed in either ```$GOROOT/bin``` or
```$GOPATH/bin``` depending on your configuration. If you did not already
add the bin directory to your system path during Go installation, we
recommend you do so now.
To start dnsseeder listening on udp 127.0.0.1:5354 with an initial connection to working testnet node running on 127.0.0.1:
```
$ ./dnsseeder -n nameserver.example.com -H network-seed.example.com -s 127.0.0.1 --testnet
```
You will then need to redirect DNS traffic on your public IP port 53 to 127.0.0.1:5354
Note: to listen directly on port 53 on most Unix systems, one has to run dnsseeder as root, which is discouraged
## Setting up DNS Records
To create a working set-up where dnsseeder can provide IPs to kaspad instances, set the following DNS records:
```
NAME TYPE VALUE
---- ---- -----
[your.domain.name] A [your ip address]
[ns-your.domain.name] NS [your.domain.name]
```

View File

@ -1,147 +0,0 @@
// Copyright (c) 2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"net"
"os"
"path/filepath"
"strings"
"github.com/kaspanet/kaspad/config"
"github.com/pkg/errors"
"github.com/jessevdk/go-flags"
"github.com/kaspanet/kaspad/util"
)
const (
defaultConfigFilename = "dnsseeder.conf"
defaultLogFilename = "dnsseeder.log"
defaultErrLogFilename = "dnsseeder_err.log"
defaultListenPort = "5354"
)
var (
// Default configuration options
defaultHomeDir = util.AppDataDir("dnsseeder", false)
defaultConfigFile = filepath.Join(defaultHomeDir, defaultConfigFilename)
defaultLogFile = filepath.Join(defaultHomeDir, defaultLogFilename)
defaultErrLogFile = filepath.Join(defaultHomeDir, defaultErrLogFilename)
)
var activeConfig *ConfigFlags
// ActiveConfig returns the active configuration struct
func ActiveConfig() *ConfigFlags {
return activeConfig
}
// ConfigFlags holds the configurations set by the command line argument
type ConfigFlags struct {
Host string `short:"H" long:"host" description:"Seed DNS address"`
Listen string `long:"listen" short:"l" description:"Listen on address:port"`
Nameserver string `short:"n" long:"nameserver" description:"hostname of nameserver"`
Seeder string `short:"s" long:"default seeder" description:"IP address of a working node"`
config.NetworkFlags
}
func loadConfig() (*ConfigFlags, error) {
err := os.MkdirAll(defaultHomeDir, 0700)
if err != nil {
// Show a nicer error message if it's because a symlink is
// linked to a directory that does not exist (probably because
// it's not mounted).
if e, ok := err.(*os.PathError); ok && os.IsExist(err) {
if link, lerr := os.Readlink(e.Path); lerr == nil {
str := "is symlink %s -> %s mounted?"
err = errors.Errorf(str, e.Path, link)
}
}
str := "failed to create home directory: %v"
err := errors.Errorf(str, err)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
// Default config.
activeConfig = &ConfigFlags{
Listen: normalizeAddress("localhost", defaultListenPort),
}
preCfg := activeConfig
preParser := flags.NewParser(preCfg, flags.Default)
_, err = preParser.Parse()
if err != nil {
e, ok := err.(*flags.Error)
if ok && e.Type == flags.ErrHelp {
os.Exit(0)
}
preParser.WriteHelp(os.Stderr)
return nil, err
}
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
usageMessage := fmt.Sprintf("Use %s -h to show usage", appName)
// Load additional config from file.
parser := flags.NewParser(activeConfig, flags.Default)
err = flags.NewIniParser(parser).ParseFile(defaultConfigFile)
if err != nil {
if _, ok := err.(*os.PathError); !ok {
fmt.Fprintf(os.Stderr, "Error parsing ConfigFlags "+
"file: %v\n", err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
}
// Parse command line options again to ensure they take precedence.
_, err = parser.Parse()
if err != nil {
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
parser.WriteHelp(os.Stderr)
}
return nil, err
}
if len(activeConfig.Host) == 0 {
str := "Please specify a hostname"
err := errors.Errorf(str)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
if len(activeConfig.Nameserver) == 0 {
str := "Please specify a nameserver"
err := errors.Errorf(str)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
activeConfig.Listen = normalizeAddress(activeConfig.Listen, defaultListenPort)
err = activeConfig.ResolveNetwork(parser)
if err != nil {
return nil, err
}
initLog(defaultLogFile, defaultErrLogFile)
return activeConfig, nil
}
// normalizeAddress returns addr with the passed default port appended if
// there is not already a port specified.
func normalizeAddress(addr, defaultPort string) string {
_, _, err := net.SplitHostPort(addr)
if err != nil {
return net.JoinHostPort(addr, defaultPort)
}
return addr
}

View File

@ -1,238 +0,0 @@
// Copyright (c) 2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"github.com/pkg/errors"
"net"
"os"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/kaspanet/kaspad/connmgr"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/wire"
"github.com/miekg/dns"
)
// DNSServer struct
type DNSServer struct {
hostname string
listen string
nameserver string
}
// Start - starts server
func (d *DNSServer) Start() {
defer wg.Done()
rr := fmt.Sprintf("%s 86400 IN NS %s", d.hostname, d.nameserver)
authority, err := dns.NewRR(rr)
if err != nil {
log.Infof("NewRR: %v", err)
return
}
udpAddr, err := net.ResolveUDPAddr("udp4", d.listen)
if err != nil {
log.Infof("ResolveUDPAddr: %v", err)
return
}
udpListen, err := net.ListenUDP("udp", udpAddr)
if err != nil {
log.Infof("ListenUDP: %v", err)
return
}
defer udpListen.Close()
for {
b := make([]byte, 512)
mainLoop:
err := udpListen.SetReadDeadline(time.Now().Add(time.Second))
if err != nil {
log.Infof("SetReadDeadline: %v", err)
os.Exit(1)
}
_, addr, err := udpListen.ReadFromUDP(b)
if err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() {
if atomic.LoadInt32(&systemShutdown) == 0 {
// use goto in order to do not re-allocate 'b' buffer
goto mainLoop
}
log.Infof("DNS server shutdown")
return
}
log.Infof("Read: %T", err.(*net.OpError).Err)
continue
}
wg.Add(1)
spawn(func() { d.handleDNSRequest(addr, authority, udpListen, b) })
}
}
// NewDNSServer - create DNS server
func NewDNSServer(hostname, nameserver, listen string) *DNSServer {
if hostname[len(hostname)-1] != '.' {
hostname = hostname + "."
}
if nameserver[len(nameserver)-1] != '.' {
nameserver = nameserver + "."
}
return &DNSServer{
hostname: hostname,
listen: listen,
nameserver: nameserver,
}
}
func (d *DNSServer) extractServicesSubnetworkID(addr *net.UDPAddr, domainName string) (wire.ServiceFlag, *subnetworkid.SubnetworkID, bool, error) {
// Domain name may be in following format:
// [n[subnetwork].][xservice.]hostname
// where connmgr.SubnetworkIDPrefixChar and connmgr.ServiceFlagPrefixChar are prefexes
wantedSF := wire.SFNodeNetwork
var subnetworkID *subnetworkid.SubnetworkID
includeAllSubnetworks := true
if d.hostname != domainName {
idx := 0
labels := dns.SplitDomainName(domainName)
if labels[0][0] == connmgr.SubnetworkIDPrefixChar {
includeAllSubnetworks = false
if len(labels[0]) > 1 {
idx = 1
subnetworkID, err := subnetworkid.NewFromStr(labels[0][1:])
if err != nil {
log.Infof("%s: subnetworkid.NewFromStr: %v", addr, err)
return wantedSF, subnetworkID, includeAllSubnetworks, err
}
}
}
if labels[idx][0] == connmgr.ServiceFlagPrefixChar && len(labels[idx]) > 1 {
wantedSFStr := labels[idx][1:]
u, err := strconv.ParseUint(wantedSFStr, 10, 64)
if err != nil {
log.Infof("%s: ParseUint: %v", addr, err)
return wantedSF, subnetworkID, includeAllSubnetworks, err
}
wantedSF = wire.ServiceFlag(u)
}
}
return wantedSF, subnetworkID, includeAllSubnetworks, nil
}
func (d *DNSServer) validateDNSRequest(addr *net.UDPAddr, b []byte) (dnsMsg *dns.Msg, domainName string, atype string, err error) {
dnsMsg = new(dns.Msg)
err = dnsMsg.Unpack(b[:])
if err != nil {
log.Infof("%s: invalid dns message: %v", addr, err)
return nil, "", "", err
}
if len(dnsMsg.Question) != 1 {
str := fmt.Sprintf("%s sent more than 1 question: %d", addr, len(dnsMsg.Question))
log.Infof("%s", str)
return nil, "", "", errors.Errorf("%s", str)
}
domainName = strings.ToLower(dnsMsg.Question[0].Name)
ff := strings.LastIndex(domainName, d.hostname)
if ff < 0 {
str := fmt.Sprintf("invalid name: %s", dnsMsg.Question[0].Name)
log.Infof("%s", str)
return nil, "", "", errors.Errorf("%s", str)
}
atype, err = translateDNSQuestion(addr, dnsMsg)
return dnsMsg, domainName, atype, err
}
func translateDNSQuestion(addr *net.UDPAddr, dnsMsg *dns.Msg) (string, error) {
var atype string
qtype := dnsMsg.Question[0].Qtype
switch qtype {
case dns.TypeA:
atype = "A"
case dns.TypeAAAA:
atype = "AAAA"
case dns.TypeNS:
atype = "NS"
default:
str := fmt.Sprintf("%s: invalid qtype: %d", addr, dnsMsg.Question[0].Qtype)
log.Infof("%s", str)
return "", errors.Errorf("%s", str)
}
return atype, nil
}
func (d *DNSServer) buildDNSResponse(addr *net.UDPAddr, authority dns.RR, dnsMsg *dns.Msg,
wantedSF wire.ServiceFlag, includeAllSubnetworks bool, subnetworkID *subnetworkid.SubnetworkID, atype string) ([]byte, error) {
respMsg := dnsMsg.Copy()
respMsg.Authoritative = true
respMsg.Response = true
qtype := dnsMsg.Question[0].Qtype
if qtype != dns.TypeNS {
respMsg.Ns = append(respMsg.Ns, authority)
addrs := amgr.GoodAddresses(qtype, wantedSF, includeAllSubnetworks, subnetworkID)
for _, a := range addrs {
rr := fmt.Sprintf("%s 30 IN %s %s", dnsMsg.Question[0].Name, atype, a.IP.String())
newRR, err := dns.NewRR(rr)
if err != nil {
log.Infof("%s: NewRR: %v", addr, err)
return nil, err
}
respMsg.Answer = append(respMsg.Answer, newRR)
}
} else {
rr := fmt.Sprintf("%s 86400 IN NS %s", dnsMsg.Question[0].Name, d.nameserver)
newRR, err := dns.NewRR(rr)
if err != nil {
log.Infof("%s: NewRR: %v", addr, err)
return nil, err
}
respMsg.Answer = append(respMsg.Answer, newRR)
}
sendBytes, err := respMsg.Pack()
if err != nil {
log.Infof("%s: failed to pack response: %v", addr, err)
return nil, err
}
return sendBytes, nil
}
func (d *DNSServer) handleDNSRequest(addr *net.UDPAddr, authority dns.RR, udpListen *net.UDPConn, b []byte) {
defer wg.Done()
dnsMsg, domainName, atype, err := d.validateDNSRequest(addr, b)
if err != nil {
return
}
wantedSF, subnetworkID, includeAllSubnetworks, err := d.extractServicesSubnetworkID(addr, domainName)
if err != nil {
return
}
log.Infof("%s: query %d for services %v, subnetwork ID %v",
addr, dnsMsg.Question[0].Qtype, wantedSF, subnetworkID)
sendBytes, err := d.buildDNSResponse(addr, authority, dnsMsg, wantedSF, includeAllSubnetworks, subnetworkID, atype)
if err != nil {
return
}
_, err = udpListen.WriteToUDP(sendBytes, addr)
if err != nil {
log.Infof("%s: failed to write response: %v", addr, err)
return
}
}

View File

@ -1,218 +0,0 @@
// Copyright (c) 2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"net"
"os"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/panics"
"github.com/kaspanet/kaspad/connmgr"
"github.com/kaspanet/kaspad/peer"
"github.com/kaspanet/kaspad/signal"
"github.com/kaspanet/kaspad/wire"
)
const (
// nodeTimeout defines the timeout time waiting for
// a response from a node.
nodeTimeout = time.Second * 3
// requiredServices describes the default services that are
// required to be supported by outbound peers.
requiredServices = wire.SFNodeNetwork
)
var (
amgr *Manager
wg sync.WaitGroup
peersDefaultPort int
systemShutdown int32
)
// hostLookup returns the correct DNS lookup function to use depending on the
// passed host and configuration options. For example, .onion addresses will be
// resolved using the onion specific proxy if one was specified, but will
// otherwise treat the normal proxy as tor unless --noonion was specified in
// which case the lookup will fail. Meanwhile, normal IP addresses will be
// resolved using tor if a proxy was specified unless --noonion was also
// specified in which case the normal system DNS resolver will be used.
func hostLookup(host string) ([]net.IP, error) {
return net.LookupIP(host)
}
func creep() {
defer wg.Done()
onAddr := make(chan struct{})
onVersion := make(chan struct{})
cfg := peer.Config{
UserAgentName: "daglabs-sniffer",
UserAgentVersion: "0.0.1",
DAGParams: ActiveConfig().NetParams(),
DisableRelayTx: true,
SelectedTip: func() *daghash.Hash { return ActiveConfig().NetParams().GenesisBlock.BlockHash() },
Listeners: peer.MessageListeners{
OnAddr: func(p *peer.Peer, msg *wire.MsgAddr) {
added := amgr.AddAddresses(msg.AddrList)
log.Infof("Peer %v sent %v addresses, %d new",
p.Addr(), len(msg.AddrList), added)
onAddr <- struct{}{}
},
OnVersion: func(p *peer.Peer, msg *wire.MsgVersion) {
log.Infof("Adding peer %v with services %v and subnetword ID %v",
p.NA().IP.String(), msg.Services, msg.SubnetworkID)
// Mark this peer as a good node.
amgr.Good(p.NA().IP, msg.Services, msg.SubnetworkID)
// Ask peer for some addresses.
p.QueueMessage(wire.NewMsgGetAddr(true, nil), nil)
// notify that version is received and Peer's subnetwork ID is updated
onVersion <- struct{}{}
},
},
}
var wgCreep sync.WaitGroup
for {
peers := amgr.Addresses()
if len(peers) == 0 && amgr.AddressCount() == 0 {
// Add peers discovered through DNS to the address manager.
connmgr.SeedFromDNS(ActiveConfig().NetParams(), requiredServices, true, nil, hostLookup, func(addrs []*wire.NetAddress) {
amgr.AddAddresses(addrs)
})
peers = amgr.Addresses()
}
if len(peers) == 0 {
log.Infof("No stale addresses -- sleeping for 10 minutes")
for i := 0; i < 600; i++ {
time.Sleep(time.Second)
if atomic.LoadInt32(&systemShutdown) != 0 {
log.Infof("Creep thread shutdown")
return
}
}
continue
}
for _, addr := range peers {
if atomic.LoadInt32(&systemShutdown) != 0 {
log.Infof("Waiting creep threads to terminate")
wgCreep.Wait()
log.Infof("Creep thread shutdown")
return
}
wgCreep.Add(1)
go func(addr *wire.NetAddress) {
defer wgCreep.Done()
host := net.JoinHostPort(addr.IP.String(), strconv.Itoa(int(addr.Port)))
p, err := peer.NewOutboundPeer(&cfg, host)
if err != nil {
log.Warnf("NewOutboundPeer on %v: %v",
host, err)
return
}
amgr.Attempt(addr.IP)
conn, err := net.DialTimeout("tcp", p.Addr(), nodeTimeout)
if err != nil {
log.Warnf("%v", err)
return
}
p.AssociateConnection(conn)
// Wait version messsage or timeout in case of failure.
select {
case <-onVersion:
case <-time.After(nodeTimeout):
log.Warnf("version timeout on peer %v",
p.Addr())
p.Disconnect()
return
}
select {
case <-onAddr:
case <-time.After(nodeTimeout):
log.Warnf("getaddr timeout on peer %v",
p.Addr())
p.Disconnect()
return
}
p.Disconnect()
}(addr)
}
wgCreep.Wait()
}
}
func main() {
defer panics.HandlePanic(log, nil, nil)
cfg, err := loadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "loadConfig: %v\n", err)
os.Exit(1)
}
amgr, err = NewManager(defaultHomeDir)
if err != nil {
fmt.Fprintf(os.Stderr, "NewManager: %v\n", err)
os.Exit(1)
}
peersDefaultPort, err = strconv.Atoi(ActiveConfig().NetParams().DefaultPort)
if err != nil {
fmt.Fprintf(os.Stderr, "Invalid peers default port %s: %v\n", ActiveConfig().NetParams().DefaultPort, err)
os.Exit(1)
}
if len(cfg.Seeder) != 0 {
ip := net.ParseIP(cfg.Seeder)
if ip == nil {
hostAddrs, err := net.LookupHost(cfg.Seeder)
if err != nil {
log.Warnf("Failed to resolve seed host: %v, %v, ignoring", cfg.Seeder, err)
} else {
ip = net.ParseIP(hostAddrs[0])
if ip == nil {
log.Warnf("Failed to resolve seed host: %v, ignoring", cfg.Seeder)
}
}
}
if ip != nil {
amgr.AddAddresses([]*wire.NetAddress{
wire.NewNetAddressIPPort(ip, uint16(peersDefaultPort),
requiredServices)})
}
}
wg.Add(1)
spawn(creep)
dnsServer := NewDNSServer(cfg.Host, cfg.Nameserver, cfg.Listen)
wg.Add(1)
spawn(dnsServer.Start)
defer func() {
log.Infof("Gracefully shutting down the seeder...")
atomic.StoreInt32(&systemShutdown, 1)
close(amgr.quit)
wg.Wait()
amgr.wg.Wait()
log.Infof("Seeder shutdown complete")
}()
// Wait until the interrupt signal is received from an OS signal or
// shutdown is requested through one of the subsystems such as the RPC
// server.
interrupt := signal.InterruptListener()
<-interrupt
}

View File

@ -1,28 +0,0 @@
# -- multistage docker build: stage #1: build stage
FROM golang:1.13-alpine AS build
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
WORKDIR /go/src/github.com/kaspanet/kaspad
RUN apk add --no-cache curl git
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN cd dnsseeder && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o dnsseeder .
# --- multistage docker build: stage #2: runtime image
FROM alpine
WORKDIR /app
RUN apk add --no-cache tini
COPY --from=build /go/src/github.com/kaspanet/kaspad/dnsseeder/ /app/
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/app/dnsseeder"]

View File

@ -1,9 +0,0 @@
1. Since btcd is not public repository still, copy/checkout
https://github.com/kaspanet/kaspad into vendor/github.com/kaspanet/kaspad before
running "docker build".
2. To build docker image invoke following command from dnsseeder directory:
docker build -t dnsseeder -f ./docker/Dockerfile .
3. To run
sudo docker run -u root -p 53:53/udp dnsseeder

View File

@ -1,27 +0,0 @@
package main
import (
"fmt"
"github.com/kaspanet/kaspad/logs"
"github.com/kaspanet/kaspad/util/panics"
"os"
)
var (
backendLog = logs.NewBackend()
log = backendLog.Logger("SEED")
spawn = panics.GoroutineWrapperFunc(log)
)
func initLog(logFile, errLogFile string) {
err := backendLog.AddLogFile(logFile, logs.LevelTrace)
if err != nil {
fmt.Fprintf(os.Stderr, "Error adding log file %s as log rotator for level %s: %s", logFile, logs.LevelTrace, err)
os.Exit(1)
}
err = backendLog.AddLogFile(errLogFile, logs.LevelWarn)
if err != nil {
fmt.Fprintf(os.Stderr, "Error adding log file %s as log rotator for level %s: %s", errLogFile, logs.LevelWarn, err)
os.Exit(1)
}
}

View File

@ -1,376 +0,0 @@
// Copyright (c) 2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"encoding/json"
"github.com/pkg/errors"
"net"
"os"
"path/filepath"
"sync"
"time"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/wire"
"github.com/miekg/dns"
)
// Node repesents a node in the Kaspa network
type Node struct {
Addr *wire.NetAddress
Services wire.ServiceFlag
LastAttempt time.Time
LastSuccess time.Time
LastSeen time.Time
SubnetworkID *subnetworkid.SubnetworkID
}
// Manager is dnsseeder's main worker-type, storing all information required
// for operation
type Manager struct {
mtx sync.RWMutex
nodes map[string]*Node
wg sync.WaitGroup
quit chan struct{}
peersFile string
}
const (
// defaultMaxAddresses is the maximum number of addresses to return.
defaultMaxAddresses = 16
// defaultStaleTimeout is the time in which a host is considered
// stale.
defaultStaleTimeout = time.Hour
// dumpAddressInterval is the interval used to dump the address
// cache to disk for future use.
dumpAddressInterval = time.Second * 30
// peersFilename is the name of the file.
peersFilename = "nodes.json"
// pruneAddressInterval is the interval used to run the address
// pruner.
pruneAddressInterval = time.Minute * 1
// pruneExpireTimeout is the expire time in which a node is
// considered dead.
pruneExpireTimeout = time.Hour * 8
)
var (
// rfc1918Nets specifies the IPv4 private address blocks as defined by
// by RFC1918 (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16).
rfc1918Nets = []net.IPNet{
ipNet("10.0.0.0", 8, 32),
ipNet("172.16.0.0", 12, 32),
ipNet("192.168.0.0", 16, 32),
}
// rfc3964Net specifies the IPv6 to IPv4 encapsulation address block as
// defined by RFC3964 (2002::/16).
rfc3964Net = ipNet("2002::", 16, 128)
// rfc4380Net specifies the IPv6 teredo tunneling over UDP address block
// as defined by RFC4380 (2001::/32).
rfc4380Net = ipNet("2001::", 32, 128)
// rfc4843Net specifies the IPv6 ORCHID address block as defined by
// RFC4843 (2001:10::/28).
rfc4843Net = ipNet("2001:10::", 28, 128)
// rfc4862Net specifies the IPv6 stateless address autoconfiguration
// address block as defined by RFC4862 (FE80::/64).
rfc4862Net = ipNet("FE80::", 64, 128)
// rfc4193Net specifies the IPv6 unique local address block as defined
// by RFC4193 (FC00::/7).
rfc4193Net = ipNet("FC00::", 7, 128)
)
// ipNet returns a net.IPNet struct given the passed IP address string, number
// of one bits to include at the start of the mask, and the total number of bits
// for the mask.
func ipNet(ip string, ones, bits int) net.IPNet {
return net.IPNet{IP: net.ParseIP(ip), Mask: net.CIDRMask(ones, bits)}
}
func isRoutable(addr net.IP) bool {
if ActiveConfig().NetParams().AcceptUnroutable {
return true
}
for _, n := range rfc1918Nets {
if n.Contains(addr) {
return false
}
}
if rfc3964Net.Contains(addr) ||
rfc4380Net.Contains(addr) ||
rfc4843Net.Contains(addr) ||
rfc4862Net.Contains(addr) ||
rfc4193Net.Contains(addr) {
return false
}
return true
}
// NewManager constructs and returns a new dnsseeder manager, with the provided dataDir
func NewManager(dataDir string) (*Manager, error) {
amgr := Manager{
nodes: make(map[string]*Node),
peersFile: filepath.Join(dataDir, peersFilename),
quit: make(chan struct{}),
}
err := amgr.deserializePeers()
if err != nil {
log.Warnf("Failed to parse file %s: %v", amgr.peersFile, err)
// if it is invalid we nuke the old one unconditionally.
err = os.Remove(amgr.peersFile)
if err != nil {
log.Warnf("Failed to remove corrupt peers file %s: %v",
amgr.peersFile, err)
}
}
amgr.wg.Add(1)
spawn(amgr.addressHandler)
return &amgr, nil
}
// AddAddresses adds an address to this dnsseeder manager, and returns the number of
// address currently held
func (m *Manager) AddAddresses(addrs []*wire.NetAddress) int {
var count int
m.mtx.Lock()
for _, addr := range addrs {
if !isRoutable(addr.IP) {
continue
}
addrStr := addr.IP.String()
_, exists := m.nodes[addrStr]
if exists {
m.nodes[addrStr].LastSeen = time.Now()
continue
}
node := Node{
Addr: addr,
LastSeen: time.Now(),
}
m.nodes[addrStr] = &node
count++
}
m.mtx.Unlock()
return count
}
// Addresses returns IPs that need to be tested again.
func (m *Manager) Addresses() []*wire.NetAddress {
addrs := make([]*wire.NetAddress, 0, defaultMaxAddresses*8)
now := time.Now()
i := defaultMaxAddresses
m.mtx.RLock()
for _, node := range m.nodes {
if i == 0 {
break
}
if now.Sub(node.LastSuccess) < defaultStaleTimeout ||
now.Sub(node.LastAttempt) < defaultStaleTimeout {
continue
}
addrs = append(addrs, node.Addr)
i--
}
m.mtx.RUnlock()
return addrs
}
// AddressCount returns number of known nodes.
func (m *Manager) AddressCount() int {
return len(m.nodes)
}
// GoodAddresses returns good working IPs that match both the
// passed DNS query type and have the requested services.
func (m *Manager) GoodAddresses(qtype uint16, services wire.ServiceFlag, includeAllSubnetworks bool, subnetworkID *subnetworkid.SubnetworkID) []*wire.NetAddress {
addrs := make([]*wire.NetAddress, 0, defaultMaxAddresses)
i := defaultMaxAddresses
if qtype != dns.TypeA && qtype != dns.TypeAAAA {
return addrs
}
now := time.Now()
m.mtx.RLock()
for _, node := range m.nodes {
if i == 0 {
break
}
if node.Addr.Port != uint16(peersDefaultPort) {
continue
}
if !includeAllSubnetworks && !node.SubnetworkID.IsEqual(subnetworkID) {
continue
}
if qtype == dns.TypeA && node.Addr.IP.To4() == nil {
continue
} else if qtype == dns.TypeAAAA && node.Addr.IP.To4() != nil {
continue
}
if node.LastSuccess.IsZero() ||
now.Sub(node.LastSuccess) > defaultStaleTimeout {
continue
}
// Does the node have the requested services?
if node.Services&services != services {
continue
}
addrs = append(addrs, node.Addr)
i--
}
m.mtx.RUnlock()
return addrs
}
// Attempt updates the last connection attempt for the specified ip address to now
func (m *Manager) Attempt(ip net.IP) {
m.mtx.Lock()
node, exists := m.nodes[ip.String()]
if exists {
node.LastAttempt = time.Now()
}
m.mtx.Unlock()
}
// Good updates the last successful connection attempt for the specified ip address to now
func (m *Manager) Good(ip net.IP, services wire.ServiceFlag, subnetworkid *subnetworkid.SubnetworkID) {
m.mtx.Lock()
node, exists := m.nodes[ip.String()]
if exists {
node.Services = services
node.LastSuccess = time.Now()
node.SubnetworkID = subnetworkid
}
m.mtx.Unlock()
}
// addressHandler is the main handler for the address manager. It must be run
// as a goroutine.
func (m *Manager) addressHandler() {
defer m.wg.Done()
pruneAddressTicker := time.NewTicker(pruneAddressInterval)
defer pruneAddressTicker.Stop()
dumpAddressTicker := time.NewTicker(dumpAddressInterval)
defer dumpAddressTicker.Stop()
out:
for {
select {
case <-dumpAddressTicker.C:
m.savePeers()
case <-pruneAddressTicker.C:
m.prunePeers()
case <-m.quit:
break out
}
}
log.Infof("Address manager: saving peers")
m.savePeers()
log.Infof("Address manager shoutdown")
}
func (m *Manager) prunePeers() {
var count int
now := time.Now()
m.mtx.Lock()
for k, node := range m.nodes {
if now.Sub(node.LastSeen) > pruneExpireTimeout {
delete(m.nodes, k)
count++
continue
}
if !node.LastSuccess.IsZero() &&
now.Sub(node.LastSuccess) > pruneExpireTimeout {
delete(m.nodes, k)
count++
continue
}
}
l := len(m.nodes)
m.mtx.Unlock()
log.Infof("Pruned %d addresses: %d remaining", count, l)
}
func (m *Manager) deserializePeers() error {
filePath := m.peersFile
_, err := os.Stat(filePath)
if os.IsNotExist(err) {
return nil
}
r, err := os.Open(filePath)
if err != nil {
return errors.Errorf("%s error opening file: %v", filePath, err)
}
defer r.Close()
var nodes map[string]*Node
dec := json.NewDecoder(r)
err = dec.Decode(&nodes)
if err != nil {
return errors.Errorf("error reading %s: %v", filePath, err)
}
l := len(nodes)
m.mtx.Lock()
m.nodes = nodes
m.mtx.Unlock()
log.Infof("%d nodes loaded", l)
return nil
}
func (m *Manager) savePeers() {
m.mtx.RLock()
defer m.mtx.RUnlock()
// Write temporary peers file and then move it into place.
tmpfile := m.peersFile + ".new"
w, err := os.Create(tmpfile)
if err != nil {
log.Errorf("Error opening file %s: %v", tmpfile, err)
return
}
enc := json.NewEncoder(w)
if err := enc.Encode(&m.nodes); err != nil {
log.Errorf("Failed to encode file %s: %v", tmpfile, err)
return
}
if err := w.Close(); err != nil {
log.Errorf("Error closing file %s: %v", tmpfile, err)
return
}
if err := os.Rename(tmpfile, m.peersFile); err != nil {
log.Errorf("Error writing file %s: %v", m.peersFile, err)
return
}
}