diff options
Diffstat (limited to 'apt-pkg/contrib/srvrec.cc')
-rw-r--r-- | apt-pkg/contrib/srvrec.cc | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/apt-pkg/contrib/srvrec.cc b/apt-pkg/contrib/srvrec.cc new file mode 100644 index 0000000..3eb5f1d --- /dev/null +++ b/apt-pkg/contrib/srvrec.cc @@ -0,0 +1,211 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + SRV record support + + ##################################################################### */ + /*}}}*/ +#include <config.h> + +#include <netdb.h> + +#include <arpa/nameser.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <resolv.h> +#include <time.h> + +#include <algorithm> +#include <memory> +#include <tuple> + +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/strutl.h> + +#include "srvrec.h" + +bool SrvRec::operator==(SrvRec const &other) const +{ + return (std::tie(target, priority, weight, port) == + std::tie(other.target, other.priority, other.weight, other.port)); +} + +bool GetSrvRecords(std::string host, int port, std::vector<SrvRec> &Result) +{ + // try SRV only for hostnames, not for IP addresses + { + struct in_addr addr4; + struct in6_addr addr6; + if (inet_pton(AF_INET, host.c_str(), &addr4) == 1 || + inet_pton(AF_INET6, host.c_str(), &addr6) == 1) + return true; + } + + std::string target; + int res; + struct servent s_ent_buf; + struct servent *s_ent = nullptr; + std::vector<char> buf(1024); + + res = getservbyport_r(htons(port), "tcp", &s_ent_buf, buf.data(), buf.size(), &s_ent); + if (res != 0 || s_ent == nullptr) + return false; + + strprintf(target, "_%s._tcp.%s", s_ent->s_name, host.c_str()); + return GetSrvRecords(target, Result); +} + +bool GetSrvRecords(std::string name, std::vector<SrvRec> &Result) +{ + unsigned char answer[PACKETSZ]; + int answer_len, compressed_name_len; + int answer_count; +#if __RES >= 19991006 + struct __res_state res; + + if (res_ninit(&res) != 0) + return _error->Errno("res_init", "Failed to init resolver"); + + // Close on return + std::shared_ptr<void> guard(&res, res_nclose); + + answer_len = res_nquery(&res, name.c_str(), C_IN, T_SRV, answer, sizeof(answer)); +#else + if (res_init() != 0) + return _error->Errno("res_init", "Failed to init resolver"); + + answer_len = res_query(name.c_str(), C_IN, T_SRV, answer, sizeof(answer)); +#endif //__RES >= 19991006 + if (answer_len == -1) + return false; + if (answer_len < (int)sizeof(HEADER)) + return _error->Warning("Not enough data from res_query (%i)", answer_len); + + // check the header + HEADER *header = (HEADER*)answer; + if (header->rcode != NOERROR) + return _error->Warning("res_query returned rcode %i", header->rcode); + answer_count = ntohs(header->ancount); + if (answer_count <= 0) + return _error->Warning("res_query returned no answers (%i) ", answer_count); + + // skip the header + compressed_name_len = dn_skipname(answer+sizeof(HEADER), answer+answer_len); + if(compressed_name_len < 0) + return _error->Warning("dn_skipname failed %i", compressed_name_len); + + // pt points to the first answer record, go over all of them now + unsigned char *pt = answer+sizeof(HEADER)+compressed_name_len+QFIXEDSZ; + while ((int)Result.size() < answer_count && pt < answer+answer_len) + { + u_int16_t type, klass, priority, weight, port, dlen; + char buf[MAXDNAME]; + + compressed_name_len = dn_skipname(pt, answer+answer_len); + if (compressed_name_len < 0) + return _error->Warning("dn_skipname failed (2): %i", + compressed_name_len); + pt += compressed_name_len; + if (((answer+answer_len) - pt) < 16) + return _error->Warning("packet too short"); + + // extract the data out of the result buffer + #define extract_u16(target, p) target = *p++ << 8; target |= *p++; + + extract_u16(type, pt); + if(type != T_SRV) + return _error->Warning("Unexpected type excepted %x != %x", + T_SRV, type); + extract_u16(klass, pt); + if(klass != C_IN) + return _error->Warning("Unexpected class excepted %x != %x", + C_IN, klass); + pt += 4; // ttl + extract_u16(dlen, pt); + extract_u16(priority, pt); + extract_u16(weight, pt); + extract_u16(port, pt); + + #undef extract_u16 + + compressed_name_len = dn_expand(answer, answer+answer_len, pt, buf, sizeof(buf)); + if(compressed_name_len < 0) + return _error->Warning("dn_expand failed %i", compressed_name_len); + pt += compressed_name_len; + + // add it to our class + Result.emplace_back(buf, priority, weight, port); + } + + // implement load balancing as specified in RFC-2782 + + // sort them by priority + std::stable_sort(Result.begin(), Result.end()); + + if (_config->FindB("Debug::Acquire::SrvRecs", false)) + for(auto const &R : Result) + std::cerr << "SrvRecs: got " << R.target + << " prio: " << R.priority + << " weight: " << R.weight + << '\n'; + + return true; +} + +SrvRec PopFromSrvRecs(std::vector<SrvRec> &Recs) +{ + // FIXME: instead of the simplistic shuffle below use the algorithm + // described in rfc2782 (with weights) + // and figure out how the weights need to be adjusted if + // a host refuses connections + +#if 0 // all code below is only needed for the weight adjusted selection + // assign random number ranges + int prev_weight = 0; + int prev_priority = 0; + for(std::vector<SrvRec>::iterator I = Result.begin(); + I != Result.end(); ++I) + { + if(prev_priority != I->priority) + prev_weight = 0; + I->random_number_range_start = prev_weight; + I->random_number_range_end = prev_weight + I->weight; + prev_weight = I->random_number_range_end; + prev_priority = I->priority; + + if (_config->FindB("Debug::Acquire::SrvRecs", false) == true) + std::cerr << "SrvRecs: got " << I->target + << " prio: " << I->priority + << " weight: " << I->weight + << std::endl; + } + + // go over the code in reverse order and note the max random range + int max = 0; + prev_priority = 0; + for(std::vector<SrvRec>::iterator I = Result.end(); + I != Result.begin(); --I) + { + if(prev_priority != I->priority) + max = I->random_number_range_end; + I->random_number_range_max = max; + } +#endif + + // shuffle in a very simplistic way for now (equal weights) + std::vector<SrvRec>::iterator I = Recs.begin(); + std::vector<SrvRec>::iterator const J = std::find_if(Recs.begin(), Recs.end(), + [&I](SrvRec const &J) { return I->priority != J.priority; }); + + // clock seems random enough. + I += std::max(static_cast<clock_t>(0), clock()) % std::distance(I, J); + SrvRec const selected = std::move(*I); + Recs.erase(I); + + if (_config->FindB("Debug::Acquire::SrvRecs", false) == true) + std::cerr << "PopFromSrvRecs: selecting " << selected.target << std::endl; + + return selected; +} |