#include #include #include #include #include #include "bitcoin.h" #include "db.h" #define NTHREADS 24 using namespace std; class CDnsSeedOpts { public: int nThreads; int nPort; int nDnsThreads; int fWipeBan; int fWipeIgnore; const char *mbox; const char *ns; const char *host; const char *tor; CDnsSeedOpts() : nThreads(24), nDnsThreads(24), nPort(53), mbox(NULL), ns(NULL), host(NULL) {} void ParseCommandLine(int argc, char **argv) { static const char *help = "Bitcoin-seeder\n" "Usage: %s -h -n [-m ] [-t ] [-p ]\n" "\n" "Options:\n" "-h Hostname of the DNS seed\n" "-n Hostname of the nameserver\n" "-m E-Mail address reported in SOA records\n" "-t Number of crawlers to run in parallel (default 24)\n" "-d Number of DNS server threads (default 24)\n" "-p UDP port to listen on (default 53)\n" "-o Tor proxy IP/Port\n" "--wipeban Wipe list of banned nodes\n" "--wipeignore Wipe list of ignored nodes\n" "-?, --help Show this text\n" "\n"; bool showHelp = false; while(1) { static struct option long_options[] = { {"host", required_argument, 0, 'h'}, {"ns", required_argument, 0, 'n'}, {"mbox", required_argument, 0, 'm'}, {"threads", required_argument, 0, 't'}, {"dnsthreads", required_argument, 0, 'd'}, {"port", required_argument, 0, 'p'}, {"onion", required_argument, 0, 'o'}, {"wipeban", no_argument, &fWipeBan, 1}, {"wipeignore", no_argument, &fWipeBan, 1}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int option_index = 0; int c = getopt_long(argc, argv, "h:n:m:t:p:d:o:", long_options, &option_index); if (c == -1) break; switch (c) { case 'h': { host = optarg; break; } case 'm': { mbox = optarg; break; } case 'n': { ns = optarg; break; } case 't': { int n = strtol(optarg, NULL, 10); if (n > 0 && n < 1000) nThreads = n; break; } case 'd': { int n = strtol(optarg, NULL, 10); if (n > 0 && n < 1000) nDnsThreads = n; break; } case 'p': { int p = strtol(optarg, NULL, 10); if (p > 0 && p < 65536) nPort = p; break; } case 'o': { tor = optarg; break; } case '?': { showHelp = true; break; } } } if (host != NULL && ns == NULL) showHelp = true; if (showHelp) fprintf(stderr, help, argv[0]); } }; extern "C" { #include "dns.h" } CAddrDb db; extern "C" void* ThreadCrawler(void* data) { do { CService ip; int wait = 5; if (!db.Get(ip, wait)) { wait *= 1000; wait += rand() % (500 * NTHREADS); Sleep(wait); continue; } int ban = 0; vector addr; int clientV = 0; int blocks = 0; std::string clientSV; bool ret = TestNode(ip,ban,clientV,clientSV,blocks,addr); db.Add(addr); if (ret) { db.Good(ip, clientV, clientSV, blocks); } else { db.Bad(ip, ban); } } while(1); } extern "C" int GetIPList(void *thread, addr_t *addr, int max, int ipv4, int ipv6); class CDnsThread { public: dns_opt_t dns_opt; // must be first const int id; vector cache; int nIPv4, nIPv6; time_t cacheTime; unsigned int cacheHits; uint64_t dbQueries; void cacheHit(bool force = false) { static bool nets[NET_MAX] = {}; if (!nets[NET_IPV4]) { nets[NET_IPV4] = true; nets[NET_IPV6] = true; } time_t now = time(NULL); cacheHits++; if (force || cacheHits > (cache.size()*cache.size()/400) || (cacheHits*cacheHits > cache.size() / 20 && (now - cacheTime > 5))) { set ips; db.GetIPs(ips, 1000, nets); dbQueries++; cache.clear(); nIPv4 = 0; nIPv6 = 0; cache.reserve(ips.size()); for (set::iterator it = ips.begin(); it != ips.end(); it++) { struct in_addr addr; struct in6_addr addr6; if ((*it).GetInAddr(&addr)) { addr_t a; a.v = 4; memcpy(&a.data.v4, &addr, 4); cache.push_back(a); nIPv4++; #ifdef USE_IPV6 } else if ((*it).GetIn6Addr(&addr6)) { addr_t a; a.v = 6; memcpy(&a.data.v6, &addr6, 16); cache.push_back(a); nIPv6++; #endif } } cacheHits = 0; cacheTime = now; } } CDnsThread(CDnsSeedOpts* opts, int idIn) : id(idIn) { dns_opt.host = opts->host; dns_opt.ns = opts->ns; dns_opt.mbox = opts->mbox; dns_opt.datattl = 60; dns_opt.nsttl = 40000; dns_opt.cb = GetIPList; dns_opt.port = opts->nPort; dns_opt.nRequests = 0; cache.clear(); cache.reserve(1000); cacheTime = 0; cacheHits = 0; dbQueries = 0; nIPv4 = 0; nIPv6 = 0; cacheHit(true); } void run() { dnsserver(&dns_opt); } }; extern "C" int GetIPList(void *data, addr_t* addr, int max, int ipv4, int ipv6) { CDnsThread *thread = (CDnsThread*)data; thread->cacheHit(); unsigned int size = thread->cache.size(); unsigned int maxmax = (ipv4 ? thread->nIPv4 : 0) + (ipv6 ? thread->nIPv6 : 0); if (max > size) max = size; if (max > maxmax) max = maxmax; int i=0; while (icache[j].v == 4) || (ipv6 && thread->cache[j].v == 6); if (ok) break; j++; if (j==size) j=i; } while(1); addr[i] = thread->cache[j]; thread->cache[j] = thread->cache[i]; thread->cache[i] = addr[i]; i++; } return max; } vector dnsThread; extern "C" void* ThreadDNS(void* arg) { CDnsThread *thread = (CDnsThread*)arg; thread->run(); } int StatCompare(const CAddrReport& a, const CAddrReport& b) { if (a.uptime[4] == b.uptime[4]) { if (a.uptime[3] == b.uptime[3]) { return a.clientVersion > b.clientVersion; } else { return a.uptime[3] > b.uptime[3]; } } else { return a.uptime[4] > b.uptime[4]; } } extern "C" void* ThreadDumper(void*) { do { Sleep(100000); { FILE *f = fopen("dnsseed.dat","w+"); if (f) { CAutoFile cf(f); cf << db; } FILE *d = fopen("dnsseed.dump", "w"); vector v = db.GetAll(); sort(v.begin(), v.end(), StatCompare); fprintf(d, "# address \t%%(2h)\t%%(8h)\t%%(1d)\t%%(7d)\t%%(30d)\tblocks\tversion\n"); double stat[5]={0,0,0,0,0}; for (vector::const_iterator it = v.begin(); it < v.end(); it++) { CAddrReport rep = *it; fprintf(d, "%s\t%.2f%%\t%.2f%%\t%.2f%%\t%.2f%%\t%.2f%%\t%i\t%i \"%s\"\n", rep.ip.ToString().c_str(), 100.0*rep.uptime[0], 100.0*rep.uptime[1], 100.0*rep.uptime[2], 100.0*rep.uptime[3], 100.0*rep.uptime[4], rep.blocks, rep.clientVersion, rep.clientSubVersion.c_str()); stat[0] += rep.uptime[0]; stat[1] += rep.uptime[1]; stat[2] += rep.uptime[2]; stat[3] += rep.uptime[3]; stat[4] += rep.uptime[4]; } fclose(d); FILE *ff = fopen("dnsstats.log", "a"); fprintf(ff, "%llu %g %g %g %g %g\n", (unsigned long long)(time(NULL)), stat[0], stat[1], stat[2], stat[3], stat[4]); fclose(ff); } } while(1); } extern "C" void* ThreadStats(void*) { bool first = true; do { char c[256]; time_t tim = time(NULL); struct tm *tmp = localtime(&tim); strftime(c, 256, "[%y-%m-%d %H:%M:%S]", tmp); CAddrDbStats stats; db.GetStats(stats); if (first) { first = false; printf("\n\n\n\x1b[3A"); } else printf("\x1b[2K\x1b[u"); printf("\x1b[s"); uint64_t requests = 0; uint64_t queries = 0; for (unsigned int i=0; idns_opt.nRequests; queries += dnsThread[i]->dbQueries; } printf("%s %i/%i available (%i tried in %is, %i new, %i active), %i banned; %llu DNS requests, %llu db queries", c, stats.nGood, stats.nAvail, stats.nTracked, stats.nAge, stats.nNew, stats.nAvail - stats.nTracked - stats.nNew, stats.nBanned, (unsigned long long)requests, (unsigned long long)queries); Sleep(1000); } while(1); } static const string seeds[] = {"dnsseed.bluematt.me", "bitseed.xf2.org", "dnsseed.bitcoin.dashjr.org", "seed.bitcoin.sipa.be"}; extern "C" void* ThreadSeeder(void*) { db.Add(CService("kjy2eqzk4zwi5zd3.onion", 8333), true); do { for (int i=0; i ips; LookupHost(seeds[i].c_str(), ips); for (vector::iterator it = ips.begin(); it != ips.end(); it++) { db.Add(CService(*it, 8333), true); } } Sleep(1800000); } while(1); } int main(int argc, char **argv) { setbuf(stdout, NULL); CDnsSeedOpts opts; opts.ParseCommandLine(argc, argv); CService service(opts.tor, 9050); if (service.IsValid()) { printf("Using Tor proxy at %s\n", service.ToStringIPPort().c_str()); SetProxy(NET_TOR, service); } bool fDNS = true; if (!opts.ns) { printf("No nameserver set. Not starting DNS server.\n"); fDNS = false; } if (fDNS && !opts.host) { fprintf(stderr, "No hostname set. Please use -h.\n"); exit(1); } FILE *f = fopen("dnsseed.dat","r"); if (f) { printf("Loading dnsseed.dat..."); CAutoFile cf(f); cf >> db; if (opts.fWipeBan) db.banned.clear(); if (opts.fWipeIgnore) db.ResetIgnores(); printf("done\n"); } pthread_t threadDns, threadSeed, threadDump, threadStats; printf("Starting seeder..."); pthread_create(&threadSeed, NULL, ThreadSeeder, NULL); printf("done\n"); printf("Starting %i crawler threads...", opts.nThreads); for (int i=0; i