mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
[NOD-495] Remove DNSSeeder to separate repository
This commit is contained in:
parent
9b832997f8
commit
b855f57371
@ -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.
|
@ -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]
|
||||
```
|
||||
|
@ -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
|
||||
}
|
238
dnsseeder/dns.go
238
dnsseeder/dns.go
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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"]
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user