Browse Source

Initial support for seeding multiple networks and improve scaling for larger networks

master
Lyndsay Roger 9 years ago
parent
commit
8ce82dda73
  1. 19
      README.md
  2. 35
      crawler.go
  3. 20
      dns.go
  4. 13
      doc.go
  5. 2
      http.go
  6. 45
      main.go
  7. 75
      network.go
  8. 145
      seeder.go
  9. 22
      twistee.go

19
README.md

@ -1,14 +1,12 @@
# dnsseeder # dnsseeder
Go Language dns seeder for the Twister P2P network Go Language dns seeder for Networks that use Bitcoin technology such as the [Twister P2P network](http://twister.net.co/)
This is a dns seeder for the [Twister P2P network](http://twister.net.co/)
It is based on the original twister-seeder https://github.com/miguelfreitas/twister-seeder It is based on the original twister-seeder https://github.com/miguelfreitas/twister-seeder
Also see the associated utility to display information about [non-standard ip addresses](https://github.com/gombadi/nonstd/) This codebase can now seed different networks. At the moment it supports Twister and Bitcoin networks. These are configured in network.go and selected at runtime with -net command line option.
Also see the associated utility to display information about [non-standard ip addresses](https://github.com/gombadi/nonstd/)
> **NOTE:** This repository is under ongoing development. Stable releases have been tagged and should be used for production systems.
## Installing ## Installing
@ -36,7 +34,7 @@ The binary will then be available in ${HOME}/go/bin
## Usage ## Usage
$ dnsseeder -h <domain respond to> $ dnsseeder -h <domain respond to> -net <network to seed>
An easy way to run the program is with tmux or screen. This enables you to log out and leave the program running. An easy way to run the program is with tmux or screen. This enables you to log out and leave the program running.
@ -48,7 +46,8 @@ If you want to be able to view the web interface then add -w port for the web se
Command line Options: Command line Options:
-h hostname to serve -h hostname to serve
-p port to listen on -net The network to seed. Currently twister, bitcoin, bitcoin-test
-p port to listen on for DNS requests
-d Produce debug output -d Produce debug output
-v Produce verbose output -v Produce verbose output
-w Port to listen on for Web Interface -w Port to listen on for Web Interface
@ -69,16 +68,16 @@ gzip ${LOGDIR}/*.log
# pass through the logging level needed # pass through the logging level needed
if [ -z ${1} ]; then if [ -z ${1} ]; then
LOGLV="-v" THENET="twister"
else else
LOGLV="${1}" THENET="${1}"
fi fi
cd cd
echo echo
echo "======= Run the Go Language dnsseed =======" echo "======= Run the Go Language dnsseed ======="
echo echo
${HOME}/go/bin/dnsseeder -h <host.to.serve> -p <port.to.listen.on> ${LOGLV} -w 8880 2>&1 | tee ${LOGDIR}/$(date +%F-%s)-goseeder.log ${HOME}/go/bin/dnsseeder -h <host.to.serve> -p <dns.port.to.listen.on> ${THENET -v} -w 8880 2>&1 | tee ${LOGDIR}/$(date +%F-%s)-goseeder.log
``` ```

35
crawler.go

@ -22,7 +22,7 @@ func (e *crawlError) Error() string {
// crawlTwistee runs in a goroutine, crawls the remote ip and updates the master // crawlTwistee runs in a goroutine, crawls the remote ip and updates the master
// list of currently active addresses // list of currently active addresses
func crawlTwistee(tw *twistee) { func crawlTwistee(s *dnsseeder, tw *twistee) {
tw.crawlActive = true tw.crawlActive = true
tw.crawlStart = time.Now() tw.crawlStart = time.Now()
@ -39,7 +39,7 @@ func crawlTwistee(tw *twistee) {
} }
// connect to the remote ip and ask them for their addr list // connect to the remote ip and ask them for their addr list
ras, e := crawlIP(tw) rna, e := crawlIP(s.net.pver, s.net.id, tw)
if e != nil { if e != nil {
// update the fact that we have not connected to this twistee // update the fact that we have not connected to this twistee
@ -50,17 +50,23 @@ func crawlTwistee(tw *twistee) {
// update the status of this failed twistee // update the status of this failed twistee
switch tw.status { switch tw.status {
case statusRG: case statusRG:
// if we are full then any RG failures will skip directly to NG
if s.isFull() {
tw.status = statusNG // not able to connect to this twistee so ignore
tw.statusTime = time.Now()
} else {
if tw.rating += 25; tw.rating > 30 { if tw.rating += 25; tw.rating > 30 {
tw.status = statusWG tw.status = statusWG
tw.statusTime = time.Now() tw.statusTime = time.Now()
} }
}
case statusCG: case statusCG:
if tw.rating += 25; tw.rating >= 50 { if tw.rating += 25; tw.rating >= 50 {
tw.status = statusWG tw.status = statusWG
tw.statusTime = time.Now() tw.statusTime = time.Now()
} }
case statusWG: case statusWG:
if tw.rating += 30; tw.rating >= 100 { if tw.rating += 15; tw.rating >= 100 {
tw.status = statusNG // not able to connect to this twistee so ignore tw.status = statusNG // not able to connect to this twistee so ignore
tw.statusTime = time.Now() tw.statusTime = time.Now()
} }
@ -93,13 +99,16 @@ func crawlTwistee(tw *twistee) {
added := 0 added := 0
// if we are full then skip adding more possible clients
if s.isFull() == false {
// loop through all the received network addresses and add to thelist if not present // loop through all the received network addresses and add to thelist if not present
for _, na := range ras { for _, na := range rna {
// a new network address so add to the system // a new network address so add to the system
if x := config.seeder.addNa(na); x == true { if x := s.addNa(na); x == true {
added++ added++
} }
} }
}
if config.verbose { if config.verbose {
log.Printf("status - crawl done: twistee: %s s:r:f: %v:%v:%v addr: %v:%v CrawlTime: %s Last connect: %v ago\n", log.Printf("status - crawl done: twistee: %s s:r:f: %v:%v:%v addr: %v:%v CrawlTime: %s Last connect: %v ago\n",
@ -108,7 +117,7 @@ func crawlTwistee(tw *twistee) {
tw.status, tw.status,
tw.rating, tw.rating,
tw.connectFails, tw.connectFails,
len(ras), len(rna),
added, added,
time.Since(tw.crawlStart).String(), time.Since(tw.crawlStart).String(),
time.Since(cs).String()) time.Since(cs).String())
@ -125,7 +134,7 @@ func crawlEnd(tw *twistee) {
} }
// crawlIP retrievs a slice of ip addresses from a client // crawlIP retrievs a slice of ip addresses from a client
func crawlIP(tw *twistee) ([]*wire.NetAddress, *crawlError) { func crawlIP(pver uint32, netID wire.BitcoinNet, tw *twistee) ([]*wire.NetAddress, *crawlError) {
ip := tw.na.IP.String() ip := tw.na.IP.String()
port := strconv.Itoa(int(tw.na.Port)) port := strconv.Itoa(int(tw.na.Port))
@ -155,14 +164,14 @@ func crawlIP(tw *twistee) ([]*wire.NetAddress, *crawlError) {
return nil, &crawlError{"Create NewMsgVersionFromConn", err} return nil, &crawlError{"Create NewMsgVersionFromConn", err}
} }
err = wire.WriteMessage(conn, msgver, pver, twistNet) err = wire.WriteMessage(conn, msgver, pver, netID)
if err != nil { if err != nil {
// Log and handle the error // Log and handle the error
return nil, &crawlError{"Write Version Message", err} return nil, &crawlError{"Write Version Message", err}
} }
// first message received should be version // first message received should be version
msg, _, err := wire.ReadMessage(conn, pver, twistNet) msg, _, err := wire.ReadMessage(conn, pver, netID)
if err != nil { if err != nil {
// Log and handle the error // Log and handle the error
return nil, &crawlError{"Read message after sending Version", err} return nil, &crawlError{"Read message after sending Version", err}
@ -190,13 +199,13 @@ func crawlIP(tw *twistee) ([]*wire.NetAddress, *crawlError) {
// send verack command // send verack command
msgverack := wire.NewMsgVerAck() msgverack := wire.NewMsgVerAck()
err = wire.WriteMessage(conn, msgverack, pver, twistNet) err = wire.WriteMessage(conn, msgverack, pver, netID)
if err != nil { if err != nil {
return nil, &crawlError{"writing message VerAck", err} return nil, &crawlError{"writing message VerAck", err}
} }
// second message received should be verack // second message received should be verack
msg, _, err = wire.ReadMessage(conn, pver, twistNet) msg, _, err = wire.ReadMessage(conn, pver, netID)
if err != nil { if err != nil {
return nil, &crawlError{"reading expected Ver Ack from remote client", err} return nil, &crawlError{"reading expected Ver Ack from remote client", err}
} }
@ -213,7 +222,7 @@ func crawlIP(tw *twistee) ([]*wire.NetAddress, *crawlError) {
// send getaddr command // send getaddr command
msgGetAddr := wire.NewMsgGetAddr() msgGetAddr := wire.NewMsgGetAddr()
err = wire.WriteMessage(conn, msgGetAddr, pver, twistNet) err = wire.WriteMessage(conn, msgGetAddr, pver, netID)
if err != nil { if err != nil {
return nil, &crawlError{"writing Addr message to remote client", err} return nil, &crawlError{"writing Addr message to remote client", err}
} }
@ -225,7 +234,7 @@ func crawlIP(tw *twistee) ([]*wire.NetAddress, *crawlError) {
// Using the Bitcoin lib for the Twister Net means it does not understand some // Using the Bitcoin lib for the Twister Net means it does not understand some
// of the commands and will error. We can ignore these as we are only // of the commands and will error. We can ignore these as we are only
// interested in the addr message and its content. // interested in the addr message and its content.
msgaddr, _, _ := wire.ReadMessage(conn, pver, twistNet) msgaddr, _, _ := wire.ReadMessage(conn, pver, netID)
if msgaddr != nil { if msgaddr != nil {
switch msg := msgaddr.(type) { switch msg := msgaddr.(type) {
case *wire.MsgAddr: case *wire.MsgAddr:

20
dns.go

@ -51,7 +51,7 @@ func updateDNS(s *dnsseeder) {
if t == dnsV4Std || t == dnsV4Non { if t == dnsV4Std || t == dnsV4Non {
if t == dnsV4Std && tw.dnsType == dnsV4Std { if t == dnsV4Std && tw.dnsType == dnsV4Std {
r := new(dns.A) r := new(dns.A)
r.Hdr = dns.RR_Header{Name: config.host + ".", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60} r.Hdr = dns.RR_Header{Name: config.host + ".", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.net.ttl}
r.A = tw.na.IP r.A = tw.na.IP
rr4std = append(rr4std, r) rr4std = append(rr4std, r)
numRR++ numRR++
@ -60,12 +60,12 @@ func updateDNS(s *dnsseeder) {
// if the twistee is using a non standard port then add the encoded port info to DNS // if the twistee is using a non standard port then add the encoded port info to DNS
if t == dnsV4Non && tw.dnsType == dnsV4Non { if t == dnsV4Non && tw.dnsType == dnsV4Non {
r := new(dns.A) r := new(dns.A)
r.Hdr = dns.RR_Header{Name: "nonstd." + config.host + ".", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60} r.Hdr = dns.RR_Header{Name: "nonstd." + config.host + ".", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.net.ttl}
r.A = tw.na.IP r.A = tw.na.IP
rr4non = append(rr4non, r) rr4non = append(rr4non, r)
numRR++ numRR++
r = new(dns.A) r = new(dns.A)
r.Hdr = dns.RR_Header{Name: "nonstd." + config.host + ".", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60} r.Hdr = dns.RR_Header{Name: "nonstd." + config.host + ".", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.net.ttl}
r.A = tw.nonstdIP r.A = tw.nonstdIP
rr4non = append(rr4non, r) rr4non = append(rr4non, r)
numRR++ numRR++
@ -74,7 +74,7 @@ func updateDNS(s *dnsseeder) {
if t == dnsV6Std || t == dnsV6Non { if t == dnsV6Std || t == dnsV6Non {
if t == dnsV6Std && tw.dnsType == dnsV6Std { if t == dnsV6Std && tw.dnsType == dnsV6Std {
r := new(dns.AAAA) r := new(dns.AAAA)
r.Hdr = dns.RR_Header{Name: config.host + ".", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 60} r.Hdr = dns.RR_Header{Name: config.host + ".", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: s.net.ttl}
r.AAAA = tw.na.IP r.AAAA = tw.na.IP
rr6std = append(rr6std, r) rr6std = append(rr6std, r)
numRR++ numRR++
@ -82,12 +82,12 @@ func updateDNS(s *dnsseeder) {
// if the twistee is using a non standard port then add the encoded port info to DNS // if the twistee is using a non standard port then add the encoded port info to DNS
if t == dnsV6Non && tw.dnsType == dnsV6Non { if t == dnsV6Non && tw.dnsType == dnsV6Non {
r := new(dns.AAAA) r := new(dns.AAAA)
r.Hdr = dns.RR_Header{Name: "nonstd." + config.host + ".", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 60} r.Hdr = dns.RR_Header{Name: "nonstd." + config.host + ".", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: s.net.ttl}
r.AAAA = tw.na.IP r.AAAA = tw.na.IP
rr6non = append(rr6non, r) rr6non = append(rr6non, r)
numRR++ numRR++
r = new(dns.AAAA) r = new(dns.AAAA)
r.Hdr = dns.RR_Header{Name: "nonstd." + config.host + ".", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 60} r.Hdr = dns.RR_Header{Name: "nonstd." + config.host + ".", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: s.net.ttl}
r.AAAA = tw.nonstdIP r.AAAA = tw.nonstdIP
rr6non = append(rr6non, r) rr6non = append(rr6non, r)
numRR++ numRR++
@ -156,8 +156,8 @@ func handleDNSStd(w dns.ResponseWriter, r *dns.Msg) {
w.WriteMsg(m) w.WriteMsg(m)
if config.verbose { if config.debug {
log.Printf("status - DNS response Type: standard To IP: %s Query Type: %s\n", w.RemoteAddr().String(), qtype) log.Printf("debug - DNS response Type: standard To IP: %s Query Type: %s\n", w.RemoteAddr().String(), qtype)
} }
} }
@ -195,8 +195,8 @@ func handleDNSNon(w dns.ResponseWriter, r *dns.Msg) {
w.WriteMsg(m) w.WriteMsg(m)
if config.verbose { if config.debug {
log.Printf("status - DNS response Type: non-standard To IP: %s Query Type: %s\n", w.RemoteAddr().String(), qtype) log.Printf("debug - DNS response Type: non-standard To IP: %s Query Type: %s\n", w.RemoteAddr().String(), qtype)
} }
} }

13
doc.go

@ -1,8 +1,17 @@
/* /*
This application provides a seeder service to the Twister Network. This application provides a DNS seeder service to network based on Bitcoin technology.
For example -
http://twister.net.co/ http://twister.net.co/
https://bitcoin.org/
It crawls the Twister Network for active clients and records their ip address and port. It then replies to DNS queries with the ip addresses.
This application crawls the Network for active clients and records their ip address and port. It then replies to DNS queries with this information.
Features:
- Preconfigured support for Twister & Bitcoin networks. use -net <network> to load config data.
- supports ipv4 & ipv6 addresses
- revisits clients on a configurable time basis to make sure they are still available
- Low memory & cpu requirements
*/ */
package main package main

2
http.go

@ -231,9 +231,7 @@ func statusHandler(w http.ResponseWriter, r *http.Request, status uint32) {
} }
} }
writeFooter(w, r, startT) writeFooter(w, r, startT)
} }
// copy Twistee details into a template friendly struct // copy Twistee details into a template friendly struct

45
main.go

@ -38,28 +38,47 @@ type configData struct {
var config configData var config configData
var counts twCounts var counts twCounts
var nwname string
func main() { func main() {
// FIXME - update with git hash during build // FIXME - update with git hash during build
config.version = "0.5.0" config.version = "0.6.0"
// initialize the stats counters // initialize the stats counters
counts.TwStatus = make([]uint32, maxStatusTypes) counts.TwStatus = make([]uint32, maxStatusTypes)
counts.TwStarts = make([]uint32, maxStatusTypes) counts.TwStarts = make([]uint32, maxStatusTypes)
counts.DNSCounts = make([]uint32, maxDNSTypes) counts.DNSCounts = make([]uint32, maxDNSTypes)
flag.StringVar(&nwname, "net", "", "Preconfigured Network config")
flag.StringVar(&config.host, "h", "", "DNS host to serve") flag.StringVar(&config.host, "h", "", "DNS host to serve")
flag.StringVar(&config.port, "p", "8053", "Port to listen on") flag.StringVar(&config.port, "p", "8053", "DNS Port to listen on")
flag.StringVar(&config.http, "w", "", "Web Port to listen on. No port specified & no web server running")
flag.BoolVar(&config.verbose, "v", false, "Display verbose output") flag.BoolVar(&config.verbose, "v", false, "Display verbose output")
flag.BoolVar(&config.debug, "d", false, "Display debug output") flag.BoolVar(&config.debug, "d", false, "Display debug output")
flag.BoolVar(&config.stats, "s", false, "Display stats output") flag.BoolVar(&config.stats, "s", false, "Display stats output")
flag.StringVar(&config.http, "w", "", "Web Port to listen on. No port specified & no web server running")
flag.Parse() flag.Parse()
if config.host == "" { if config.host == "" {
log.Fatalf("error - no hostname provided\n") fmt.Printf("error - no hostname provided\n")
os.Exit(1)
}
// configure the network options so we can start crawling
thenet := selectNetwork(nwname)
if thenet == nil {
fmt.Printf("Error - No valid network specified. Please add -net=<network> from one of the following:\n")
for _, n := range getNetworkNames() {
fmt.Printf("%s\n", n)
} }
os.Exit(1)
}
// init the seeder
config.seeder = &dnsseeder{}
config.seeder.theList = make(map[string]*twistee)
config.seeder.uptime = time.Now()
config.seeder.net = thenet
if config.debug == true { if config.debug == true {
config.verbose = true config.verbose = true
@ -73,18 +92,14 @@ func main() {
if config.verbose == false { if config.verbose == false {
log.Printf("status - Running in quiet mode with limited output produced\n") log.Printf("status - Running in quiet mode with limited output produced\n")
} else {
log.Printf("status - system is configured for %s\n", config.seeder.net.name)
} }
// start the web interface if we want it running // start the web interface if we want it running
if config.http != "" { if config.http != "" {
go startHTTP(config.http) go startHTTP(config.http)
} }
// init the seeder
config.seeder = &dnsseeder{}
config.seeder.theList = make(map[string]*twistee)
config.seeder.uptime = time.Now()
// start dns server // start dns server
dns.HandleFunc("nonstd."+config.host, handleDNSNon) dns.HandleFunc("nonstd."+config.host, handleDNSNon)
dns.HandleFunc(config.host, handleDNSStd) dns.HandleFunc(config.host, handleDNSStd)
@ -92,7 +107,7 @@ func main() {
//go serve("tcp", config.port) //go serve("tcp", config.port)
// seed the seeder with some ip addresses // seed the seeder with some ip addresses
initCrawlers() config.seeder.initCrawlers()
// start first crawl // start first crawl
config.seeder.startCrawlers() config.seeder.startCrawlers()
@ -100,11 +115,11 @@ func main() {
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
// extract good dns records from all twistees on regular basis // extract good dns records from all twistees on regular basis
dnsChan := time.NewTicker(time.Second * 57).C dnsChan := time.NewTicker(time.Second * dnsDelay).C
// used to start crawlers on a regular basis // used to start crawlers on a regular basis
crawlChan := time.NewTicker(time.Second * 22).C crawlChan := time.NewTicker(time.Second * crawlDelay).C
// used to remove old statusNG twistees that have reached fail count // used to remove old statusNG twistees that have reached fail count
auditChan := time.NewTicker(time.Hour * 1).C auditChan := time.NewTicker(time.Minute * auditDelay).C
dowhile := true dowhile := true
for dowhile == true { for dowhile == true {
@ -115,7 +130,7 @@ func main() {
if config.debug { if config.debug {
log.Printf("debug - Audit twistees timer triggered\n") log.Printf("debug - Audit twistees timer triggered\n")
} }
config.seeder.auditTwistees() config.seeder.auditClients()
case <-dnsChan: case <-dnsChan:
if config.debug { if config.debug {
log.Printf("debug - DNS - Updating latest ip addresses timer triggered\n") log.Printf("debug - DNS - Updating latest ip addresses timer triggered\n")

75
network.go

@ -0,0 +1,75 @@
package main
import (
"github.com/btcsuite/btcd/wire"
)
// network struct holds config details for the network the seeder is using
type network struct {
id wire.BitcoinNet // Magic number - Unique ID for this network. Sent in header of all messages
maxSize int // max number of clients before we start restricting new entries
port uint16 // default network port this network uses
pver uint32 // minimum block height for the network
ttl uint32 // DNS TTL to use for this network
name string // Short name for the network
description string // Long description for the network
seeders []string // slice of seeders to pull ip addresses when starting this seeder
maxStart []uint32 // max number of goroutines to start each run for each status type
delay []int64 // number of seconds to wait before we connect to a known client for each status
}
// getNetworkNames returns a slice of the networks that have been configured
func getNetworkNames() []string {
return []string{"twister", "bitcoin", "bitcoin-testnet"}
}
// selectNetwork will return a network struct for a given network
func selectNetwork(name string) *network {
switch name {
case "twister":
return &network{
id: 0xd2bbdaf0,
port: 28333,
pver: 60000,
ttl: 600,
maxSize: 1000,
name: "TwisterNet",
description: "Twister P2P Net",
seeders: []string{"seed2.twister.net.co", "seed.twister.net.co", "seed3.twister.net.co"},
maxStart: []uint32{15, 15, 15, 30},
delay: []int64{184, 678, 237, 1876},
}
case "bitcoin":
return &network{
id: 0xd9b4bef9,
port: 8333,
pver: 70001,
ttl: 900,
maxSize: 1250,
name: "BitcoinMainNet",
description: "Bitcoin Main Net",
seeders: []string{"dnsseed.bluematt.me", "bitseed.xf2.org", "dnsseed.bitcoin.dashjr.org", "seed.bitcoin.sipa.be"},
maxStart: []uint32{20, 20, 20, 30},
delay: []int64{210, 789, 234, 1876},
}
case "bitcoin-testnet":
return &network{
id: 0xdab5bffa,
port: 18333,
pver: 70001,
ttl: 300,
maxSize: 250,
name: "BitcoinTestNet",
description: "Bitcoin Test Net",
seeders: []string{"testnet-seed.alexykot.me", "testnet-seed.bitcoin.petertodd.org", "testnet-seed.bluematt.me", "testnet-seed.bitcoin.schildbach.de"},
maxStart: []uint32{15, 15, 15, 30},
delay: []int64{184, 678, 237, 1876},
}
default:
return nil
}
}
/*
*/

145
seeder.go

@ -12,68 +12,73 @@ import (
const ( const (
// TWISTNET Magic number to make it incompatible with the Bitcoin network
twistNet = 0xd2bbdaf0
// NOUNCE is used to check if we connect to ourselves // NOUNCE is used to check if we connect to ourselves
// as we don't listen we can use a fixed value // as we don't listen we can use a fixed value
nounce = 0x0539a019ca550825 nounce = 0x0539a019ca550825
pver = 60000
minPort = 0 minPort = 0
maxPort = 65535 maxPort = 65535
twStdPort = 28333 // standard port twister listens on crawlDelay = 22 // seconds between start crawlwer ticks
auditDelay = 22 // minutes between audit channel ticks
dnsDelay = 57 // seconds between updates to active dns record list
maxFails = 58 // max number of connect fails before we delete a twistee. Just over 24 hours(checked every 33 minutes) maxFails = 58 // max number of connect fails before we delete a twistee. Just over 24 hours(checked every 33 minutes)
maxTo = 250 // max seconds (4min 10 sec) for all comms to twistee to complete before we timeout maxTo = 250 // max seconds (4min 10 sec) for all comms to twistee to complete before we timeout
)
dnsInvalid = 0 const (
dnsV4Std = 1 dnsInvalid = iota //
dnsV4Non = 2 dnsV4Std // ip v4 using network standard port
dnsV6Std = 3 dnsV4Non // ip v4 using network non standard port
dnsV6Non = 4 dnsV6Std // ipv6 using network standard port
maxDNSTypes = 5 dnsV6Non // ipv6 using network non standard port
maxDNSTypes // used in main to allocate slice
)
const (
// twistee status // twistee status
statusRG = 1 // reported good status. A remote twistee has reported this ip but we have not connected statusRG = iota // reported good status. A remote twistee has reported this ip but we have not connected
statusCG = 2 // confirmed good. We have connected to the twistee and received addresses statusCG // confirmed good. We have connected to the twistee and received addresses
statusWG = 3 // was good. Twistee was confirmed good but now having problems statusWG // was good. Twistee was confirmed good but now having problems
statusNG = 4 // no good. Will be removed from theList after 24 hours to redure bouncing ip addresses statusNG // no good. Will be removed from theList after 24 hours to redure bouncing ip addresses
maxStatusTypes = 5 maxStatusTypes // used in main to allocate slice
) )
type dnsseeder struct { type dnsseeder struct {
uptime time.Time net *network // network struct with config options for this network
theList map[string]*twistee uptime time.Time // as the name says
theList map[string]*twistee // the list of current clients
mtx sync.RWMutex mtx sync.RWMutex
} }
// initCrawlers needs to be run before the startCrawlers so it can get // initCrawlers needs to be run before the startCrawlers so it can get
// a list of current ip addresses from the other seeders and therefore // a list of current ip addresses from the other seeders and therefore
// start the crawl process // start the crawl process
func initCrawlers() { func (s *dnsseeder) initCrawlers() {
seeders := []string{"seed2.twister.net.co", "seed3.twister.net.co", "seed.twister.net.co"} // get a list of permenant seeders
seeders := s.net.seeders
for _, seeder := range seeders { for _, aseeder := range seeders {
c := 0 c := 0
newRRs, err := net.LookupHost(seeder) newRRs, err := net.LookupHost(aseeder)
if err != nil { if err != nil {
log.Printf("status - unable to do initial lookup to seeder %s %v\n", seeder, err) log.Printf("status - unable to do initial lookup to seeder %s %v\n", aseeder, err)
continue continue
} }
for _, ip := range newRRs { for _, ip := range newRRs {
if newIP := net.ParseIP(ip); newIP != nil { if newIP := net.ParseIP(ip); newIP != nil {
// 1 at the end is the services flag // 1 at the end is the services flag
if x := config.seeder.addNa(wire.NewNetAddressIPPort(newIP, 28333, 1)); x == true { if x := config.seeder.addNa(wire.NewNetAddressIPPort(newIP, s.net.port, 1)); x == true {
c++ c++
} }
} }
} }
if config.verbose { if config.verbose {
log.Printf("status - completed import of %v addresses from %s\n", c, seeder) log.Printf("status - completed import of %v addresses from %s\n", c, aseeder)
} }
} }
} }
@ -99,19 +104,20 @@ func (s *dnsseeder) startCrawlers() {
started uint32 // count of goroutines started for this type started uint32 // count of goroutines started for this type
delay int64 // number of second since last try delay int64 // number of second since last try
}{ }{
{"statusRG", statusRG, 10, 0, 0, 184}, {"statusRG", statusRG, s.net.maxStart[statusRG], 0, 0, s.net.delay[statusRG]},
{"statusCG", statusCG, 10, 0, 0, 325}, {"statusCG", statusCG, s.net.maxStart[statusCG], 0, 0, s.net.delay[statusCG]},
{"statusWG", statusWG, 10, 0, 0, 237}, {"statusWG", statusWG, s.net.maxStart[statusWG], 0, 0, s.net.delay[statusWG]},
{"statusNG", statusNG, 20, 0, 0, 1876}, {"statusNG", statusNG, s.net.maxStart[statusNG], 0, 0, s.net.delay[statusNG]},
} }
s.mtx.RLock() s.mtx.RLock()
defer s.mtx.RUnlock() defer s.mtx.RUnlock()
// step through each of the status types RG, CG, WG, NG
for _, c := range crawlers { for _, c := range crawlers {
// range on a map will not return items in the same order each time // range on a map will not return items in the same order each time
// not the best method to randomly pick twistees to crawl. FIXME // so this is a random'ish selection
for _, tw := range s.theList { for _, tw := range s.theList {
if tw.status != c.status { if tw.status != c.status {
@ -134,7 +140,7 @@ func (s *dnsseeder) startCrawlers() {
} }
// all looks good so start a go routine to crawl the remote twistee // all looks good so start a go routine to crawl the remote twistee
go crawlTwistee(tw) go crawlTwistee(s, tw)
c.started++ c.started++
} }
@ -166,6 +172,12 @@ func (s *dnsseeder) isNaDup(na *wire.NetAddress) bool {
// addNa validates and adds a network address to theList // addNa validates and adds a network address to theList
func (s *dnsseeder) addNa(nNa *wire.NetAddress) bool { func (s *dnsseeder) addNa(nNa *wire.NetAddress) bool {
// as this is run in many different goroutines then they may all try and
// add new addresses so do a final check
if s.isFull() {
return false
}
if dup := s.isNaDup(nNa); dup == true { if dup := s.isNaDup(nNa); dup == true {
return false return false
} }
@ -191,7 +203,7 @@ func (s *dnsseeder) addNa(nNa *wire.NetAddress) bool {
// select the dns type based on the remote address type and port // select the dns type based on the remote address type and port
if x := nt.na.IP.To4(); x == nil { if x := nt.na.IP.To4(); x == nil {
// not ipv4 // not ipv4
if nNa.Port != twStdPort { if nNa.Port != s.net.port {
nt.dnsType = dnsV6Non nt.dnsType = dnsV6Non
// produce the nonstdIP // produce the nonstdIP
@ -202,7 +214,7 @@ func (s *dnsseeder) addNa(nNa *wire.NetAddress) bool {
} }
} else { } else {
// ipv4 // ipv4
if nNa.Port != twStdPort { if nNa.Port != s.net.port {
nt.dnsType = dnsV4Non nt.dnsType = dnsV4Non
// force ipv4 address into a 4 byte buffer // force ipv4 address into a 4 byte buffer
@ -216,8 +228,7 @@ func (s *dnsseeder) addNa(nNa *wire.NetAddress) bool {
// generate the key and add to theList // generate the key and add to theList
k := net.JoinHostPort(nNa.IP.String(), strconv.Itoa(int(nNa.Port))) k := net.JoinHostPort(nNa.IP.String(), strconv.Itoa(int(nNa.Port)))
s.mtx.Lock() s.mtx.Lock()
// final check to make sure another twistee & goroutine has not already added this twistee // final check to make sure another crawl & goroutine has not already added this client
// FIXME migrate to use channels
if _, dup := s.theList[k]; dup == false { if _, dup := s.theList[k]; dup == false {
s.theList[k] = &nt s.theList[k] = &nt
} }
@ -259,10 +270,20 @@ func crc16(bs []byte) uint16 {
return crc return crc
} }
func (s *dnsseeder) auditTwistees() { func (s *dnsseeder) auditClients() {
c := 0 c := 0
log.Printf("status - Audit start. System Uptime: %s\n", time.Since(s.uptime).String())
// set this early so for this audit run all NG clients will be purged
// and space will be made for new, possible CG clients
iAmFull := s.isFull()
// cgGoal is 75% of the max statusCG clients we can crawl with the current network delay & maxStart settings.
// This allows us to cycle statusCG users to keep the list fresh
cgGoal := int(float64(float64(s.net.delay[statusCG]/crawlDelay)*float64(s.net.maxStart[statusCG])) * 0.75)
cgCount := 0
log.Printf("status - Audit start. statusCG Goal: %v System Uptime: %s\n", cgGoal, time.Since(s.uptime).String())
s.mtx.Lock() s.mtx.Lock()
defer s.mtx.Unlock() defer s.mtx.Unlock()
@ -280,19 +301,8 @@ func (s *dnsseeder) auditTwistees() {
tw.statusStr) tw.statusStr)
} }
} }
if tw.status == statusRG || tw.status == statusWG {
if time.Now().Unix()-tw.statusTime.Unix() >= 900 {
log.Printf("warning - unchanged status > 15 minutes ====\n- %s status:rating:fails %v:%v:%v last status change: %s last status: %s\n====\n",
k,
tw.status,
tw.rating,
tw.connectFails,
tw.statusTime.String(),
tw.statusStr)
}
}
// last audit task is to remove twistees that we can not connect to // Audit task is to remove clients that we have not been able to connect to
if tw.status == statusNG && tw.connectFails > maxFails { if tw.status == statusNG && tw.connectFails > maxFails {
if config.verbose { if config.verbose {
log.Printf("status - purging twistee %s after %v failed connections\n", k, tw.connectFails) log.Printf("status - purging twistee %s after %v failed connections\n", k, tw.connectFails)
@ -305,6 +315,36 @@ func (s *dnsseeder) auditTwistees() {
delete(s.theList, k) delete(s.theList, k)
} }
// If seeder is full then remove old NG clients and fill up with possible new CG clients
if tw.status == statusNG && iAmFull {
if config.verbose {
log.Printf("status - seeder full purging twistee %s\n", k)
}
c++
// remove the map entry and mark the old twistee as
// nil so garbage collector will remove it
s.theList[k] = nil
delete(s.theList, k)
}
// check if we need to purge statusCG to freshen the list
if tw.status == statusCG {
if cgCount++; cgCount > cgGoal {
// we have enough statusCG clients so purge remaining to cycle through the list
if config.verbose {
log.Printf("status - seeder cycle statusCG - purging client %s\n", k)
}
c++
// remove the map entry and mark the old twistee as
// nil so garbage collector will remove it
s.theList[k] = nil
delete(s.theList, k)
}
}
} }
if config.verbose { if config.verbose {
log.Printf("status - Audit complete. %v twistees purged\n", c) log.Printf("status - Audit complete. %v twistees purged\n", c)
@ -314,10 +354,17 @@ func (s *dnsseeder) auditTwistees() {
// teatload loads the dns records with time based test data // teatload loads the dns records with time based test data
func (s *dnsseeder) loadDNS() { func (s *dnsseeder) loadDNS() {
updateDNS(s) updateDNS(s)
} }
// isFull returns true if the number of remote clients is more than we want to store
func (s *dnsseeder) isFull() bool {
if len(s.theList) > s.net.maxSize {
return true
}
return false
}
/* /*
*/ */

22
twistee.go

@ -9,22 +9,22 @@ import (
// Twistee struct contains details on one twister client // Twistee struct contains details on one twister client
type twistee struct { type twistee struct {
na *wire.NetAddress na *wire.NetAddress // holds ip address & port details
lastConnect time.Time lastConnect time.Time // last time we sucessfully connected to this client
lastTry time.Time lastTry time.Time // last time we tried to connect to this client
crawlStart time.Time crawlStart time.Time // time when we started the last crawl
statusTime time.Time statusTime time.Time // time the status was last updated
crawlActive bool crawlActive bool // are we currently crawling this client
connectFails uint32 connectFails uint32 // number of times we have failed to connect to this client
statusStr string // string with last error or OK details statusStr string // string with last error or OK details
version int32 // remote client protocol version version int32 // remote client protocol version
strVersion string // remote client user agent strVersion string // remote client user agent
services wire.ServiceFlag // remote client supported services services wire.ServiceFlag // remote client supported services
lastBlock int32 lastBlock int32 // remote client last block
status uint32 // rg,cg,wg,ng status uint32 // rg,cg,wg,ng
rating uint32 // if it reaches 100 then we ban them rating uint32 // if it reaches 100 then we mark them statusNG
nonstdIP net.IP nonstdIP net.IP // if not using the default port then this is the encoded ip containing the actual port
dnsType uint32 dnsType uint32 // what dns type this client is
} }
// status2str will return the string description of the status // status2str will return the string description of the status

Loading…
Cancel
Save