Merge pull request #2 from svost/master
[novacoin-seeder.git] / main.cpp
1 #include <algorithm>
2
3 #include <pthread.h>
4 #include <signal.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <getopt.h>
8
9 #include "bitcoin.h"
10 #include "db.h"
11
12 #define NTHREADS 24
13
14 using namespace std;
15
16 bool NoLog = false;
17
18 class CDnsSeedOpts {
19 public:
20   int nThreads;
21   int nPort;
22   int nP2Port;
23   int nDnsThreads;
24   int fWipeBan;
25   int fWipeIgnore;
26   const char *mbox;
27   const char *ns;
28   const char *host;
29   const char *tor;
30   const char *magic;
31   int fNoStatsLog;
32   vector<string> vSeeds;
33   
34   CDnsSeedOpts() : nThreads(24), nDnsThreads(24), nPort(53), fWipeBan(0), fWipeIgnore(0), mbox(NULL), ns(NULL), host(NULL), tor(NULL), magic(NULL), fNoStatsLog(0), vSeeds()
35     { nP2Port = GetDefaultPort(); }
36   
37   void ParseCommandLine(int argc, char **argv) {
38     static const char *help = "NovaCoin-seeder\n"
39                               "Usage: %s -h <host> -n <ns> [-m <mbox>] [-t <threads>] [-p <port>]\n"
40                               "\n"
41                               "Options:\n"
42                               "-s <seed>       Seed node to collect peers from (replaces default)\n"
43                               "-h <host>       Hostname of the DNS seed\n"
44                               "-n <ns>         Hostname of the nameserver\n"
45                               "-m <mbox>       E-Mail address reported in SOA records\n"
46                               "-t <threads>    Number of crawlers to run in parallel (default 24)\n"
47                               "-d <threads>    Number of DNS server threads (default 24)\n"
48                               "-p <port>       UDP port to listen on (default 53)\n"
49                               "-o <ip:port>    Tor proxy IP/Port\n"
50                               "--p2port <port> P2P port to connect to\n"
51                               "--magic <hex>   Magic string/network prefix\n"
52                               "--wipeban       Wipe list of banned nodes\n"
53                               "--wipeignore    Wipe list of ignored nodes\n"
54                               "--nostatslog    Do not write dnsstats.log file (default write)\n"
55                               "-?, --help      Show this text\n"
56                               "\n";
57     bool showHelp = false;
58
59     while(1) {
60       static struct option long_options[] = {
61         {"seed", required_argument, 0, 's'},
62         {"host", required_argument, 0, 'h'},
63         {"ns",   required_argument, 0, 'n'},
64         {"mbox", required_argument, 0, 'm'},
65         {"threads", required_argument, 0, 't'},
66         {"dnsthreads", required_argument, 0, 'd'},
67         {"port", required_argument, 0, 'p'},
68         {"onion", required_argument, 0, 'o'},
69         {"p2port", required_argument, 0, 'b'},
70         {"magic", required_argument, 0, 'k'},
71         {"wipeban", no_argument, &fWipeBan, 1},
72         {"wipeignore", no_argument, &fWipeBan, 1},
73         {"nostatslog", no_argument, &fNoStatsLog, 1},
74         {"help", no_argument, 0, 'h'},
75         {0, 0, 0, 0}
76       };
77       int option_index = 0;
78       int c = getopt_long(argc, argv, "s:h:n:m:t:p:d:o:b:k:", long_options, &option_index);
79       if (c == -1) break;
80       switch (c) {
81         case 's': {
82           vSeeds.push_back(optarg);
83           break;
84         }
85
86         case 'h': {
87           host = optarg;
88           break;
89         }
90         
91         case 'm': {
92           mbox = optarg;
93           break;
94         }
95         
96         case 'n': {
97           ns = optarg;
98           break;
99         }
100         
101         case 't': {
102           int n = strtol(optarg, NULL, 10);
103           if (n > 0 && n < 1000) nThreads = n;
104           break;
105         }
106
107         case 'd': {
108           int n = strtol(optarg, NULL, 10);
109           if (n > 0 && n < 1000) nDnsThreads = n;
110           break;
111         }
112
113         case 'p': {
114           int p = strtol(optarg, NULL, 10);
115           if (p > 0 && p < 65536) nPort = p;
116           break;
117         }
118         
119         case 'o': {
120           tor = optarg;
121           break;
122         }
123
124         case 'b': {
125           int p = strtol(optarg, NULL, 10);
126           if (p > 0 && p < 65536) nP2Port = p;
127           break;
128         }
129
130         case 'k': {
131           long int n;
132           unsigned int c;
133           magic = optarg;
134           if (strlen(magic)!=8)
135             break;
136           n = strtol(magic, NULL, 16);
137           if (n==0 && strcmp(magic, "00000000"))
138             break;
139           for (n=0; n<4; ++n) {
140             sscanf(&magic[n*2], "%2x", &c);
141             pchMessageStart[n] = (unsigned char) (c & 0xff);
142           }
143           break;
144         }
145
146         case '?': {
147           showHelp = true;
148           break;
149         }
150       }
151     }
152     if (host != NULL && ns == NULL) showHelp = true;
153     if (showHelp) fprintf(stderr, help, argv[0]);
154   }
155 };
156
157 extern "C" {
158 #include "dns.h"
159 }
160
161 CAddrDb db;
162
163 extern "C" void* ThreadCrawler(void* data) {
164   do {
165     CService ip;
166     int wait = 5;
167     if (!db.Get(ip, wait)) {
168       wait *= 1000;
169       wait += rand() % (500 * NTHREADS);
170       Sleep(wait);
171       continue;
172     }
173     int ban = 0;
174     vector<CAddress> addr;
175     int clientV = 0;
176     int blocks = 0;
177     std::string clientSV;
178     bool ret = TestNode(ip,ban,clientV,clientSV,blocks,addr);
179     db.Add(addr);
180     if (ret) {
181       db.Good(ip, clientV, clientSV, blocks);
182     } else {
183       db.Bad(ip, ban);
184     }
185   } while(1);
186 }
187
188 extern "C" int GetIPList(void *thread, addr_t *addr, int max, int ipv4, int ipv6);
189
190 class CDnsThread {
191 public:
192   dns_opt_t dns_opt; // must be first
193   const int id;
194   vector<addr_t> cache;
195   int nIPv4, nIPv6;
196   time_t cacheTime;
197   unsigned int cacheHits;
198   uint64_t dbQueries;
199
200   void cacheHit(bool force = false) {
201     static bool nets[NET_MAX] = {};
202     if (!nets[NET_IPV4]) {
203         nets[NET_IPV4] = true;
204         nets[NET_IPV6] = true;
205     }
206     time_t now = time(NULL);
207     cacheHits++;
208     if (force || cacheHits > (cache.size()*cache.size()/400) || (cacheHits*cacheHits > cache.size() / 20 && (now - cacheTime > 5))) {
209       set<CNetAddr> ips;
210       db.GetIPs(ips, 1000, nets);
211       dbQueries++;
212       cache.clear();
213       nIPv4 = 0;
214       nIPv6 = 0;
215       cache.reserve(ips.size());
216       for (set<CNetAddr>::iterator it = ips.begin(); it != ips.end(); it++) {
217         struct in_addr addr;
218         struct in6_addr addr6;
219         if ((*it).GetInAddr(&addr)) {
220           addr_t a;
221           a.v = 4;
222           memcpy(&a.data.v4, &addr, 4);
223           cache.push_back(a);
224           nIPv4++;
225 #ifdef USE_IPV6
226         } else if ((*it).GetIn6Addr(&addr6)) {
227           addr_t a;
228           a.v = 6;
229           memcpy(&a.data.v6, &addr6, 16);
230           cache.push_back(a);
231           nIPv6++;
232 #endif
233         }
234       }
235       cacheHits = 0;
236       cacheTime = now;
237     }
238   }
239
240   CDnsThread(CDnsSeedOpts* opts, int idIn) : id(idIn) {
241     dns_opt.host = opts->host;
242     dns_opt.ns = opts->ns;
243     dns_opt.mbox = opts->mbox;
244     dns_opt.datattl = 60;
245     dns_opt.nsttl = 40000;
246     dns_opt.cb = GetIPList;
247     dns_opt.port = opts->nPort;
248     dns_opt.nRequests = 0;
249     cache.clear();
250     cache.reserve(1000);
251     cacheTime = 0;
252     cacheHits = 0;
253     dbQueries = 0;
254     nIPv4 = 0;
255     nIPv6 = 0;
256     cacheHit(true);
257   }
258
259   void run() {
260     dnsserver(&dns_opt);
261   }
262 };
263
264 extern "C" int GetIPList(void *data, addr_t* addr, int max, int ipv4, int ipv6) {
265   CDnsThread *thread = (CDnsThread*)data;
266   thread->cacheHit();
267   unsigned int size = thread->cache.size();
268   unsigned int maxmax = (ipv4 ? thread->nIPv4 : 0) + (ipv6 ? thread->nIPv6 : 0);
269   if (max > size)
270     max = size;
271   if (max > maxmax)
272     max = maxmax;
273   int i=0;
274   while (i<max) {
275     int j = i + (rand() % (size - i));
276     do {
277         bool ok = (ipv4 && thread->cache[j].v == 4) || 
278                   (ipv6 && thread->cache[j].v == 6);
279         if (ok) break;
280         j++;
281         if (j==size)
282             j=i;
283     } while(1);
284     addr[i] = thread->cache[j];
285     thread->cache[j] = thread->cache[i];
286     thread->cache[i] = addr[i];
287     i++;
288   }
289   return max;
290 }
291
292 vector<CDnsThread*> dnsThread;
293
294 extern "C" void* ThreadDNS(void* arg) {
295   CDnsThread *thread = (CDnsThread*)arg;
296   thread->run();
297 }
298
299 int StatCompare(const CAddrReport& a, const CAddrReport& b) {
300   if (a.uptime[4] == b.uptime[4]) {
301     if (a.uptime[3] == b.uptime[3]) {
302       return a.clientVersion > b.clientVersion;
303     } else {
304       return a.uptime[3] > b.uptime[3];
305     }
306   } else {
307     return a.uptime[4] > b.uptime[4];
308   }
309 }
310
311 extern "C" void* ThreadDumper(void*) {
312   do {
313     Sleep(100000);
314     {
315       FILE *f = fopen("dnsseed.dat.new","w+");
316       if (f) {
317         {
318           CAutoFile cf(f);
319           cf << db;
320         }
321         rename("dnsseed.dat.new", "dnsseed.dat");
322       }
323       FILE *d = fopen("dnsseed.dump", "w");
324       vector<CAddrReport> v = db.GetAll();
325       sort(v.begin(), v.end(), StatCompare);
326       fprintf(d, "# address        \t%%(2h)\t%%(8h)\t%%(1d)\t%%(7d)\t%%(30d)\tblocks\tversion\n");
327       double stat[5]={0,0,0,0,0};
328       for (vector<CAddrReport>::const_iterator it = v.begin(); it < v.end(); it++) {
329         CAddrReport rep = *it;
330         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());
331         stat[0] += rep.uptime[0];
332         stat[1] += rep.uptime[1];
333         stat[2] += rep.uptime[2];
334         stat[3] += rep.uptime[3];
335         stat[4] += rep.uptime[4];
336       }
337       fclose(d);
338       if (NoLog == false)
339       {
340       FILE *ff = fopen("dnsstats.log", "a");
341       fprintf(ff, "%llu %g %g %g %g %g\n", (unsigned long long)(time(NULL)), stat[0], stat[1], stat[2], stat[3], stat[4]);
342       fclose(ff);
343       }
344     }
345   } while(1);
346 }
347
348 extern "C" void* ThreadStats(void*) {
349   bool first = true;
350   do {
351     char c[256];
352     time_t tim = time(NULL);
353     struct tm *tmp = localtime(&tim);
354     strftime(c, 256, "[%y-%m-%d %H:%M:%S]", tmp);
355     CAddrDbStats stats;
356     db.GetStats(stats);
357     if (first)
358     {
359       first = false;
360       printf("\n\n\n\x1b[3A");
361     }
362     else
363       printf("\x1b[2K\x1b[u");
364     printf("\x1b[s");
365     uint64_t requests = 0;
366     uint64_t queries = 0;
367     for (unsigned int i=0; i<dnsThread.size(); i++) {
368       requests += dnsThread[i]->dns_opt.nRequests;
369       queries += dnsThread[i]->dbQueries;
370     }
371     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);
372     Sleep(1000);
373   } while(1);
374 }
375
376 static vector<string> vSeeds;
377 unsigned short nP2Port;
378
379 extern "C" void* ThreadSeeder(void*) {
380   vector<string> vDnsSeeds;
381   vector<string>::iterator itr;
382   for (itr = vSeeds.begin(); itr != vSeeds.end(); itr++) {
383     size_t len = itr->length();
384     if (len>=6 && !itr->compare(len-6, 6, ".onion"))
385       db.Add(CService(itr->c_str(), nP2Port), true);
386     else
387       vDnsSeeds.push_back(*itr);
388   }
389   do {
390     for (itr = vDnsSeeds.begin(); itr != vDnsSeeds.end(); itr++) {
391       vector<CNetAddr> ips;
392       LookupHost(itr->c_str(), ips);
393       for (vector<CNetAddr>::iterator it = ips.begin(); it != ips.end(); it++) {
394         db.Add(CService(*it, nP2Port), true);
395       }
396     }
397     Sleep(1800000);
398   } while(1);
399 }
400
401 int main(int argc, char **argv) {
402   signal(SIGPIPE, SIG_IGN);
403   setbuf(stdout, NULL);
404   CDnsSeedOpts opts;
405   opts.ParseCommandLine(argc, argv);
406   nP2Port = opts.nP2Port;
407   vSeeds.reserve(vSeeds.size() + opts.vSeeds.size());
408   vSeeds.insert(vSeeds.end(), opts.vSeeds.begin(), opts.vSeeds.end());
409   if (opts.vSeeds.empty()) {
410     vSeeds.push_back("novacoin.ru");
411     vSeeds.push_back("itzod.ru");
412   }
413   if (opts.tor) {
414     CService service(opts.tor, 9050);
415     if (service.IsValid()) {
416       printf("Using Tor proxy at %s\n", service.ToStringIPPort().c_str());
417       SetProxy(NET_TOR, service);
418     }
419   }
420   bool fDNS = true;
421   if (!opts.ns) {
422     printf("No nameserver set. Not starting DNS server.\n");
423     fDNS = false;
424   }
425   if (fDNS && !opts.host) {
426     fprintf(stderr, "No hostname set. Please use -h.\n");
427     exit(1);
428   }
429   FILE *f = fopen("dnsseed.dat","r");
430   if (f) {
431     printf("Loading dnsseed.dat...");
432     CAutoFile cf(f);
433     cf >> db;
434     if (opts.fWipeBan)
435         db.banned.clear();
436     if (opts.fWipeIgnore)
437         db.ResetIgnores();
438     printf("done\n");
439   }
440   if (opts.fNoStatsLog) NoLog = true;
441   pthread_t threadDns, threadSeed, threadDump, threadStats;
442   printf("Starting seeder...");
443   pthread_create(&threadSeed, NULL, ThreadSeeder, NULL);
444   printf("done\n");
445   printf("Starting %i crawler threads...", opts.nThreads);
446   for (int i=0; i<opts.nThreads; i++) {
447     pthread_t thread;
448     pthread_create(&thread, NULL, ThreadCrawler, NULL);
449   }
450   printf("done\n");
451   pthread_create(&threadDump, NULL, ThreadDumper, NULL);
452   if (fDNS) {
453     printf("Starting %i DNS threads for %s on %s (port %i)...", opts.nDnsThreads, opts.host, opts.ns, opts.nPort);
454     dnsThread.clear();
455     for (int i=0; i<opts.nDnsThreads; i++) {
456       dnsThread.push_back(new CDnsThread(&opts, i));
457       pthread_create(&threadDns, NULL, ThreadDNS, dnsThread[i]);
458       printf(".");
459       Sleep(20);
460     }
461     printf("done\n");
462   }
463   pthread_create(&threadStats, NULL, ThreadStats, NULL);
464   void* res;
465   pthread_join(threadDump, &res);
466   return 0;
467 }