Skip to content

Commit

Permalink
Merge pull request #6068 from rgacogne/dnsdist-edns-options
Browse files Browse the repository at this point in the history
dnsdist: Add DNSQuestion:getEDNSOptions() to access incoming EDNS options
  • Loading branch information
rgacogne committed Oct 3, 2018
2 parents 9c2197c + cbf4e13 commit ef8640d
Show file tree
Hide file tree
Showing 14 changed files with 876 additions and 117 deletions.
8 changes: 4 additions & 4 deletions pdns/dnsdist-cache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,16 @@ DNSDistPacketCache::~DNSDistPacketCache()

bool DNSDistPacketCache::getClientSubnet(const char* packet, unsigned int consumed, uint16_t len, boost::optional<Netmask>& subnet)
{
char * optRDLen = NULL;
uint16_t optRDPosition;
size_t remaining = 0;

int res = getEDNSOptionsStart(const_cast<char*>(packet), consumed, len, &optRDLen, &remaining);
int res = getEDNSOptionsStart(const_cast<char*>(packet), consumed, len, &optRDPosition, &remaining);

if (res == 0) {
char * ecsOptionStart = NULL;
char * ecsOptionStart = nullptr;
size_t ecsOptionSize = 0;

res = getEDNSOption(optRDLen, remaining, EDNSOptionCode::ECS, &ecsOptionStart, &ecsOptionSize);
res = getEDNSOption(const_cast<char*>(packet) + optRDPosition, remaining, EDNSOptionCode::ECS, &ecsOptionStart, &ecsOptionSize);

if (res == 0 && ecsOptionSize > (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) {

Expand Down
204 changes: 126 additions & 78 deletions pdns/dnsdist-ecs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ int rewriteResponseWithoutEDNS(const std::string& initialPacket, vector<uint8_t>
rrname = pr.getName();
rrtype = pr.get16BitInt();
rrclass = pr.get16BitInt();

DNSPacketWriter pw(newContent, rrname, rrtype, rrclass, dh->opcode);
pw.getHeader()->id=dh->id;
pw.getHeader()->qr=dh->qr;
Expand All @@ -77,7 +77,7 @@ int rewriteResponseWithoutEDNS(const std::string& initialPacket, vector<uint8_t>
pw.getHeader()->ad=dh->ad;
pw.getHeader()->cd=dh->cd;
pw.getHeader()->rcode=dh->rcode;

/* consume remaining qd if any */
if (qdcount > 1) {
for(idx = 1; idx < qdcount; idx++) {
Expand Down Expand Up @@ -191,13 +191,13 @@ int locateEDNSOptRR(const std::string& packet, uint16_t * optStart, size_t * opt
}

/* extract the start of the OPT RR in a QUERY packet if any */
int getEDNSOptionsStart(char* packet, const size_t offset, const size_t len, char ** optRDLen, size_t * remaining)
int getEDNSOptionsStart(const char* packet, const size_t offset, const size_t len, uint16_t* optRDPosition, size_t * remaining)
{
assert(packet != NULL);
assert(optRDLen != NULL);
assert(remaining != NULL);
assert(packet != nullptr);
assert(optRDPosition != nullptr);
assert(remaining != nullptr);
const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet);

if (offset >= len) {
return ENOENT;
}
Expand Down Expand Up @@ -229,13 +229,13 @@ int getEDNSOptionsStart(char* packet, const size_t offset, const size_t len, cha
return ENOENT;

pos += DNS_TTL_SIZE;
*optRDLen = packet + pos;
*optRDPosition = pos;
*remaining = len - pos;

return 0;
}

static void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength)
void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength)
{
Netmask sourceNetmask(source, ECSPrefixLength);
EDNSSubnetOpts ecsOpts;
Expand Down Expand Up @@ -264,22 +264,20 @@ void generateOptRR(const std::string& optRData, string& res, uint16_t udpPayload
res.append(optRData.c_str(), optRData.length());
}

static bool replaceEDNSClientSubnetOption(char * const packet, const size_t packetSize, uint16_t * const len, const ComboAddress& remote, char * const oldEcsOptionStart, size_t const oldEcsOptionSize, unsigned char * const optRDLen, uint16_t ECSPrefixLength)
static bool replaceEDNSClientSubnetOption(char * const packet, const size_t packetSize, uint16_t * const len, char * const oldEcsOptionStart, size_t const oldEcsOptionSize, unsigned char * const optRDLen, const string& newECSOption)
{
assert(packet != NULL);
assert(len != NULL);
assert(oldEcsOptionStart != NULL);
assert(optRDLen != NULL);
string ECSOption;
generateECSOption(remote, ECSOption, ECSPrefixLength);

if (ECSOption.size() == oldEcsOptionSize) {
if (newECSOption.size() == oldEcsOptionSize) {
/* same size as the existing option */
memcpy(oldEcsOptionStart, ECSOption.c_str(), oldEcsOptionSize);
memcpy(oldEcsOptionStart, newECSOption.c_str(), oldEcsOptionSize);
}
else {
/* different size than the existing option */
const unsigned int newPacketLen = *len + (ECSOption.length() - oldEcsOptionSize);
const unsigned int newPacketLen = *len + (newECSOption.length() - oldEcsOptionSize);
const size_t beforeOptionLen = oldEcsOptionStart - packet;
const size_t dataBehindSize = *len - beforeOptionLen - oldEcsOptionSize;

Expand All @@ -290,91 +288,140 @@ static bool replaceEDNSClientSubnetOption(char * const packet, const size_t pack

/* fix the size of ECS Option RDLen */
uint16_t newRDLen = (optRDLen[0] * 256) + optRDLen[1];
newRDLen += (ECSOption.size() - oldEcsOptionSize);
newRDLen += (newECSOption.size() - oldEcsOptionSize);
optRDLen[0] = newRDLen / 256;
optRDLen[1] = newRDLen % 256;

if (dataBehindSize > 0) {
memmove(oldEcsOptionStart, oldEcsOptionStart + oldEcsOptionSize, dataBehindSize);
}
memcpy(oldEcsOptionStart + dataBehindSize, ECSOption.c_str(), ECSOption.size());
memcpy(oldEcsOptionStart + dataBehindSize, newECSOption.c_str(), newECSOption.size());
*len = newPacketLen;
}

return true;
}

bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const unsigned int consumed, uint16_t* const len, bool* const ednsAdded, bool* const ecsAdded, const ComboAddress& remote, bool overrideExisting, uint16_t ecsPrefixLength)
/* This function looks for an OPT RR, return true if a valid one was found (even if there was no options)
and false otherwise. */
bool parseEDNSOptions(DNSQuestion& dq)
{
assert(packet != NULL);
assert(len != NULL);
assert(consumed <= (size_t) *len);
assert(ednsAdded != NULL);
assert(ecsAdded != NULL);
unsigned char * optRDLen = NULL;
size_t remaining = 0;
assert(dq.dh != nullptr);
assert(dq.consumed <= dq.len);
assert(dq.len <= dq.size);

if (dq.ednsOptions != nullptr) {
return true;
}

int res = getEDNSOptionsStart(packet, consumed, *len, (char**) &optRDLen, &remaining);
dq.ednsOptions = std::make_shared<std::map<uint16_t, EDNSOptionView> >();
const char* packet = reinterpret_cast<const char*>(dq.dh);

size_t remaining = 0;
uint16_t optRDPosition;
int res = getEDNSOptionsStart(packet, dq.consumed, dq.len, &optRDPosition, &remaining);

if (res == 0) {
char * ecsOptionStart = NULL;
size_t ecsOptionSize = 0;

res = getEDNSOption((char*)optRDLen, remaining, EDNSOptionCode::ECS, &ecsOptionStart, &ecsOptionSize);

if (res == 0) {
/* there is already an ECS value */
if (overrideExisting) {
return replaceEDNSClientSubnetOption(packet, packetSize, len, remote, ecsOptionStart, ecsOptionSize, optRDLen, ecsPrefixLength);
}
} else {
/* we need to add one EDNS0 ECS option, fixing the size of EDNS0 RDLENGTH */
/* getEDNSOptionsStart has already checked that there is exactly one AR,
no NS and no AN */
string ECSOption;
generateECSOption(remote, ECSOption, ecsPrefixLength);
const size_t ECSOptionSize = ECSOption.size();

/* check if the existing buffer is large enough */
if (packetSize - *len <= ECSOptionSize) {
return false;
}
res = getEDNSOptions(packet + optRDPosition, remaining, *dq.ednsOptions);
return (res == 0);
}

uint16_t newRDLen = (optRDLen[0] * 256) + optRDLen[1];
newRDLen += ECSOptionSize;
optRDLen[0] = newRDLen / 256;
optRDLen[1] = newRDLen % 256;
return false;
}

memcpy(packet + *len, ECSOption.c_str(), ECSOptionSize);
*len += ECSOptionSize;
*ecsAdded = true;
}
static bool addECSToExistingOPT(char* const packet, size_t const packetSize, uint16_t* const len, const string& newECSOption, unsigned char* optRDLen, bool* const ecsAdded)
{
/* we need to add one EDNS0 ECS option, fixing the size of EDNS0 RDLENGTH */
/* getEDNSOptionsStart has already checked that there is exactly one AR,
no NS and no AN */

/* check if the existing buffer is large enough */
const size_t newECSOptionSize = newECSOption.size();
if (packetSize - *len <= newECSOptionSize) {
return false;
}
else {
/* we need to add a EDNS0 RR with one EDNS0 ECS option, fixing the AR count */
string EDNSRR;
struct dnsheader* dh = (struct dnsheader*) packet;
string optRData;
generateECSOption(remote, optRData, ecsPrefixLength);
generateOptRR(optRData, EDNSRR, g_EdnsUDPPayloadSize, false);

/* does it fit in the existing buffer? */
if (packetSize - *len <= EDNSRR.size()) {
return false;
}

uint16_t arcount = ntohs(dh->arcount);
arcount++;
dh->arcount = htons(arcount);
*ednsAdded = true;
uint16_t newRDLen = (optRDLen[0] * 256) + optRDLen[1];
newRDLen += newECSOptionSize;
optRDLen[0] = newRDLen / 256;
optRDLen[1] = newRDLen % 256;

memcpy(packet + *len, newECSOption.c_str(), newECSOptionSize);
*len += newECSOptionSize;
*ecsAdded = true;

return true;
}

static bool addEDNSWithECS(char* const packet, size_t const packetSize, uint16_t* const len, const string& newECSOption, bool* const ednsAdded)
{
/* we need to add a EDNS0 RR with one EDNS0 ECS option, fixing the AR count */
string EDNSRR;
struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(packet);
generateOptRR(newECSOption, EDNSRR, g_EdnsUDPPayloadSize, false);

/* does it fit in the existing buffer? */
if (packetSize - *len <= EDNSRR.size()) {
return false;
}

uint16_t arcount = ntohs(dh->arcount);
arcount++;
dh->arcount = htons(arcount);
*ednsAdded = true;

memcpy(packet + *len, EDNSRR.c_str(), EDNSRR.size());
*len += EDNSRR.size();
return true;
}

bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const unsigned int consumed, uint16_t* const len, bool* const ednsAdded, bool* const ecsAdded, bool overrideExisting, const string& newECSOption)
{
assert(packet != nullptr);
assert(len != nullptr);
assert(consumed <= (size_t) *len);
assert(ednsAdded != nullptr);
assert(ecsAdded != nullptr);
uint16_t optRDPosition = 0;
size_t remaining = 0;

int res = getEDNSOptionsStart(packet, consumed, *len, &optRDPosition, &remaining);

memcpy(packet + *len, EDNSRR.c_str(), EDNSRR.size());
*len += EDNSRR.size();
if (res != 0) {
return addEDNSWithECS(packet, packetSize, len, newECSOption, ednsAdded);
}

unsigned char* optRDLen = reinterpret_cast<unsigned char*>(packet) + optRDPosition;
char * ecsOptionStart = nullptr;
size_t ecsOptionSize = 0;

res = getEDNSOption(reinterpret_cast<char*>(optRDLen), remaining, EDNSOptionCode::ECS, &ecsOptionStart, &ecsOptionSize);

if (res == 0) {
/* there is already an ECS value */
if (!overrideExisting) {
return true;
}

return replaceEDNSClientSubnetOption(packet, packetSize, len, ecsOptionStart, ecsOptionSize, optRDLen, newECSOption);
} else {
/* we have an EDNS OPT RR but no existing ECS option */
return addECSToExistingOPT(packet, packetSize, len, newECSOption, optRDLen, ecsAdded);
}

return true;
}

bool handleEDNSClientSubnet(DNSQuestion& dq, bool* ednsAdded, bool* ecsAdded)
{
assert(dq.remote != nullptr);
string newECSOption;
generateECSOption(dq.ecsSet ? dq.ecs.getNetwork() : *dq.remote, newECSOption, dq.ecsSet ? dq.ecs.getBits() : dq.ecsPrefixLength);
char* packet = reinterpret_cast<char*>(dq.dh);

return handleEDNSClientSubnet(packet, dq.size, dq.consumed, &dq.len, ednsAdded, ecsAdded, dq.ecsOverride, newECSOption);
}

static int removeEDNSOptionFromOptions(unsigned char* optionsStart, const uint16_t optionsLen, const uint16_t optionCodeToRemove, uint16_t* newOptionsLen)
{
unsigned char* p = optionsStart;
Expand Down Expand Up @@ -582,11 +629,11 @@ bool addEDNS(dnsheader* dh, uint16_t& len, const size_t size, bool dnssecOK, uin

bool addEDNSToQueryTurnedResponse(DNSQuestion& dq)
{
char* optRDLen = nullptr;
uint16_t optRDPosition;
/* remaining is at least the size of the rdlen + the options if any + the following records if any */
size_t remaining = 0;

int res = getEDNSOptionsStart(reinterpret_cast<char*>(dq.dh), dq.consumed, dq.len, &optRDLen, &remaining);
int res = getEDNSOptionsStart(reinterpret_cast<char*>(dq.dh), dq.consumed, dq.len, &optRDPosition, &remaining);

if (res != 0) {
/* if the initial query did not have EDNS0, we are done */
Expand All @@ -599,6 +646,7 @@ bool addEDNSToQueryTurnedResponse(DNSQuestion& dq)
return false;
}

char* optRDLen = reinterpret_cast<char*>(dq.dh) + optRDPosition;
char * optPtr = (optRDLen - (/* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + /* Z */ 2));

const uint8_t* zPtr = (const uint8_t*) optPtr + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE;
Expand Down Expand Up @@ -663,10 +711,10 @@ catch(...)

bool queryHasEDNS(const DNSQuestion& dq)
{
char * optRDLen = nullptr;
uint16_t optRDPosition;
size_t ecsRemaining = 0;

int res = getEDNSOptionsStart(reinterpret_cast<char*>(dq.dh), dq.consumed, dq.len, &optRDLen, &ecsRemaining);
int res = getEDNSOptionsStart(reinterpret_cast<char*>(dq.dh), dq.consumed, dq.len, &optRDPosition, &ecsRemaining);
if (res == 0) {
return true;
}
Expand Down
9 changes: 7 additions & 2 deletions pdns/dnsdist-ecs.hh
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,20 @@ extern uint16_t g_PayloadSizeSelfGenAnswers;

int rewriteResponseWithoutEDNS(const std::string& initialPacket, vector<uint8_t>& newContent);
int locateEDNSOptRR(const std::string& packet, uint16_t * optStart, size_t * optLen, bool * last);
bool handleEDNSClientSubnet(char * packet, size_t packetSize, unsigned int consumed, uint16_t * len, bool* ednsAdded, bool* ecsAdded, const ComboAddress& remote, bool overrideExisting, uint16_t ecsPrefixLength);
void generateOptRR(const std::string& optRData, string& res, uint16_t udpPayloadSize, bool dnssecOK);
void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength);
int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove);
int rewriteResponseWithoutEDNSOption(const std::string& initialPacket, const uint16_t optionCodeToSkip, vector<uint8_t>& newContent);
int getEDNSOptionsStart(char* packet, const size_t offset, const size_t len, char ** optRDLen, size_t * remaining);
int getEDNSOptionsStart(const char* packet, const size_t offset, const size_t len, uint16_t* optRDPosition, size_t * remaining);
bool isEDNSOptionInOpt(const std::string& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind);
bool addEDNS(dnsheader* dh, uint16_t& len, const size_t size, bool dnssecOK, uint16_t payloadSize);
bool addEDNSToQueryTurnedResponse(DNSQuestion& dq);

bool handleEDNSClientSubnet(DNSQuestion& dq, bool* ednsAdded, bool* ecsAdded);
bool handleEDNSClientSubnet(char* const packet, const size_t packetSize, const unsigned int consumed, uint16_t* const len, bool* const ednsAdded, bool* const ecsAdded, bool overrideExisting, const string& newECSOption);

bool parseEDNSOptions(DNSQuestion& dq);

int getEDNSZ(const DNSQuestion& dq);
bool queryHasEDNS(const DNSQuestion& dq);

6 changes: 4 additions & 2 deletions pdns/dnsdist-lua-actions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ TeeAction::~TeeAction()
d_worker.join();
}


DNSAction::Action TeeAction::operator()(DNSQuestion* dq, string* ruleresult) const
{
if(dq->tcp) {
Expand All @@ -173,7 +172,10 @@ DNSAction::Action TeeAction::operator()(DNSQuestion* dq, string* ruleresult) con
query.reserve(dq->size);
query.assign((char*) dq->dh, len);

if (!handleEDNSClientSubnet(const_cast<char*>(query.c_str()), query.capacity(), dq->qname->wirelength(), &len, &ednsAdded, &ecsAdded, dq->ecsSet ? dq->ecs.getNetwork() : *dq->remote, dq->ecsOverride, dq->ecsSet ? dq->ecs.getBits() : dq->ecsPrefixLength)) {
string newECSOption;
generateECSOption(dq->ecsSet ? dq->ecs.getNetwork() : *dq->remote, newECSOption, dq->ecsSet ? dq->ecs.getBits() : dq->ecsPrefixLength);

if (!handleEDNSClientSubnet(const_cast<char*>(query.c_str()), query.capacity(), dq->qname->wirelength(), &len, &ednsAdded, &ecsAdded, dq->ecsOverride, newECSOption)) {
return DNSAction::Action::None;
}

Expand Down
9 changes: 9 additions & 0 deletions pdns/dnsdist-lua-bindings-dnsquestion.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ void setupLuaBindingsDNSQuestion()
g_lua.registerFunction<bool(DNSQuestion::*)()>("getDO", [](const DNSQuestion& dq) {
return getEDNSZ(dq) & EDNS_HEADER_FLAG_DO;
});

g_lua.registerFunction<std::map<uint16_t, EDNSOptionView>(DNSQuestion::*)()>("getEDNSOptions", [](DNSQuestion& dq) {
if (dq.ednsOptions == nullptr) {
parseEDNSOptions(dq);
}

return *dq.ednsOptions;
});

g_lua.registerFunction<void(DNSQuestion::*)(std::string)>("sendTrap", [](const DNSQuestion& dq, boost::optional<std::string> reason) {
#ifdef HAVE_NET_SNMP
if (g_snmpAgent && g_snmpTrapsEnabled) {
Expand Down
Loading

0 comments on commit ef8640d

Please sign in to comment.