diff --git a/src/seeder/db.cpp b/src/seeder/db.cpp index 30394b493..24ad325f4 100644 --- a/src/seeder/db.cpp +++ b/src/seeder/db.cpp @@ -1,232 +1,234 @@ // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include void CAddrInfo::Update(bool good) { int64_t now = time(nullptr); if (ourLastTry == 0) { ourLastTry = now - MIN_RETRY; } int age = now - ourLastTry; lastTry = now; ourLastTry = now; total++; if (good) { success++; ourLastSuccess = now; } stat2H.Update(good, age, 3600 * 2); stat8H.Update(good, age, 3600 * 8); stat1D.Update(good, age, 3600 * 24); stat1W.Update(good, age, 3600 * 24 * 7); stat1M.Update(good, age, 3600 * 24 * 30); int64_t ign = GetIgnoreTime(); if (ign && (ignoreTill == 0 || ignoreTill < ign + now)) { ignoreTill = ign + now; } // fprintf(stdout, "%s: got %s result: success=%i/%i; // 2H:%.2f%%-%.2f%%(%.2f) 8H:%.2f%%-%.2f%%(%.2f) 1D:%.2f%%-%.2f%%(%.2f) // 1W:%.2f%%-%.2f%%(%.2f) \n", ToString(ip).c_str(), good ? "good" : "bad", // success, total, 100.0 * stat2H.reliability, 100.0 * (stat2H.reliability // + 1.0 - stat2H.weight), stat2H.count, 100.0 * stat8H.reliability, 100.0 // * (stat8H.reliability + 1.0 - stat8H.weight), stat8H.count, 100.0 * // stat1D.reliability, 100.0 * (stat1D.reliability + 1.0 - stat1D.weight), // stat1D.count, 100.0 * stat1W.reliability, 100.0 * (stat1W.reliability // + 1.0 - stat1W.weight), stat1W.count); } bool CAddrDb::Get_(CServiceResult &ip, int &wait) { int64_t now = time(nullptr); size_t tot = unkId.size() + ourId.size(); if (tot == 0) { wait = 5; return false; } do { size_t rnd = rand() % tot; int ret; if (rnd < unkId.size()) { std::set::iterator it = unkId.end(); it--; ret = *it; unkId.erase(it); } else { ret = ourId.front(); if (time(nullptr) - idToInfo[ret].ourLastTry < MIN_RETRY) { return false; } ourId.pop_front(); } if (idToInfo[ret].ignoreTill && idToInfo[ret].ignoreTill < now) { ourId.push_back(ret); idToInfo[ret].ourLastTry = now; } else { ip.service = idToInfo[ret].ip; ip.ourLastSuccess = idToInfo[ret].ourLastSuccess; break; } } while (1); nDirty++; return true; } int CAddrDb::Lookup_(const CService &ip) { if (ipToId.count(ip)) { return ipToId[ip]; } return -1; } void CAddrDb::Good_(const CService &addr, int clientV, std::string clientSV, int blocks) { int id = Lookup_(addr); if (id == -1) { return; } unkId.erase(id); banned.erase(addr); CAddrInfo &info = idToInfo[id]; info.clientVersion = clientV; info.clientSubVersion = clientSV; info.blocks = blocks; info.Update(true); if (info.IsGood() && goodId.count(id) == 0) { goodId.insert(id); // fprintf(stdout, "%s: good; %i good nodes now\n", // ToString(addr).c_str(), (int)goodId.size()); } nDirty++; ourId.push_back(id); } void CAddrDb::Bad_(const CService &addr, int ban) { int id = Lookup_(addr); if (id == -1) { return; } unkId.erase(id); CAddrInfo &info = idToInfo[id]; info.Update(false); uint32_t now = time(nullptr); int ter = info.GetBanTime(); if (ter) { // fprintf(stdout, "%s: terrible\n", ToString(addr).c_str()); - if (ban < ter) ban = ter; + if (ban < ter) { + ban = ter; + } } if (ban > 0) { // fprintf(stdout, "%s: ban for %i seconds\n", // ToString(addr).c_str(), ban); banned[info.ip] = ban + now; ipToId.erase(info.ip); goodId.erase(id); idToInfo.erase(id); } else { if (/*!info.IsGood() && */ goodId.count(id) == 1) { goodId.erase(id); // fprintf(stdout, "%s: not good; %i good nodes left\n", // ToString(addr).c_str(), (int)goodId.size()); } ourId.push_back(id); } nDirty++; } void CAddrDb::Add_(const CAddress &addr, bool force) { if (!force && !addr.IsRoutable()) { return; } CService ipp(addr); if (banned.count(ipp)) { time_t bantime = banned[ipp]; if (force || (bantime < time(nullptr) && addr.nTime > bantime)) { banned.erase(ipp); } else { return; } } if (ipToId.count(ipp)) { CAddrInfo &ai = idToInfo[ipToId[ipp]]; if (addr.nTime > ai.lastTry || ai.services != addr.nServices) { ai.lastTry = addr.nTime; ai.services |= addr.nServices; // fprintf(stdout, "%s: updated\n", ToString(addr).c_str()); } if (force) { ai.ignoreTill = 0; } return; } CAddrInfo ai; ai.ip = ipp; ai.services = addr.nServices; ai.lastTry = addr.nTime; ai.ourLastTry = 0; ai.total = 0; ai.success = 0; int id = nId++; idToInfo[id] = ai; ipToId[ipp] = id; // fprintf(stdout, "%s: added\n", ToString(ipp).c_str(), ipToId[ipp]); unkId.insert(id); nDirty++; } void CAddrDb::GetIPs_(std::set &ips, uint64_t requestedFlags, uint32_t max, const bool *nets) { if (goodId.size() == 0) { int id = -1; if (ourId.size() == 0) { if (unkId.size() == 0) { return; } id = *unkId.begin(); } else { id = *ourId.begin(); } if (id >= 0 && (idToInfo[id].services & requestedFlags) == requestedFlags) { ips.insert(idToInfo[id].ip); } return; } std::vector goodIdFiltered; for (auto &id : goodId) { if ((idToInfo[id].services & requestedFlags) == requestedFlags) { goodIdFiltered.push_back(id); } } if (!goodIdFiltered.size()) { return; } if (max > goodIdFiltered.size() / 2) { max = goodIdFiltered.size() / 2; } if (max < 1) { max = 1; } std::set ids; while (ids.size() < max) { ids.insert(goodIdFiltered[rand() % goodIdFiltered.size()]); } for (auto &id : ids) { CService &ip = idToInfo[id].ip; if (nets[ip.GetNetwork()]) { ips.insert(ip); } } } diff --git a/src/seeder/dns.cpp b/src/seeder/dns.cpp index 37de6ad5d..fdc2511dd 100644 --- a/src/seeder/dns.cpp +++ b/src/seeder/dns.cpp @@ -1,671 +1,672 @@ // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #define BUFLEN 512 #if defined IP_RECVDSTADDR #define DSTADDR_SOCKOPT IP_RECVDSTADDR #define DSTADDR_DATASIZE (CMSG_SPACE(sizeof(struct in6_addr))) #define dstaddr(x) (CMSG_DATA(x)) #elif defined IPV6_PKTINFO #define DSTADDR_SOCKOPT IPV6_PKTINFO #define DSTADDR_DATASIZE (CMSG_SPACE(sizeof(struct in6_pktinfo))) #define dstaddr(x) (&(((struct in6_pktinfo *)(CMSG_DATA(x)))->ipi6_addr)) #else #error "can't determine socket option" #endif union control_data { struct cmsghdr cmsg; uint8_t data[DSTADDR_DATASIZE]; }; typedef enum { CLASS_IN = 1, QCLASS_ANY = 255, } dns_class; typedef enum { TYPE_A = 1, TYPE_NS = 2, TYPE_CNAME = 5, TYPE_SOA = 6, TYPE_MX = 15, TYPE_AAAA = 28, TYPE_SRV = 33, QTYPE_ANY = 255 } dns_type; enum class DNSResponseCode : uint8_t { OK = 0, FORMAT_ERROR = 1, SERVER_FAILURE = 2, NAME_ERROR = 3, NOT_IMPLEMENTED = 4, REFUSED = 5, }; int parse_name(const uint8_t **inpos, const uint8_t *inend, const uint8_t *inbuf, char *buf, size_t bufsize) { size_t bufused = 0; int init = 1; do { if (*inpos == inend) { return -1; } // read length of next component int octet = *((*inpos)++); if (octet == 0) { buf[bufused] = 0; return 0; } // add dot in output if (!init) { if (bufused == bufsize - 1) { return -2; } buf[bufused++] = '.'; - } else + } else { init = 0; + } // handle references if ((octet & 0xC0) == 0xC0) { if (*inpos == inend) { return -1; } int ref = ((octet - 0xC0) << 8) + *((*inpos)++); if (ref < 0 || ref >= (*inpos) - inbuf - 2) { return -1; } const uint8_t *newbuf = inbuf + ref; return parse_name(&newbuf, (*inpos) - 2, inbuf, buf + bufused, bufsize - bufused); } if (octet > 63) { return -1; } // copy label while (octet) { if (*inpos == inend) { return -1; } if (bufused == bufsize - 1) { return -2; } int c = *((*inpos)++); if (c == '.') { return -1; } octet--; buf[bufused++] = c; } } while (1); } // 0: k // -1: component > 63 characters // -2: insufficent space in output // -3: two subsequent dots static int write_name(uint8_t **outpos, const uint8_t *outend, const char *name, int offset) { while (*name != 0) { const char *dot = strchr(name, '.'); const char *fin = dot; if (!dot) { fin = name + strlen(name); } if (fin - name > 63) { return -1; } if (fin == name) { return -3; } if (outend - *outpos < fin - name + 2) { return -2; } *((*outpos)++) = fin - name; memcpy(*outpos, name, fin - name); *outpos += fin - name; if (!dot) { break; } name = dot + 1; } if (offset < 0) { // no reference if (outend == *outpos) { return -2; } *((*outpos)++) = 0; } else { if (outend - *outpos < 2) { return -2; } *((*outpos)++) = (offset >> 8) | 0xC0; *((*outpos)++) = offset & 0xFF; } return 0; } static int write_record(uint8_t **outpos, const uint8_t *outend, const char *name, int offset, dns_type typ, dns_class cls, int ttl) { uint8_t *oldpos = *outpos; int error = 0; // name int ret = write_name(outpos, outend, name, offset); if (ret) { error = ret; goto error; } if (outend - *outpos < 8) { error = -4; goto error; } // type *((*outpos)++) = typ >> 8; *((*outpos)++) = typ & 0xFF; // class *((*outpos)++) = cls >> 8; *((*outpos)++) = cls & 0xFF; // ttl *((*outpos)++) = (ttl >> 24) & 0xFF; *((*outpos)++) = (ttl >> 16) & 0xFF; *((*outpos)++) = (ttl >> 8) & 0xFF; *((*outpos)++) = ttl & 0xFF; return 0; error: *outpos = oldpos; return error; } static int write_record_a(uint8_t **outpos, const uint8_t *outend, const char *name, int offset, dns_class cls, int ttl, const addr_t *ip) { if (ip->v != 4) { return -6; } uint8_t *oldpos = *outpos; int error = 0; int ret = write_record(outpos, outend, name, offset, TYPE_A, cls, ttl); if (ret) { return ret; } if (outend - *outpos < 6) { error = -5; goto error; } // rdlength *((*outpos)++) = 0; *((*outpos)++) = 4; // rdata for (int i = 0; i < 4; i++) { *((*outpos)++) = ip->data.v4[i]; } return 0; error: *outpos = oldpos; return error; } static int write_record_aaaa(uint8_t **outpos, const uint8_t *outend, const char *name, int offset, dns_class cls, int ttl, const addr_t *ip) { if (ip->v != 6) { return -6; } uint8_t *oldpos = *outpos; int error = 0; int ret = write_record(outpos, outend, name, offset, TYPE_AAAA, cls, ttl); if (ret) { return ret; } if (outend - *outpos < 6) { error = -5; goto error; } // rdlength *((*outpos)++) = 0; *((*outpos)++) = 16; // rdata for (int i = 0; i < 16; i++) { *((*outpos)++) = ip->data.v6[i]; } return 0; error: *outpos = oldpos; return error; } static int write_record_ns(uint8_t **outpos, const uint8_t *outend, const char *name, int offset, dns_class cls, int ttl, const char *ns) { uint8_t *oldpos = *outpos; int ret = write_record(outpos, outend, name, offset, TYPE_NS, cls, ttl); if (ret) { return ret; } // Predeclare to avoid jumping over declaration. uint8_t *curpos; int error = 0; if (outend - *outpos < 2) { error = -5; goto error; } (*outpos) += 2; curpos = *outpos; ret = write_name(outpos, outend, ns, -1); if (ret) { error = ret; goto error; } curpos[-2] = (*outpos - curpos) >> 8; curpos[-1] = (*outpos - curpos) & 0xFF; return 0; error: *outpos = oldpos; return error; } static int write_record_soa(uint8_t **outpos, const uint8_t *outend, const char *name, int offset, dns_class cls, int ttl, const char *mname, const char *rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum) { uint8_t *oldpos = *outpos; int ret = write_record(outpos, outend, name, offset, TYPE_SOA, cls, ttl); if (ret) { return ret; } // Predeclare variable to not jump over declarations. uint8_t *curpos; int error = 0; if (outend - *outpos < 2) { error = -5; goto error; } (*outpos) += 2; curpos = *outpos; ret = write_name(outpos, outend, mname, -1); if (ret) { error = ret; goto error; } ret = write_name(outpos, outend, rname, -1); if (ret) { error = ret; goto error; } if (outend - *outpos < 20) { error = -5; goto error; } *((*outpos)++) = (serial >> 24) & 0xFF; *((*outpos)++) = (serial >> 16) & 0xFF; *((*outpos)++) = (serial >> 8) & 0xFF; *((*outpos)++) = serial & 0xFF; *((*outpos)++) = (refresh >> 24) & 0xFF; *((*outpos)++) = (refresh >> 16) & 0xFF; *((*outpos)++) = (refresh >> 8) & 0xFF; *((*outpos)++) = refresh & 0xFF; *((*outpos)++) = (retry >> 24) & 0xFF; *((*outpos)++) = (retry >> 16) & 0xFF; *((*outpos)++) = (retry >> 8) & 0xFF; *((*outpos)++) = retry & 0xFF; *((*outpos)++) = (expire >> 24) & 0xFF; *((*outpos)++) = (expire >> 16) & 0xFF; *((*outpos)++) = (expire >> 8) & 0xFF; *((*outpos)++) = expire & 0xFF; *((*outpos)++) = (minimum >> 24) & 0xFF; *((*outpos)++) = (minimum >> 16) & 0xFF; *((*outpos)++) = (minimum >> 8) & 0xFF; *((*outpos)++) = minimum & 0xFF; curpos[-2] = (*outpos - curpos) >> 8; curpos[-1] = (*outpos - curpos) & 0xFF; return 0; error: *outpos = oldpos; return error; } static ssize_t dnshandle(dns_opt_t *opt, const uint8_t *inbuf, size_t insize, uint8_t *outbuf) { DNSResponseCode responseCode = DNSResponseCode::OK; if (insize < 12) { // DNS header return -1; } // Predeclare various variables to avoid jumping over declarations. int have_ns = 0; int max_auth_size = 0; int nquestion = 0; // copy id outbuf[0] = inbuf[0]; outbuf[1] = inbuf[1]; // copy flags; outbuf[2] = inbuf[2]; outbuf[3] = inbuf[3]; // clear response code outbuf[3] &= ~15; // check qr if (inbuf[2] & 128) { /* fprintf(stdout, "Got response?\n"); */ responseCode = DNSResponseCode::FORMAT_ERROR; goto error; } // check opcode if (((inbuf[2] & 120) >> 3) != 0) { /* fprintf(stdout, "Opcode nonzero?\n"); */ responseCode = DNSResponseCode::NOT_IMPLEMENTED; goto error; } // unset TC outbuf[2] &= ~2; // unset RA outbuf[3] &= ~128; // check questions nquestion = (inbuf[4] << 8) + inbuf[5]; if (nquestion == 0) { /* fprintf(stdout, "No questions?\n"); */ responseCode = DNSResponseCode::OK; goto error; } if (nquestion > 1) { /* fprintf(stdout, "Multiple questions %i?\n", nquestion); */ responseCode = DNSResponseCode::NOT_IMPLEMENTED; goto error; } { const uint8_t *inpos = inbuf + 12; const uint8_t *inend = inbuf + insize; char name[256]; int offset = inpos - inbuf; int ret = parse_name(&inpos, inend, inbuf, name, 256); if (ret == -1) { responseCode = DNSResponseCode::FORMAT_ERROR; goto error; } if (ret == -2) { responseCode = DNSResponseCode::REFUSED; goto error; } int namel = strlen(name), hostl = strlen(opt->host); if (strcasecmp(name, opt->host) && (namel < hostl + 2 || name[namel - hostl - 1] != '.' || strcasecmp(name + namel - hostl, opt->host))) { responseCode = DNSResponseCode::REFUSED; goto error; } if (inend - inpos < 4) { responseCode = DNSResponseCode::FORMAT_ERROR; goto error; } // copy question to output memcpy(outbuf + 12, inbuf + 12, inpos + 4 - (inbuf + 12)); // set counts outbuf[4] = 0; outbuf[5] = 1; outbuf[6] = 0; outbuf[7] = 0; outbuf[8] = 0; outbuf[9] = 0; outbuf[10] = 0; outbuf[11] = 0; // set qr outbuf[2] |= 128; int typ = (inpos[0] << 8) + inpos[1]; int cls = (inpos[2] << 8) + inpos[3]; inpos += 4; uint8_t *outpos = outbuf + (inpos - inbuf); uint8_t *outend = outbuf + BUFLEN; // fprintf(stdout, "DNS: Request host='%s' type=%i class=%i\n", name, // typ, cls); // calculate max size of authority section if (!((typ == TYPE_NS || typ == QTYPE_ANY) && (cls == CLASS_IN || cls == QCLASS_ANY))) { // authority section will be necessary, either NS or SOA uint8_t *newpos = outpos; write_record_ns(&newpos, outend, "", offset, CLASS_IN, 0, opt->ns); max_auth_size = newpos - outpos; newpos = outpos; write_record_soa(&newpos, outend, "", offset, CLASS_IN, opt->nsttl, opt->ns, opt->mbox, time(NULL), 604800, 86400, 2592000, 604800); if (max_auth_size < newpos - outpos) { max_auth_size = newpos - outpos; } // fprintf(stdout, "Authority section will claim %i bytes max\n", // max_auth_size); } // Answer section // NS records if ((typ == TYPE_NS || typ == QTYPE_ANY) && (cls == CLASS_IN || cls == QCLASS_ANY)) { int ret2 = write_record_ns(&outpos, outend - max_auth_size, "", offset, CLASS_IN, opt->nsttl, opt->ns); // fprintf(stdout, "wrote NS record: %i\n", ret2); if (!ret2) { outbuf[7]++; have_ns++; } } // SOA records if ((typ == TYPE_SOA || typ == QTYPE_ANY) && (cls == CLASS_IN || cls == QCLASS_ANY) && opt->mbox) { int ret2 = write_record_soa(&outpos, outend - max_auth_size, "", offset, CLASS_IN, opt->nsttl, opt->ns, opt->mbox, time(NULL), 604800, 86400, 2592000, 604800); // fprintf(stdout, "wrote SOA record: %i\n", ret2); if (!ret2) { outbuf[7]++; } } // A/AAAA records if ((typ == TYPE_A || typ == TYPE_AAAA || typ == QTYPE_ANY) && (cls == CLASS_IN || cls == QCLASS_ANY)) { addr_t addr[32]; int naddr = opt->cb((void *)opt, name, addr, 32, typ == TYPE_A || typ == QTYPE_ANY, typ == TYPE_AAAA || typ == QTYPE_ANY); int n = 0; while (n < naddr) { int mustbreak = 1; if (addr[n].v == 4) { mustbreak = write_record_a(&outpos, outend - max_auth_size, "", offset, CLASS_IN, opt->datattl, &addr[n]); } else if (addr[n].v == 6) { mustbreak = write_record_aaaa( &outpos, outend - max_auth_size, "", offset, CLASS_IN, opt->datattl, &addr[n]); } // fprintf(stdout, "wrote A record: %i\n", mustbreak); if (mustbreak) { break; } n++; outbuf[7]++; } } // Authority section if (!have_ns && outbuf[7]) { int ret2 = write_record_ns(&outpos, outend, "", offset, CLASS_IN, opt->nsttl, opt->ns); // fprintf(stdout, "wrote NS record: %i\n", ret2); if (!ret2) { outbuf[9]++; } } else if (!outbuf[7]) { // Didn't include any answers, so reply with SOA as this is a // negative response. If we replied with NS above we'd create a bad // horizontal referral loop, as the NS response indicates where the // resolver should try next. int ret2 = write_record_soa( &outpos, outend, "", offset, CLASS_IN, opt->nsttl, opt->ns, opt->mbox, time(NULL), 604800, 86400, 2592000, 604800); // fprintf(stdout, "wrote SOA record: %i\n", ret2); if (!ret2) { outbuf[9]++; } } // set AA outbuf[2] |= 4; return outpos - outbuf; } error: // set response code outbuf[3] |= uint8_t(responseCode) & 0xF; // set counts outbuf[4] = 0; outbuf[5] = 0; outbuf[6] = 0; outbuf[7] = 0; outbuf[8] = 0; outbuf[9] = 0; outbuf[10] = 0; outbuf[11] = 0; return 12; } static int listenSocket = -1; int dnsserver(dns_opt_t *opt) { struct sockaddr_in6 si_other; int senderSocket = -1; senderSocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (senderSocket == -1) { return -3; } int replySocket; if (listenSocket == -1) { struct sockaddr_in6 si_me; if ((listenSocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1) { listenSocket = -1; return -1; } replySocket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (replySocket == -1) { close(listenSocket); return -1; } int sockopt = 1; setsockopt(listenSocket, IPPROTO_IPV6, DSTADDR_SOCKOPT, &sockopt, sizeof sockopt); memset((char *)&si_me, 0, sizeof(si_me)); si_me.sin6_family = AF_INET6; si_me.sin6_port = htons(opt->port); si_me.sin6_addr = in6addr_any; if (bind(listenSocket, (struct sockaddr *)&si_me, sizeof(si_me)) == -1) { return -2; } } uint8_t inbuf[BUFLEN], outbuf[BUFLEN]; struct iovec iov[1] = { { .iov_base = inbuf, .iov_len = sizeof(inbuf), }, }; union control_data cmsg; msghdr msg; msg.msg_name = &si_other; msg.msg_namelen = sizeof(si_other); msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = &cmsg; msg.msg_controllen = sizeof(cmsg); for (; 1; ++(opt->nRequests)) { ssize_t insize = recvmsg(listenSocket, &msg, 0); // uint8_t *addr = (uint8_t*)&si_other.sin_addr.s_addr; // fprintf(stdout, "DNS: Request %llu from %i.%i.%i.%i:%i of %i // bytes\n", (unsigned long long)(opt->nRequests), addr[0], addr[1], // addr[2], addr[3], ntohs(si_other.sin_port), (int)insize); if (insize <= 0) { continue; } ssize_t ret = dnshandle(opt, inbuf, insize, outbuf); if (ret <= 0) { continue; } bool handled = false; for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; hdr = CMSG_NXTHDR(&msg, hdr)) { if (hdr->cmsg_level == IPPROTO_IP && hdr->cmsg_type == DSTADDR_SOCKOPT) { msg.msg_iov[0].iov_base = outbuf; msg.msg_iov[0].iov_len = ret; sendmsg(listenSocket, &msg, 0); msg.msg_iov[0].iov_base = inbuf; msg.msg_iov[0].iov_len = sizeof(inbuf); handled = true; } } if (!handled) { sendto(listenSocket, outbuf, ret, 0, (struct sockaddr *)&si_other, sizeof(si_other)); } } return 0; } diff --git a/src/seeder/main.cpp b/src/seeder/main.cpp index af3f440b2..cb9e43768 100644 --- a/src/seeder/main.cpp +++ b/src/seeder/main.cpp @@ -1,563 +1,564 @@ // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const std::function G_TRANSLATION_FUN = nullptr; static const int CONTINUE_EXECUTION = -1; static const int DEFAULT_NUM_THREADS = 96; static const int DEFAULT_PORT = 53; static const int DEFAULT_NUM_DNS_THREADS = 4; static const bool DEFAULT_WIPE_BAN = false; static const bool DEFAULT_WIPE_IGNORE = false; static const std::string DEFAULT_EMAIL = ""; static const std::string DEFAULT_NAMESERVER = ""; static const std::string DEFAULT_HOST = ""; static const std::string DEFAULT_TOR_PROXY = ""; static const std::string DEFAULT_IPV4_PROXY = ""; static const std::string DEFAULT_IPV6_PROXY = ""; class CDnsSeedOpts { public: int nThreads; int nPort; int nDnsThreads; bool fWipeBan; bool fWipeIgnore; std::string mbox; std::string ns; std::string host; std::string tor; std::string ipv4_proxy; std::string ipv6_proxy; std::set filter_whitelist; CDnsSeedOpts() : nThreads(DEFAULT_NUM_THREADS), nPort(DEFAULT_PORT), nDnsThreads(DEFAULT_NUM_DNS_THREADS), fWipeBan(DEFAULT_WIPE_BAN), fWipeIgnore(DEFAULT_WIPE_IGNORE), mbox(DEFAULT_EMAIL), ns(DEFAULT_NAMESERVER), host(DEFAULT_HOST), tor(DEFAULT_TOR_PROXY), ipv4_proxy(DEFAULT_IPV4_PROXY), ipv6_proxy(DEFAULT_IPV6_PROXY) {} int ParseCommandLine(int argc, char **argv) { SetupSeederArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); return EXIT_FAILURE; } if (HelpRequested(gArgs)) { std::string strUsage = "Bitcoin-cash-seeder\nUsage: bitcoin-seeder " "-host= -ns= [-mbox=] " "[-threads=] [-port=]\n\n" + gArgs.GetHelpMessage(); fprintf(stdout, "%s", strUsage.c_str()); return EXIT_SUCCESS; } nThreads = gArgs.GetArg("-threads", DEFAULT_NUM_THREADS); nPort = gArgs.GetArg("-port", DEFAULT_PORT); nDnsThreads = gArgs.GetArg("-dnsthreads", DEFAULT_NUM_DNS_THREADS); fWipeBan = gArgs.GetBoolArg("-wipeban", DEFAULT_WIPE_BAN); fWipeIgnore = gArgs.GetBoolArg("-wipeignore", DEFAULT_WIPE_IGNORE); mbox = gArgs.GetArg("-mbox", DEFAULT_EMAIL); ns = gArgs.GetArg("-ns", DEFAULT_NAMESERVER); host = gArgs.GetArg("-host", DEFAULT_HOST); tor = gArgs.GetArg("-onion", DEFAULT_TOR_PROXY); ipv4_proxy = gArgs.GetArg("-proxyipv4", DEFAULT_IPV4_PROXY); ipv6_proxy = gArgs.GetArg("-proxyipv6", DEFAULT_IPV6_PROXY); SelectParams(gArgs.GetChainName()); if (gArgs.IsArgSet("-filter")) { // Parse whitelist additions std::string flagString = gArgs.GetArg("-filter", ""); size_t flagstartpos = 0; while (flagstartpos < flagString.size()) { size_t flagendpos = flagString.find_first_of(',', flagstartpos); uint64_t flag = atoi64(flagString.substr( flagstartpos, (flagendpos - flagstartpos))); filter_whitelist.insert(flag); if (flagendpos == std::string::npos) { break; } flagstartpos = flagendpos + 1; } } if (filter_whitelist.empty()) { filter_whitelist.insert(NODE_NETWORK); filter_whitelist.insert(NODE_NETWORK | NODE_BLOOM); filter_whitelist.insert(NODE_NETWORK | NODE_XTHIN); filter_whitelist.insert(NODE_NETWORK | NODE_BLOOM | NODE_XTHIN); } return CONTINUE_EXECUTION; } private: void SetupSeederArgs() { gArgs.AddArg("-?", _("Print this help message and exit"), false, OptionsCategory::OPTIONS); gArgs.AddArg("-host=", _("Hostname of the DNS seed"), false, OptionsCategory::OPTIONS); gArgs.AddArg("-ns=", _("Hostname of the nameserver"), false, OptionsCategory::OPTIONS); gArgs.AddArg("-mbox=", _("E-Mail address reported in SOA records"), false, OptionsCategory::OPTIONS); gArgs.AddArg("-threads=", _("Number of crawlers to run in parallel (default 96)"), false, OptionsCategory::OPTIONS); gArgs.AddArg("-dnsthreads=", _("Number of DNS server threads (default 4)"), false, OptionsCategory::OPTIONS); gArgs.AddArg("-port=", _("UDP port to listen on (default 53)"), false, OptionsCategory::CONNECTION); gArgs.AddArg("-onion=", _("Tor proxy IP/Port"), false, OptionsCategory::CONNECTION); gArgs.AddArg("-proxyipv4=", _("IPV4 SOCKS5 proxy IP/Port"), false, OptionsCategory::CONNECTION); gArgs.AddArg("-proxyipv6=", _("IPV6 SOCKS5 proxy IP/Port"), false, OptionsCategory::CONNECTION); gArgs.AddArg("-filter=", _("Allow these flag combinations as filters"), false, OptionsCategory::OPTIONS); gArgs.AddArg("-wipeban", _("Wipe list of banned nodes"), false, OptionsCategory::CONNECTION); gArgs.AddArg("-wipeignore", _("Wipe list of ignored nodes"), false, OptionsCategory::CONNECTION); gArgs.AddArg( "-help-debug", _("Show all debugging options (usage: --help -help-debug)"), false, OptionsCategory::DEBUG_TEST); SetupChainParamsBaseOptions(); gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN); gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN); } }; extern "C" { #include } CAddrDb db; extern "C" void *ThreadCrawler(void *data) { int *nThreads = (int *)data; do { std::vector ips; int wait = 5; db.GetMany(ips, 16, wait); int64_t now = time(nullptr); if (ips.empty()) { wait *= 1000; wait += rand() % (500 * *nThreads); Sleep(wait); continue; } std::vector addr; for (size_t i = 0; i < ips.size(); i++) { CServiceResult &res = ips[i]; res.nBanTime = 0; res.nClientV = 0; res.nHeight = 0; res.strClientV = ""; bool getaddr = res.ourLastSuccess + 86400 < now; res.fGood = TestNode(res.service, res.nBanTime, res.nClientV, res.strClientV, res.nHeight, getaddr ? &addr : nullptr); } db.ResultMany(ips); db.Add(addr); } while (1); return nullptr; } extern "C" uint32_t GetIPList(void *thread, char *requestedHostname, addr_t *addr, uint32_t max, uint32_t ipv4, uint32_t ipv6); class CDnsThread { public: struct FlagSpecificData { int nIPv4, nIPv6; std::vector cache; time_t cacheTime; unsigned int cacheHits; FlagSpecificData() : nIPv4(0), nIPv6(0), cacheTime(0), cacheHits(0) {} }; dns_opt_t dns_opt; // must be first const int id; std::map perflag; std::atomic dbQueries; std::set filterWhitelist; void cacheHit(uint64_t requestedFlags, bool force = false) { static bool nets[NET_MAX] = {}; if (!nets[NET_IPV4]) { nets[NET_IPV4] = true; nets[NET_IPV6] = true; } time_t now = time(nullptr); FlagSpecificData &thisflag = perflag[requestedFlags]; thisflag.cacheHits++; if (force || thisflag.cacheHits * 400 > (thisflag.cache.size() * thisflag.cache.size()) || (thisflag.cacheHits * thisflag.cacheHits * 20 > thisflag.cache.size() && (now - thisflag.cacheTime > 5))) { std::set ips; db.GetIPs(ips, requestedFlags, 1000, nets); dbQueries++; thisflag.cache.clear(); thisflag.nIPv4 = 0; thisflag.nIPv6 = 0; thisflag.cache.reserve(ips.size()); for (auto &ip : ips) { struct in_addr addr; struct in6_addr addr6; if (ip.GetInAddr(&addr)) { addr_t a; a.v = 4; memcpy(&a.data.v4, &addr, 4); thisflag.cache.push_back(a); thisflag.nIPv4++; } else if (ip.GetIn6Addr(&addr6)) { addr_t a; a.v = 6; memcpy(&a.data.v6, &addr6, 16); thisflag.cache.push_back(a); thisflag.nIPv6++; } } thisflag.cacheHits = 0; thisflag.cacheTime = now; } } CDnsThread(CDnsSeedOpts *opts, int idIn) : id(idIn) { dns_opt.host = opts->host.c_str(); dns_opt.ns = opts->ns.c_str(); dns_opt.mbox = opts->mbox.c_str(); dns_opt.datattl = 3600; dns_opt.nsttl = 40000; dns_opt.cb = GetIPList; dns_opt.port = opts->nPort; dns_opt.nRequests = 0; dbQueries = 0; perflag.clear(); filterWhitelist = opts->filter_whitelist; } void run() { dnsserver(&dns_opt); } }; extern "C" uint32_t GetIPList(void *data, char *requestedHostname, addr_t *addr, uint32_t max, uint32_t ipv4, uint32_t ipv6) { CDnsThread *thread = (CDnsThread *)data; uint64_t requestedFlags = 0; int hostlen = strlen(requestedHostname); if (hostlen > 1 && requestedHostname[0] == 'x' && requestedHostname[1] != '0') { char *pEnd; uint64_t flags = (uint64_t)strtoull(requestedHostname + 1, &pEnd, 16); if (*pEnd == '.' && pEnd <= requestedHostname + 17 && std::find(thread->filterWhitelist.begin(), thread->filterWhitelist.end(), flags) != thread->filterWhitelist.end()) { requestedFlags = flags; } else { return 0; } } else if (strcasecmp(requestedHostname, thread->dns_opt.host)) { return 0; } thread->cacheHit(requestedFlags); auto &thisflag = thread->perflag[requestedFlags]; uint32_t size = thisflag.cache.size(); uint32_t maxmax = (ipv4 ? thisflag.nIPv4 : 0) + (ipv6 ? thisflag.nIPv6 : 0); if (max > size) { max = size; } if (max > maxmax) { max = maxmax; } uint32_t i = 0; while (i < max) { uint32_t j = i + (rand() % (size - i)); do { bool ok = (ipv4 && thisflag.cache[j].v == 4) || (ipv6 && thisflag.cache[j].v == 6); if (ok) { break; } j++; if (j == size) { j = i; } } while (1); addr[i] = thisflag.cache[j]; thisflag.cache[j] = thisflag.cache[i]; thisflag.cache[i] = addr[i]; i++; } return max; } std::vector dnsThread; extern "C" void *ThreadDNS(void *arg) { CDnsThread *thread = (CDnsThread *)arg; thread->run(); return nullptr; } 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 *) { int count = 0; do { // First 100s, than 200s, 400s, 800s, 1600s, and then 3200s forever Sleep(100000 << count); if (count < 5) { count++; } { std::vector v = db.GetAll(); sort(v.begin(), v.end(), StatCompare); FILE *f = fsbridge::fopen("dnsseed.dat.new", "w+"); if (f) { { CAutoFile cf(f, SER_DISK, CLIENT_VERSION); cf << db; } rename("dnsseed.dat.new", "dnsseed.dat"); } FILE *d = fsbridge::fopen("dnsseed.dump", "w"); fprintf(d, "# address good " "lastSuccess %%(2h) %%(8h) %%(1d) %%(7d) " "%%(30d) blocks svcs version\n"); double stat[5] = {0, 0, 0, 0, 0}; for (CAddrReport rep : v) { fprintf( d, "%-47s %4d %11" PRId64 " %6.2f%% %6.2f%% %6.2f%% %6.2f%% %6.2f%% %6i %08" PRIx64 " %5i \"%s\"\n", rep.ip.ToString().c_str(), (int)rep.fGood, rep.lastSuccess, 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.services, 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 = fsbridge::fopen("dnsstats.log", "a"); fprintf(ff, "%llu %g %g %g %g %g\n", (unsigned long long)(time(nullptr)), stat[0], stat[1], stat[2], stat[3], stat[4]); fclose(ff); } } while (1); return nullptr; } extern "C" void *ThreadStats(void *) { bool first = true; do { char c[256]; time_t tim = time(nullptr); struct tm *tmp = localtime(&tim); strftime(c, 256, "[%y-%m-%d %H:%M:%S]", tmp); CAddrDbStats stats; db.GetStats(stats); if (first) { first = false; fprintf(stdout, "\n\n\n\x1b[3A"); - } else + } else { fprintf(stdout, "\x1b[2K\x1b[u"); + } fprintf(stdout, "\x1b[s"); uint64_t requests = 0; uint64_t queries = 0; for (unsigned int i = 0; i < dnsThread.size(); i++) { requests += dnsThread[i]->dns_opt.nRequests; queries += dnsThread[i]->dbQueries; } fprintf(stdout, "%s %i/%i available (%i tried in %is, %i new, %i active), %i " "banned; %llu DNS requests, %llu db queries\n", 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); return nullptr; } const static unsigned int MAX_HOSTS_PER_SEED = 128; extern "C" void *ThreadSeeder(void *) { do { for (const std::string &seed : Params().DNSSeeds()) { std::vector ips; LookupHost(seed.c_str(), ips, MAX_HOSTS_PER_SEED, true); for (auto &ip : ips) { db.Add(CAddress(CService(ip, GetDefaultPort()), ServiceFlags()), true); } } Sleep(1800000); } while (1); return nullptr; } int main(int argc, char **argv) { // The logger dump everything on the console by default. LogInstance().m_print_to_console = true; signal(SIGPIPE, SIG_IGN); setbuf(stdout, nullptr); CDnsSeedOpts opts; int parseResults = opts.ParseCommandLine(argc, argv); if (parseResults != CONTINUE_EXECUTION) { return parseResults; } fprintf(stdout, "Supporting whitelisted filters: "); for (std::set::const_iterator it = opts.filter_whitelist.begin(); it != opts.filter_whitelist.end(); it++) { if (it != opts.filter_whitelist.begin()) { fprintf(stdout, ","); } fprintf(stdout, "0x%lx", (unsigned long)*it); } fprintf(stdout, "\n"); if (!opts.tor.empty()) { CService service(LookupNumeric(opts.tor.c_str(), 9050)); if (service.IsValid()) { fprintf(stdout, "Using Tor proxy at %s\n", service.ToStringIPPort().c_str()); SetProxy(NET_ONION, proxyType(service)); } } if (!opts.ipv4_proxy.empty()) { CService service(LookupNumeric(opts.ipv4_proxy.c_str(), 9050)); if (service.IsValid()) { fprintf(stdout, "Using IPv4 proxy at %s\n", service.ToStringIPPort().c_str()); SetProxy(NET_IPV4, proxyType(service)); } } if (!opts.ipv6_proxy.empty()) { CService service(LookupNumeric(opts.ipv6_proxy.c_str(), 9050)); if (service.IsValid()) { fprintf(stdout, "Using IPv6 proxy at %s\n", service.ToStringIPPort().c_str()); SetProxy(NET_IPV6, proxyType(service)); } } bool fDNS = true; fprintf(stdout, "Using %s.\n", gArgs.GetChainName().c_str()); netMagic = Params().NetMagic(); if (opts.ns.empty()) { fprintf(stdout, "No nameserver set. Not starting DNS server.\n"); fDNS = false; } if (fDNS && opts.host.empty()) { fprintf(stderr, "No hostname set. Please use -h.\n"); return EXIT_FAILURE; } if (fDNS && opts.mbox.empty()) { fprintf(stderr, "No e-mail address set. Please use -m.\n"); return EXIT_FAILURE; } FILE *f = fsbridge::fopen("dnsseed.dat", "r"); if (f) { fprintf(stdout, "Loading dnsseed.dat..."); CAutoFile cf(f, SER_DISK, CLIENT_VERSION); cf >> db; if (opts.fWipeBan) { db.banned.clear(); fprintf(stdout, "Ban list wiped..."); } if (opts.fWipeIgnore) { db.ResetIgnores(); fprintf(stdout, "Ignore list wiped..."); } fprintf(stdout, "done\n"); } pthread_t threadDns, threadSeed, threadDump, threadStats; if (fDNS) { fprintf(stdout, "Starting %i DNS threads for %s on %s (port %i)...", opts.nDnsThreads, opts.host.c_str(), opts.ns.c_str(), opts.nPort); dnsThread.clear(); for (int i = 0; i < opts.nDnsThreads; i++) { dnsThread.push_back(new CDnsThread(&opts, i)); pthread_create(&threadDns, nullptr, ThreadDNS, dnsThread[i]); fprintf(stdout, "."); Sleep(20); } fprintf(stdout, "done\n"); } fprintf(stdout, "Starting seeder..."); pthread_create(&threadSeed, nullptr, ThreadSeeder, nullptr); fprintf(stdout, "done\n"); fprintf(stdout, "Starting %i crawler threads...", opts.nThreads); pthread_attr_t attr_crawler; pthread_attr_init(&attr_crawler); pthread_attr_setstacksize(&attr_crawler, 0x20000); for (int i = 0; i < opts.nThreads; i++) { pthread_t thread; pthread_create(&thread, &attr_crawler, ThreadCrawler, &opts.nThreads); } pthread_attr_destroy(&attr_crawler); fprintf(stdout, "done\n"); pthread_create(&threadStats, nullptr, ThreadStats, nullptr); pthread_create(&threadDump, nullptr, ThreadDumper, nullptr); void *res; pthread_join(threadDump, &res); return EXIT_SUCCESS; }