1
0
Fork 0
apt/apt-pkg/contrib/srvrec.cc
Daniel Baumann 6810ba718b
Adding upstream version 3.0.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-20 21:10:43 +02:00

212 lines
6.7 KiB
C++

// -*- mode: cpp; mode: fold -*-
// Description /*{{{*/
/* ######################################################################
SRV record support
##################################################################### */
/*}}}*/
#include <config.h>
#include <netdb.h>
#include <ctime>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <netinet/in.h>
#include <resolv.h>
#include <array>
#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::array<char, 1024> buf;
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;
}