[NOD-47] Put dnsseeder package inside btcd package (#207)

* [NOD-47] Put dnsseeder package inside btcd package

* [NOD-47] Remove .gitignore and run_tests.sh from dnsseeder package
This commit is contained in:
Ori Newman 2019-03-13 13:15:58 +02:00 committed by Evgeny Khirin
parent c9ef176f4c
commit 55659022bb
10 changed files with 1121 additions and 38 deletions

53
Gopkg.lock generated
View File

@ -2,10 +2,10 @@
[[projects]]
branch = "master"
name = "bou.ke/monkey"
packages = ["."]
revision = "21d0bd9ad94e052d4a8117b0910ba026bec94c99"
revision = "bdf6dea004c6fd1cdf4b25da8ad45a606c09409a"
version = "v1.0.1"
[[projects]]
name = "github.com/aead/siphash"
@ -27,20 +27,7 @@
[[projects]]
name = "github.com/btcsuite/goleveldb"
packages = [
"leveldb",
"leveldb/cache",
"leveldb/comparer",
"leveldb/errors",
"leveldb/filter",
"leveldb/iterator",
"leveldb/journal",
"leveldb/memdb",
"leveldb/opt",
"leveldb/storage",
"leveldb/table",
"leveldb/util"
]
packages = ["leveldb","leveldb/cache","leveldb/comparer","leveldb/errors","leveldb/filter","leveldb/iterator","leveldb/journal","leveldb/memdb","leveldb/opt","leveldb/storage","leveldb/table","leveldb/util"]
revision = "3fd0373267b6461dbefe91cef614278064d05465"
version = "v1.0.0"
@ -58,13 +45,7 @@
[[projects]]
name = "github.com/btcsuite/winsvc"
packages = [
"eventlog",
"mgr",
"registry",
"svc",
"winapi"
]
packages = ["eventlog","mgr","registry","svc","winapi"]
revision = "f8fb11f83f7e860e3769a08e6811d1b399a43722"
version = "v1.0.0"
@ -90,17 +71,35 @@
branch = "master"
name = "github.com/kkdai/bstream"
packages = ["."]
revision = "f71540b9dfdcfe64dbf2818e9b66423c6aafcacd"
revision = "b3251f7901ec4dd4ec66b3210e8f4bd5c0f1c5a3"
[[projects]]
name = "github.com/miekg/dns"
packages = ["."]
revision = "cc8cd02140663157ce797c6650488d6c8563f31f"
version = "v1.1.6"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["ripemd160"]
revision = "182538f80094b6a8efaade63a8fd8e0d9d5843dd"
packages = ["ed25519","ed25519/internal/edwards25519","ripemd160"]
revision = "c2843e01d9a2bc60bb26ad24e09734fdc2d9ec58"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["bpf","internal/iana","internal/socket","ipv4","ipv6"]
revision = "d8887717615a059821345a5c23649351b52a1c0b"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "fead79001313d15903fb4605b4a1b781532cd93e"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "5976f1edbea819ab26c66eb5e5604c255a72f174b7a47bb218f3b8a6733d0c3a"
inputs-digest = "00392a00928f96fc94e2c8c65ce3a98cc6f5e2f93dda64d3c4502f2f38026e96"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,9 +1,11 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
@ -15,13 +17,17 @@
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "bou.ke/monkey"
version = "1.0.1"
[[constraint]]
name = "github.com/aead/siphash"
version = "1.0.1"
[[constraint]]
branch = "master"
@ -32,8 +38,8 @@
name = "github.com/btcsuite/go-socks"
[[constraint]]
branch = "master"
name = "github.com/btcsuite/goleveldb"
version = "1.0.0"
[[constraint]]
branch = "master"
@ -45,7 +51,7 @@
[[constraint]]
name = "github.com/davecgh/go-spew"
version = "1.1.0"
version = "1.1.1"
[[constraint]]
name = "github.com/jessevdk/go-flags"
@ -57,11 +63,15 @@
[[constraint]]
branch = "master"
name = "golang.org/x/crypto"
name = "github.com/kkdai/bstream"
[[constraint]]
name = "github.com/miekg/dns"
version = "1.1.6"
[[constraint]]
branch = "master"
name = "bou.ke/monkey"
name = "golang.org/x/crypto"
[prune]
go-tests = true

15
dnsseeder/LICENSE Normal file
View File

@ -0,0 +1,15 @@
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.

62
dnsseeder/README.md Normal file
View File

@ -0,0 +1,62 @@
dnsseeder
=========
## Requirements
[Go](http://golang.org) 1.10 or newer.
## Getting Started
- 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.
### Build from source (all platforms)
Building or updating from source requires the following build dependencies:
- **Go 1.10 or 1.11**
Installation instructions can be found here: https://golang.org/doc/install.
It is recommended to add `$GOPATH/bin` to your `PATH` at this point.
- **Vgo (Go 1.10 only)**
The `GO111MODULE` experiment is used to manage project dependencies and
provide reproducible builds. The module experiment is provided by the Go 1.11
toolchain, but the Go 1.10 toolchain does not provide any module support. To
perform module-aware builds with Go 1.10,
[vgo](https://godoc.org/golang.org/x/vgo) (a drop-in replacement for the go
command) must be used instead.
To build and install from a checked-out repo, run `go install` in the repo's
root directory. Some notes:
* Set the `GO111MODULE=on` environment variable if using Go 1.11 and building
from within `GOPATH`.
* Replace `go` with `vgo` when using Go 1.10.
* The `dnsseeder` executable will be installed to `$GOPATH/bin`. `GOPATH`
defaults to `$HOME/go` (or `%USERPROFILE%\go` on Windows) if unset.
For more information about Daglabs and how to set up your software please go to
our docs page at [docs.daglabs.org](https://docs.daglabs.org/getting-started/beginner-guide/).
To start dnsseeder listening on udp 127.0.0.1:5354 with an initial connection to working testnet node 192.168.0.1:
```
$ ./dnsseeder -n nameserver.example.com -H network-seed.example.com -s 192.168.0.1 --testnet
```
You will then need to redirect DNS traffic on your public IP port 53 to 127.0.0.1:5354
## Issue Tracker
The [integrated github issue tracker](https://github.com/daglabs/dnsseeder/issues)
is used for this project.
## License
dnsseeder is licensed under the [copyfree](http://copyfree.org) ISC License.

136
dnsseeder/config.go Normal file
View File

@ -0,0 +1,136 @@
// 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/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/util"
flags "github.com/jessevdk/go-flags"
)
const (
defaultConfigFilename = "daglabs-seeder.conf"
defaultListenPort = "5354"
)
var (
// Default network parameters
activeNetParams = &dagconfig.MainNetParams
// Default configuration options
defaultConfigFile = filepath.Join(defaultHomeDir, defaultConfigFilename)
defaultHomeDir = util.AppDataDir("daglabs-seeder", false)
)
// config defines the configuration options for hardforkdemo.
//
// See loadConfig for details on the configuration load process.
type config 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"`
TestNet bool `long:"testnet" description:"Use testnet"`
}
func loadConfig() (*config, 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 = fmt.Errorf(str, e.Path, link)
}
}
str := "failed to create home directory: %v"
err := fmt.Errorf(str, err)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
// Default config.
cfg := config{
Listen: normalizeAddress("localhost", defaultListenPort),
}
preCfg := cfg
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(&cfg, flags.Default)
err = flags.NewIniParser(parser).ParseFile(defaultConfigFile)
if err != nil {
if _, ok := err.(*os.PathError); !ok {
fmt.Fprintf(os.Stderr, "Error parsing config "+
"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(cfg.Host) == 0 {
str := "Please specify a hostname"
err := fmt.Errorf(str)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
if len(cfg.Nameserver) == 0 {
str := "Please specify a nameserver"
err := fmt.Errorf(str)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
cfg.Listen = normalizeAddress(cfg.Listen, defaultListenPort)
if cfg.TestNet {
activeNetParams = &dagconfig.TestNet3Params
}
return &cfg, 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
}

234
dnsseeder/dns.go Normal file
View File

@ -0,0 +1,234 @@
// 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"
"log"
"net"
"os"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/daglabs/btcd/connmgr"
"github.com/daglabs/btcd/util/subnetworkid"
"github.com/daglabs/btcd/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.Printf("NewRR: %v", err)
return
}
udpAddr, err := net.ResolveUDPAddr("udp4", d.listen)
if err != nil {
log.Printf("ResolveUDPAddr: %v", err)
return
}
udpListen, err := net.ListenUDP("udp", udpAddr)
if err != nil {
log.Printf("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.Printf("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.Printf("DNS server shutdown")
return
}
log.Printf("Read: %T", err.(*net.OpError).Err)
continue
}
wg.Add(1)
go 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, error) {
// Domain name may be in following format:
// [nsubnetwork.][xservice.]hostname
// where connmgr.SubnetworkIDPrefixChar and connmgr.ServiceFlagPrefixChar are prefexes
wantedSF := wire.SFNodeNetwork
subnetworkID := subnetworkid.SubnetworkIDSupportsAll
if d.hostname != domainName {
idx := 0
labels := dns.SplitDomainName(domainName)
if labels[0][0] == connmgr.SubnetworkIDPrefixChar && len(labels[0]) > 1 {
idx = 1
subnetworkID, err := subnetworkid.NewFromStr(labels[0][1:])
if err != nil {
log.Printf("%s: subnetworkid.NewFromStr: %v", addr, err)
return wantedSF, subnetworkID, 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.Printf("%s: ParseUint: %v", addr, err)
return wantedSF, subnetworkID, err
}
wantedSF = wire.ServiceFlag(u)
}
}
return wantedSF, subnetworkID, 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.Printf("%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.Printf("%s", str)
return nil, "", "", fmt.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.Printf("%s", str)
return nil, "", "", fmt.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.Printf("%s", str)
return "", fmt.Errorf("%s", str)
}
return atype, nil
}
func (d *DNSServer) buildDNSResponse(addr *net.UDPAddr, authority dns.RR, dnsMsg *dns.Msg,
wantedSF wire.ServiceFlag, 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, 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.Printf("%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.Printf("%s: NewRR: %v", addr, err)
return nil, err
}
respMsg.Answer = append(respMsg.Answer, newRR)
}
sendBytes, err := respMsg.Pack()
if err != nil {
log.Printf("%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, err := d.extractServicesSubnetworkID(addr, domainName)
if err != nil {
return
}
log.Printf("%s: query %d for services %v, subnetwork ID %v",
addr, dnsMsg.Question[0].Qtype, wantedSF, subnetworkID)
sendBytes, err := d.buildDNSResponse(addr, authority, dnsMsg, wantedSF, subnetworkID, atype)
if err != nil {
return
}
_, err = udpListen.WriteToUDP(sendBytes, addr)
if err != nil {
log.Printf("%s: failed to write response: %v", addr, err)
return
}
}

216
dnsseeder/dnsseed.go Normal file
View File

@ -0,0 +1,216 @@
// 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"
"log"
"net"
"os"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/daglabs/btcd/connmgr"
"github.com/daglabs/btcd/peer"
"github.com/daglabs/btcd/signal"
"github.com/daglabs/btcd/util/subnetworkid"
"github.com/daglabs/btcd/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{})
config := peer.Config{
UserAgentName: "daglabs-sniffer",
UserAgentVersion: "0.0.1",
DAGParams: activeNetParams,
DisableRelayTx: true,
Listeners: peer.MessageListeners{
OnAddr: func(p *peer.Peer, msg *wire.MsgAddr) {
added := amgr.AddAddresses(msg.AddrList)
log.Printf("Peer %v sent %v addresses, %d new",
p.Addr(), len(msg.AddrList), added)
onAddr <- struct{}{}
},
OnVersion: func(p *peer.Peer, msg *wire.MsgVersion) {
log.Printf("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(nil), nil)
// notify that version is received and Peer's subnetwork ID is updated
onVersion <- struct{}{}
},
},
SubnetworkID: subnetworkid.SubnetworkIDSupportsAll,
}
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(activeNetParams, requiredServices, subnetworkid.SubnetworkIDSupportsAll, hostLookup, func(addrs []*wire.NetAddress) {
amgr.AddAddresses(addrs)
})
peers = amgr.Addresses()
}
if len(peers) == 0 {
log.Printf("No stale addresses -- sleeping for 10 minutes")
for i := 0; i < 600; i++ {
time.Sleep(time.Second)
if atomic.LoadInt32(&systemShutdown) != 0 {
log.Printf("Creep thread shutdown")
return
}
}
continue
}
for _, addr := range peers {
if atomic.LoadInt32(&systemShutdown) != 0 {
log.Printf("Waiting creep threads to terminate")
wgCreep.Wait()
log.Printf("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(&config, host)
if err != nil {
log.Printf("NewOutboundPeer on %v: %v",
host, err)
return
}
amgr.Attempt(addr.IP)
conn, err := net.DialTimeout("tcp", p.Addr(), nodeTimeout)
if err != nil {
log.Printf("%v", err)
return
}
p.AssociateConnection(conn)
// Wait version messsage or timeout in case of failure.
select {
case <-onVersion:
case <-time.After(nodeTimeout):
log.Printf("version timeout on peer %v",
p.Addr())
p.Disconnect()
return
}
select {
case <-onAddr:
case <-time.After(nodeTimeout):
log.Printf("getaddr timeout on peer %v",
p.Addr())
p.Disconnect()
return
}
p.Disconnect()
}(addr)
}
wgCreep.Wait()
}
}
func main() {
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(activeNetParams.DefaultPort)
if err != nil {
fmt.Fprintf(os.Stderr, "Invalid peers default port %s: %v\n", activeNetParams.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.Printf("Failed to resolve seed host: %v, %v, ignoring", cfg.Seeder, err)
} else {
ip = net.ParseIP(hostAddrs[0])
if ip == nil {
log.Printf("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)
go creep()
dnsServer := NewDNSServer(cfg.Host, cfg.Nameserver, cfg.Listen)
wg.Add(1)
go dnsServer.Start()
defer func() {
log.Printf("Gracefully shutting down the seeder...")
atomic.StoreInt32(&systemShutdown, 1)
close(amgr.quit)
wg.Wait()
amgr.wg.Wait()
log.Printf("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

@ -0,0 +1,37 @@
# -- multistage docker build: stage #1: build stage
FROM golang:1.12-alpine AS build
RUN mkdir -p /go/src/github.com/daglabs/btcd
WORKDIR /go/src/github.com/daglabs/btcd
RUN apk add --no-cache curl git openssh binutils gcc musl-dev
RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
RUN go get -u golang.org/x/lint/golint \
github.com/kisielk/errcheck \
github.com/opennota/check/cmd/aligncheck \
github.com/opennota/check/cmd/structcheck \
github.com/opennota/check/cmd/varcheck
COPY ./Gopkg.* ./
RUN dep ensure -v -vendor-only
COPY . .
RUN go vet ./...
# RUN aligncheck ./...
# RUN structcheck -e ./...
# RUN varcheck -e ./...
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 ca-certificates tini
COPY --from=build /go/src/github.com/daglabs/btcd/dnsseeder/ /app/
USER nobody
ENTRYPOINT ["/app/dnsseeder", "-H", "dnsseed.my.home", "-n", "192.168.16.104", "-l", "0.0.0.0:53", "-s", "localhost", "--testnet"]

9
dnsseeder/docker/README Normal file
View File

@ -0,0 +1,9 @@
1. Since btcd is not public repository still, copy/checkout
https://github.com/daglabs/btcd into vendor/github.com/daglabs/btcd 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

365
dnsseeder/manager.go Normal file
View File

@ -0,0 +1,365 @@
// 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"
"fmt"
"log"
"net"
"os"
"path/filepath"
"sync"
"time"
"github.com/daglabs/btcd/util/subnetworkid"
"github.com/daglabs/btcd/wire"
"github.com/miekg/dns"
)
type Node struct {
Addr *wire.NetAddress
Services wire.ServiceFlag
LastAttempt time.Time
LastSuccess time.Time
LastSeen time.Time
SubnetworkID *subnetworkid.SubnetworkID
}
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 {
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
}
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.Printf("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.Printf("Failed to remove corrupt peers file %s: %v",
amgr.peersFile, err)
}
}
amgr.wg.Add(1)
go amgr.addressHandler()
return &amgr, nil
}
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, 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 node.SubnetworkID == nil || !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
}
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()
}
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.Printf("Address manager: saving peers")
m.savePeers()
log.Printf("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.Printf("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 fmt.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 fmt.Errorf("error reading %s: %v", filePath, err)
}
l := len(nodes)
m.mtx.Lock()
m.nodes = nodes
m.mtx.Unlock()
log.Printf("%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.Printf("Error opening file %s: %v", tmpfile, err)
return
}
enc := json.NewEncoder(w)
if err := enc.Encode(&m.nodes); err != nil {
log.Printf("Failed to encode file %s: %v", tmpfile, err)
return
}
if err := w.Close(); err != nil {
log.Printf("Error closing file %s: %v", tmpfile, err)
return
}
if err := os.Rename(tmpfile, m.peersFile); err != nil {
log.Printf("Error writing file %s: %v", m.peersFile, err)
return
}
}