/* */ package main import ( "flag" "fmt" "log" "os" "os/signal" "strings" "sync" "sync/atomic" "syscall" "time" "github.com/miekg/dns" ) // NodeCounts holds various statistics about the running system for use in html templates type NodeCounts struct { NdStatus []uint32 // number of nodes at each of the 4 statuses - RG, CG, WG, NG NdStarts []uint32 // number of crawles started last startcrawlers run DNSCounts []uint32 // number of dns requests for each dns type - dnsV4Std, dnsV4Non, dnsV6Std, dnsV6Non mtx sync.RWMutex // protect the structures } // configData holds information on the application type configData struct { dnsUnknown uint64 // the number of dns requests for we are not configured to handle uptime time.Time // application start time port string // port for the dns server to listen on http string // port for the web server to listen on version string // application version seeders map[string]*dnsseeder // holds a pointer to all the current seeders smtx sync.RWMutex // protect the seeders map order []string // the order of loading the netfiles so we can display in this order dns map[string][]dns.RR // holds details of all the currently served dns records dnsmtx sync.RWMutex // protect the dns map verbose bool // verbose output cmdline option debug bool // debug cmdline option stats bool // stats cmdline option } var config configData var netfile string func main() { var j bool config.version = "0.8.0" config.uptime = time.Now() flag.StringVar(&netfile, "netfile", "", "List of json config files to load") 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(&j, "j", false, "Write network template file (dnsseeder.json) and exit") flag.BoolVar(&config.verbose, "v", false, "Display verbose output") flag.BoolVar(&config.debug, "d", false, "Display debug output") flag.BoolVar(&config.stats, "s", false, "Display stats output") flag.Parse() if j == true { createNetFile() fmt.Printf("Template file has been created\n") os.Exit(0) } // configure the network options so we can start crawling netwFiles := strings.Split(netfile, ",") if len(netwFiles) == 0 { fmt.Printf("Error - No filenames specified. Please add -net= to load these files\n") os.Exit(1) } config.seeders = make(map[string]*dnsseeder) config.dns = make(map[string][]dns.RR) config.order = []string{} for _, nwFile := range netwFiles { nnw, err := loadNetwork(nwFile) if err != nil { fmt.Printf("Error loading data from netfile %s - %v\n", nwFile, err) os.Exit(1) } if nnw != nil { config.seeders[nnw.name] = nnw config.order = append(config.order, nnw.name) } } if config.debug == true { config.verbose = true config.stats = true } if config.verbose == true { config.stats = true } for _, v := range config.seeders { log.Printf("status - system is configured for network: %s\n", v.name) } if config.verbose == false { log.Printf("status - Running in quiet mode with limited output produced\n") } // start the web interface if we want it running if config.http != "" { go startHTTP(config.http) } // start dns server dns.HandleFunc(".", handleDNS) go serve("udp", config.port) //go serve("tcp", config.port) var wg sync.WaitGroup done := make(chan struct{}) // start a goroutine for each seeder for _, s := range config.seeders { wg.Add(1) go s.runSeeder(done, &wg) } sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) // block until a signal is received fmt.Println("\nShutting down on signal:", <-sig) // FIXME - call dns server.Shutdown() // close the done channel to signal to all seeders to shutdown // and wait for them to exit close(done) wg.Wait() fmt.Printf("\nProgram exiting. Bye\n") } // updateNodeCounts runs in a goroutine and updates the global stats with the latest // counts from a startCrawlers run func updateNodeCounts(s *dnsseeder, tcount uint32, started, totals []uint32) { s.counts.mtx.Lock() for st := range []int{statusRG, statusCG, statusWG, statusNG} { if config.stats { log.Printf("%s: started crawler: %s total: %v started: %v\n", s.name, status2str(uint32(st)), totals[st], started[st]) } // update the stats counters s.counts.NdStatus[st] = totals[st] s.counts.NdStarts[st] = started[st] } if config.stats { log.Printf("%s: crawlers started. total nodes: %d\n", s.name, tcount) } s.counts.mtx.Unlock() } // status2str will return the string description of the status func status2str(status uint32) string { switch status { case statusRG: return "statusRG" case statusCG: return "statusCG" case statusWG: return "statusWG" case statusNG: return "statusNG" default: return "Unknown" } } // updateDNSCounts runs in a goroutine and updates the global stats for the number of DNS requests func updateDNSCounts(name, qtype string) { var ndType uint32 var counted bool nonstd := strings.HasPrefix(name, "nonstd.") switch qtype { case "A": if nonstd { ndType = dnsV4Non } else { ndType = dnsV4Std } case "AAAA": if nonstd { ndType = dnsV6Non } else { ndType = dnsV6Std } default: ndType = dnsInvalid } // for DNS requests we do not have a reference to a seeder so we have to find it for _, s := range config.seeders { s.counts.mtx.Lock() if name == s.dnsHost+"." || name == "nonstd."+s.dnsHost+"." { s.counts.DNSCounts[ndType]++ counted = true } s.counts.mtx.Unlock() } if counted != true { atomic.AddUint64(&config.dnsUnknown, 1) } } /* */