diff options
Diffstat (limited to 'test-dnsdist_cc.cc')
-rw-r--r-- | test-dnsdist_cc.cc | 2083 |
1 files changed, 2083 insertions, 0 deletions
diff --git a/test-dnsdist_cc.cc b/test-dnsdist_cc.cc new file mode 100644 index 0000000..8bf0e99 --- /dev/null +++ b/test-dnsdist_cc.cc @@ -0,0 +1,2083 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_NO_MAIN + +#include <boost/test/unit_test.hpp> +#include <unistd.h> + +#include "dnsdist.hh" +#include "dnsdist-ecs.hh" +#include "dnsdist-xpf.hh" + +#include "dolog.hh" +#include "dnsname.hh" +#include "dnsparser.hh" +#include "dnswriter.hh" +#include "ednsoptions.hh" +#include "ednscookies.hh" +#include "ednssubnet.hh" + +BOOST_AUTO_TEST_SUITE(test_dnsdist_cc) + +static const uint16_t ECSSourcePrefixV4 = 24; +static const uint16_t ECSSourcePrefixV6 = 56; + +static void validateQuery(const PacketBuffer& packet, bool hasEdns=true, bool hasXPF=false, uint16_t additionals=0, uint16_t answers=0, uint16_t authorities=0) +{ + MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size()); + + BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com."); + + BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U); + BOOST_CHECK_EQUAL(mdp.d_header.ancount, answers); + BOOST_CHECK_EQUAL(mdp.d_header.nscount, authorities); + uint16_t expectedARCount = additionals + (hasEdns ? 1U : 0U) + (hasXPF ? 1U : 0U); + BOOST_CHECK_EQUAL(mdp.d_header.arcount, expectedARCount); +} + +static void validateECS(const PacketBuffer& packet, const ComboAddress& expected) +{ + ComboAddress rem("::1"); + unsigned int consumed = 0; + uint16_t qtype; + uint16_t qclass; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed); + DNSQuestion dq(&qname, qtype, qclass, nullptr, &rem, const_cast<PacketBuffer&>(packet), dnsdist::Protocol::DoUDP, nullptr); + BOOST_CHECK(parseEDNSOptions(dq)); + BOOST_REQUIRE(dq.ednsOptions != nullptr); + BOOST_CHECK_EQUAL(dq.ednsOptions->size(), 1U); + const auto& ecsOption = dq.ednsOptions->find(EDNSOptionCode::ECS); + BOOST_REQUIRE(ecsOption != dq.ednsOptions->cend()); + + string expectedOption; + generateECSOption(expected, expectedOption, expected.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + /* we need to skip the option code and length, which are not included */ + BOOST_REQUIRE_EQUAL(ecsOption->second.values.size(), 1U); + BOOST_CHECK_EQUAL(expectedOption.substr(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), std::string(ecsOption->second.values.at(0).content, ecsOption->second.values.at(0).size)); +} + +static void validateResponse(const PacketBuffer& packet, bool hasEdns, uint8_t additionalCount=0) +{ + MOADNSParser mdp(false, reinterpret_cast<const char*>(packet.data()), packet.size()); + + BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com."); + + BOOST_CHECK_EQUAL(mdp.d_header.qr, 1U); + BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U); + BOOST_CHECK_EQUAL(mdp.d_header.ancount, 1U); + BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U); + BOOST_CHECK_EQUAL(mdp.d_header.arcount, (hasEdns ? 1U : 0U) + additionalCount); +} + +BOOST_AUTO_TEST_CASE(test_addXPF) +{ + static const uint16_t xpfOptionCode = 65422; + + struct timespec queryTime; + gettime(&queryTime); // does not have to be accurate ("realTime") in tests + ComboAddress remote; + DNSName name("www.powerdns.com."); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + PacketBuffer queryWithXPF; + + { + PacketBuffer packet = query; + + /* large enough packet */ + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime); + + BOOST_CHECK(addXPF(dq, xpfOptionCode)); + BOOST_CHECK(packet.size() > query.size()); + validateQuery(packet, false, true); + queryWithXPF = packet; + } + + { + PacketBuffer packet = query; + + /* packet is already too large for the 4096 limit over UDP */ + packet.resize(4096); + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime); + + BOOST_REQUIRE(!addXPF(dq, xpfOptionCode)); + BOOST_CHECK_EQUAL(packet.size(), 4096U); + packet.resize(query.size()); + validateQuery(packet, false, false); + } + + { + PacketBuffer packet = query; + + /* packet with trailing data (overriding it) */ + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime); + + /* add trailing data */ + const size_t trailingDataSize = 10; + /* Making sure we have enough room to allow for fake trailing data */ + packet.resize(packet.size() + trailingDataSize); + for (size_t idx = 0; idx < trailingDataSize; idx++) { + packet.push_back('A'); + } + + BOOST_CHECK(addXPF(dq, xpfOptionCode)); + BOOST_CHECK_EQUAL(packet.size(), queryWithXPF.size()); + BOOST_CHECK_EQUAL(memcmp(queryWithXPF.data(), packet.data(), queryWithXPF.size()), 0); + validateQuery(packet, false, true); + } +} + +BOOST_AUTO_TEST_CASE(addECSWithoutEDNS) +{ + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.0.2.1"); + DNSName name("www.powerdns.com."); + string newECSOption; + generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + uint16_t len = query.size(); + + /* large enough packet */ + PacketBuffer packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption)); + BOOST_CHECK(packet.size() > query.size()); + BOOST_CHECK_EQUAL(ednsAdded, true); + BOOST_CHECK_EQUAL(ecsAdded, true); + validateQuery(packet); + validateECS(packet, remote); + PacketBuffer queryWithEDNS = packet; + + /* not large enough packet */ + packet = query; + + ednsAdded = false; + ecsAdded = false; + consumed = 0; + qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption)); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + packet.resize(query.size()); + validateQuery(packet, false); + + /* packet with trailing data (overriding it) */ + packet = query; + ednsAdded = false; + ecsAdded = false; + consumed = 0; + qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + /* add trailing data */ + const size_t trailingDataSize = 10; + /* Making sure we have enough room to allow for fake trailing data */ + packet.resize(packet.size() + trailingDataSize); + for (size_t idx = 0; idx < trailingDataSize; idx++) { + packet[len + idx] = 'A'; + } + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption)); + BOOST_REQUIRE_EQUAL(packet.size(), queryWithEDNS.size()); + BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet.data(), queryWithEDNS.size()), 0); + BOOST_CHECK_EQUAL(ednsAdded, true); + BOOST_CHECK_EQUAL(ecsAdded, true); + validateQuery(packet); +} + +BOOST_AUTO_TEST_CASE(addECSWithoutEDNSButWithAnswer) +{ + /* this might happen for NOTIFY queries where, according to rfc1996: + "If ANCOUNT>0, then the answer section represents an + unsecure hint at the new RRset for this <QNAME,QCLASS,QTYPE>". + */ + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.0.2.1"); + DNSName name("www.powerdns.com."); + string newECSOption; + generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::ANSWER, false); + pw.xfrIP(remote.sin4.sin_addr.s_addr); + pw.commit(); + uint16_t len = query.size(); + + /* large enough packet */ + PacketBuffer packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption)); + BOOST_CHECK(packet.size() > query.size()); + BOOST_CHECK_EQUAL(ednsAdded, true); + BOOST_CHECK_EQUAL(ecsAdded, true); + validateQuery(packet, true, false, 0, 1); + validateECS(packet, remote); + PacketBuffer queryWithEDNS = packet; + + /* not large enough packet */ + packet = query; + + ednsAdded = false; + ecsAdded = false; + consumed = 0; + qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption)); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + packet.resize(query.size()); + validateQuery(packet, false, false, 0, 1); + + /* packet with trailing data (overriding it) */ + packet = query; + ednsAdded = false; + ecsAdded = false; + consumed = 0; + qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + /* add trailing data */ + const size_t trailingDataSize = 10; + /* Making sure we have enough room to allow for fake trailing data */ + packet.resize(packet.size() + trailingDataSize); + for (size_t idx = 0; idx < trailingDataSize; idx++) { + packet[len + idx] = 'A'; + } + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption)); + BOOST_REQUIRE_EQUAL(packet.size(), queryWithEDNS.size()); + BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet.data(), queryWithEDNS.size()), 0); + BOOST_CHECK_EQUAL(ednsAdded, true); + BOOST_CHECK_EQUAL(ecsAdded, true); + validateQuery(packet, true, false, 0, 1); +} + +BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed) +{ + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.0.2.1"); + DNSName name("www.powerdns.com."); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + uint16_t qclass; + DNSName qname(reinterpret_cast<const char *>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + BOOST_CHECK(qclass == QClass::IN); + + DNSQuestion dq(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr); + /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */ + BOOST_CHECK(!parseEDNSOptions(dq)); + + /* And now we add our own ECS */ + BOOST_CHECK(handleEDNSClientSubnet(dq, ednsAdded, ecsAdded)); + BOOST_CHECK_GT(packet.size(), query.size()); + BOOST_CHECK_EQUAL(ednsAdded, true); + BOOST_CHECK_EQUAL(ecsAdded, true); + validateQuery(packet); + validateECS(packet, remote); + + /* trailing data */ + packet = query; + packet.resize(2048); + + ednsAdded = false; + ecsAdded = false; + consumed = 0; + qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + BOOST_CHECK(qclass == QClass::IN); + DNSQuestion dq2(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr); + + BOOST_CHECK(handleEDNSClientSubnet(dq2, ednsAdded, ecsAdded)); + BOOST_CHECK_GT(packet.size(), query.size()); + BOOST_CHECK_LT(packet.size(), 2048U); + BOOST_CHECK_EQUAL(ednsAdded, true); + BOOST_CHECK_EQUAL(ecsAdded, true); + validateQuery(packet); + validateECS(packet, remote); +} + +BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS) { + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote; + DNSName name("www.powerdns.com."); + string newECSOption; + generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.addOpt(512, 0, 0); + pw.commit(); + + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption)); + BOOST_CHECK(packet.size() > query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, true); + validateQuery(packet); + validateECS(packet, remote); + + /* not large enough packet */ + consumed = 0; + ednsAdded = false; + ecsAdded = false; + packet = query; + + qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption)); + BOOST_CHECK_EQUAL(packet.size(), query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet); +} + +BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed) { + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("2001:DB8::1"); + DNSName name("www.powerdns.com."); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.addOpt(512, 0, 0); + pw.commit(); + + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + uint16_t qclass; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + BOOST_CHECK(qclass == QClass::IN); + + DNSQuestion dq(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr); + /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */ + BOOST_CHECK(parseEDNSOptions(dq)); + + /* And now we add our own ECS */ + BOOST_CHECK(handleEDNSClientSubnet(dq, ednsAdded, ecsAdded)); + BOOST_CHECK_GT(packet.size(), query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, true); + validateQuery(packet); + validateECS(packet, remote); + + /* trailing data */ + packet = query; + packet.resize(2048); + consumed = 0; + ednsAdded = false; + ecsAdded = false; + qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + BOOST_CHECK(qclass == QClass::IN); + DNSQuestion dq2(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr); + + BOOST_CHECK(handleEDNSClientSubnet(dq2, ednsAdded, ecsAdded)); + BOOST_CHECK_GT(packet.size(), query.size()); + BOOST_CHECK_LT(packet.size(), 2048U); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, true); + validateQuery(packet); + validateECS(packet, remote); +} + +BOOST_AUTO_TEST_CASE(replaceECSWithSameSize) { + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.168.1.25"); + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + string newECSOption; + generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4); + string origECSOption = makeEDNSSubnetOptsString(ecsOpts); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, origECSOption); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + /* large enough packet */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK_EQUAL(packet.size(), query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet); + validateECS(packet, remote); +} + +BOOST_AUTO_TEST_CASE(replaceECSWithSameSizeAlreadyParsed) { + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.168.1.25"); + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4); + string origECSOption = makeEDNSSubnetOptsString(ecsOpts); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, origECSOption); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + uint16_t qclass; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + BOOST_CHECK(qclass == QClass::IN); + + DNSQuestion dq(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr); + dq.ecsOverride = true; + + /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */ + BOOST_CHECK(parseEDNSOptions(dq)); + + /* And now we add our own ECS */ + BOOST_CHECK(handleEDNSClientSubnet(dq, ednsAdded, ecsAdded)); + BOOST_CHECK_EQUAL(packet.size(), query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet); + validateECS(packet, remote); +} + +BOOST_AUTO_TEST_CASE(replaceECSWithSmaller) { + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.168.1.25"); + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + string newECSOption; + generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, 32); + string origECSOption = makeEDNSSubnetOptsString(ecsOpts); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, origECSOption); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK(packet.size() < query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet); + validateECS(packet, remote); +} + +BOOST_AUTO_TEST_CASE(replaceECSWithLarger) { + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.168.1.25"); + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + string newECSOption; + generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + EDNSSubnetOpts ecsOpts; + // smaller (less specific so less bits) option + static_assert(8 < ECSSourcePrefixV4, "The ECS scope should be smaller"); + ecsOpts.source = Netmask(origRemote, 8); + string origECSOption = makeEDNSSubnetOptsString(ecsOpts); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, origECSOption); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + /* large enough packet */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK(packet.size() > query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet); + validateECS(packet, remote); + + /* not large enough packet */ + packet = query; + + ednsAdded = false; + ecsAdded = false; + consumed = 0; + qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK_EQUAL(packet.size(), query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet); +} + +BOOST_AUTO_TEST_CASE(replaceECSFollowedByTSIG) { + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.168.1.25"); + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + string newECSOption; + generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, 8); + string origECSOption = makeEDNSSubnetOptsString(ecsOpts); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, origECSOption); + pw.addOpt(512, 0, 0, opts); + pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false); + pw.commit(); + + /* large enough packet */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK(packet.size() > query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet, true, false, 1); + validateECS(packet, remote); + + /* not large enough packet */ + packet = query; + + ednsAdded = false; + ecsAdded = false; + consumed = 0; + qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK_EQUAL(packet.size(), query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet, true, false, 1); +} + +BOOST_AUTO_TEST_CASE(replaceECSAfterAN) { + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.168.1.25"); + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + string newECSOption; + generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.commit(); + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, 8); + string origECSOption = makeEDNSSubnetOptsString(ecsOpts); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, origECSOption); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + /* large enough packet */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK(packet.size() > query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet, true, false, 0, 1, 0); + validateECS(packet, remote); + + /* not large enough packet */ + packet = query; + + ednsAdded = false; + ecsAdded = false; + consumed = 0; + qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK_EQUAL(packet.size(), query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet, true, false, 0, 1, 0); +} + +BOOST_AUTO_TEST_CASE(replaceECSAfterAuth) { + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.168.1.25"); + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + string newECSOption; + generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::AUTHORITY, true); + pw.commit(); + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, 8); + string origECSOption = makeEDNSSubnetOptsString(ecsOpts); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, origECSOption); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + /* large enough packet */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK(packet.size() > query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet, true, false, 0, 0, 1); + validateECS(packet, remote); + + /* not large enough packet */ + packet = query; + + ednsAdded = false; + ecsAdded = false; + consumed = 0; + qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK_EQUAL(packet.size(), query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet, true, false, 0, 0, 1); +} + +BOOST_AUTO_TEST_CASE(replaceECSBetweenTwoRecords) { + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.168.1.25"); + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + string newECSOption; + generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, 8); + string origECSOption = makeEDNSSubnetOptsString(ecsOpts); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, origECSOption); + pw.startRecord(DNSName("additional"), QType::A, 0, QClass::IN, DNSResourceRecord::ADDITIONAL, false); + pw.xfr32BitInt(0x01020304); + pw.addOpt(512, 0, 0, opts); + pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false); + pw.commit(); + + /* large enough packet */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK(packet.size() > query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet, true, false, 2); + validateECS(packet, remote); + + /* not large enough packet */ + packet = query; + + ednsAdded = false; + ecsAdded = false; + consumed = 0; + qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK_EQUAL(packet.size(), query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet, true, false, 2); +} + +BOOST_AUTO_TEST_CASE(insertECSInEDNSBetweenTwoRecords) { + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.168.1.25"); + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + string newECSOption; + generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.startRecord(DNSName("additional"), QType::A, 0, QClass::IN, DNSResourceRecord::ADDITIONAL, false); + pw.xfr32BitInt(0x01020304); + pw.addOpt(512, 0, 0); + pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false); + pw.commit(); + + /* large enough packet */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK(packet.size() > query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, true); + validateQuery(packet, true, false, 2); + validateECS(packet, remote); + + /* not large enough packet */ + packet = query; + + ednsAdded = false; + ecsAdded = false; + consumed = 0; + qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(!handleEDNSClientSubnet(query, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK_EQUAL(packet.size(), query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet, true, false, 2); +} + +BOOST_AUTO_TEST_CASE(insertECSAfterTSIG) { + bool ednsAdded = false; + bool ecsAdded = false; + ComboAddress remote("192.168.1.25"); + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + string newECSOption; + generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6); + + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false); + pw.commit(); + + /* large enough packet */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK(packet.size() > query.size()); + BOOST_CHECK_EQUAL(ednsAdded, true); + BOOST_CHECK_EQUAL(ecsAdded, true); + /* the MOADNSParser does not allow anything except XPF after a TSIG */ + BOOST_CHECK_THROW(validateQuery(packet, true, false, 1), MOADNSException); + validateECS(packet, remote); + + /* not large enough packet */ + packet = query; + + ednsAdded = false; + ecsAdded = false; + consumed = 0; + qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption)); + BOOST_CHECK_EQUAL(packet.size(), query.size()); + BOOST_CHECK_EQUAL(ednsAdded, false); + BOOST_CHECK_EQUAL(ecsAdded, false); + validateQuery(packet, true, false); +} + + +BOOST_AUTO_TEST_CASE(removeEDNSWhenFirst) { + DNSName name("www.powerdns.com."); + + PacketBuffer response; + GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + pw.addOpt(512, 0, 0); + pw.commit(); + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + PacketBuffer newResponse; + int res = rewriteResponseWithoutEDNS(response, newResponse); + BOOST_CHECK_EQUAL(res, 0); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */; + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize); + + validateResponse(newResponse, false, 1); +} + +BOOST_AUTO_TEST_CASE(removeEDNSWhenIntermediary) { + DNSName name("www.powerdns.com."); + + PacketBuffer response; + GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + pw.startRecord(DNSName("other.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + pw.addOpt(512, 0, 0); + pw.commit(); + pw.startRecord(DNSName("yetanother.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + PacketBuffer newResponse; + int res = rewriteResponseWithoutEDNS(response, newResponse); + BOOST_CHECK_EQUAL(res, 0); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */; + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize); + + validateResponse(newResponse, false, 2); +} + +BOOST_AUTO_TEST_CASE(removeEDNSWhenLast) { + DNSName name("www.powerdns.com."); + + PacketBuffer response; + GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + pw.startRecord(DNSName("other.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + pw.addOpt(512, 0, 0); + pw.commit(); + + PacketBuffer newResponse; + int res = rewriteResponseWithoutEDNS(response, newResponse); + + BOOST_CHECK_EQUAL(res, 0); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */; + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize); + + validateResponse(newResponse, false, 1); +} + +BOOST_AUTO_TEST_CASE(removeECSWhenOnlyOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + PacketBuffer response; + GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + uint16_t optStart; + size_t optLen = 0; + bool last = false; + + int res = locateEDNSOptRR(response, &optStart, &optLen, &last); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(last, true); + + size_t responseLen = response.size(); + size_t existingOptLen = optLen; + BOOST_CHECK(existingOptLen < responseLen); + res = removeEDNSOptionFromOPT(reinterpret_cast<char *>(response.data()) + optStart, &optLen, EDNSOptionCode::ECS); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4)); + responseLen -= (existingOptLen - optLen); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse(response, true, 1); +} + +BOOST_AUTO_TEST_CASE(removeECSWhenFirstOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + PacketBuffer response; + GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV6); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef"); + string cookiesOptionStr = cookiesOpt.makeOptString(); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr); + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + uint16_t optStart; + size_t optLen = 0; + bool last = false; + + int res = locateEDNSOptRR(response, &optStart, &optLen, &last); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(last, true); + + size_t responseLen = response.size(); + size_t existingOptLen = optLen; + BOOST_CHECK(existingOptLen < responseLen); + res = removeEDNSOptionFromOPT(reinterpret_cast<char *>(response.data()) + optStart, &optLen, EDNSOptionCode::ECS); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4)); + responseLen -= (existingOptLen - optLen); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse(response, true, 1); +} + +BOOST_AUTO_TEST_CASE(removeECSWhenIntermediaryOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + PacketBuffer response; + GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + + EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef"); + string cookiesOptionStr1 = cookiesOpt.makeOptString(); + string cookiesOptionStr2 = cookiesOpt.makeOptString(); + + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr1); + opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr); + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr2); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + uint16_t optStart; + size_t optLen = 0; + bool last = false; + + int res = locateEDNSOptRR(response, &optStart, &optLen, &last); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(last, true); + + size_t responseLen = response.size(); + size_t existingOptLen = optLen; + BOOST_CHECK(existingOptLen < responseLen); + res = removeEDNSOptionFromOPT(reinterpret_cast<char *>(response.data()) + optStart, &optLen, EDNSOptionCode::ECS); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4)); + responseLen -= (existingOptLen - optLen); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse(response, true, 1); +} + +BOOST_AUTO_TEST_CASE(removeECSWhenLastOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + PacketBuffer response; + GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef"); + string cookiesOptionStr = cookiesOpt.makeOptString(); + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr); + opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + uint16_t optStart; + size_t optLen = 0; + bool last = false; + + int res = locateEDNSOptRR(response, &optStart, &optLen, &last); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(last, true); + + size_t responseLen = response.size(); + size_t existingOptLen = optLen; + BOOST_CHECK(existingOptLen < responseLen); + res = removeEDNSOptionFromOPT(reinterpret_cast<char *>(response.data()) + optStart, &optLen, EDNSOptionCode::ECS); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4)); + responseLen -= (existingOptLen - optLen); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse(response, true, 1); +} + +BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenOnlyOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + PacketBuffer response; + GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + PacketBuffer newResponse; + int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse); + BOOST_CHECK_EQUAL(res, 0); + + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4)); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse(newResponse, true, 1); +} + +BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenFirstOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + PacketBuffer response; + GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef"); + string cookiesOptionStr = cookiesOpt.makeOptString(); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr); + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + PacketBuffer newResponse; + int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse); + BOOST_CHECK_EQUAL(res, 0); + + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4)); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse(newResponse, true, 1); +} + +BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenIntermediaryOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + PacketBuffer response; + GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef"); + string cookiesOptionStr1 = cookiesOpt.makeOptString(); + string cookiesOptionStr2 = cookiesOpt.makeOptString(); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr1); + opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr); + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr2); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + PacketBuffer newResponse; + int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse); + BOOST_CHECK_EQUAL(res, 0); + + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4)); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse(newResponse, true, 1); +} + +BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenLastOption) { + DNSName name("www.powerdns.com."); + ComboAddress origRemote("127.0.0.1"); + + PacketBuffer response; + GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0); + pw.getHeader()->qr = 1; + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true); + pw.xfr32BitInt(0x01020304); + + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef"); + string cookiesOptionStr = cookiesOpt.makeOptString(); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr); + opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true); + pw.xfr32BitInt(0x01020304); + pw.commit(); + + PacketBuffer newResponse; + int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse); + BOOST_CHECK_EQUAL(res, 0); + + BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4)); + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + BOOST_CHECK_EQUAL(qname, name); + BOOST_CHECK(qtype == QType::A); + + validateResponse(newResponse, true, 1); +} + +static DNSQuestion getDNSQuestion(const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& lc, const ComboAddress& rem, const struct timespec& realTime, PacketBuffer& query) +{ + return DNSQuestion(&qname, qtype, qclass, &lc, &rem, query, dnsdist::Protocol::DoUDP, &realTime); +} + +static DNSQuestion turnIntoResponse(const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& lc, const ComboAddress& rem, const struct timespec& queryRealTime, PacketBuffer& query, bool resizeBuffer=true) +{ + if (resizeBuffer) { + query.resize(4096); + } + + auto dq = getDNSQuestion(qname, qtype, qclass, lc, rem, queryRealTime, query); + + BOOST_CHECK(addEDNSToQueryTurnedResponse(dq)); + + return dq; +} + +static int getZ(const DNSName& qname, const uint16_t qtype, const uint16_t qclass, PacketBuffer& query) +{ + ComboAddress lc("127.0.0.1"); + ComboAddress rem("127.0.0.1"); + struct timespec queryRealTime; + gettime(&queryRealTime, true); + DNSQuestion dq = getDNSQuestion(qname, qtype, qclass, lc, rem, queryRealTime, query); + + return getEDNSZ(dq); +} + +BOOST_AUTO_TEST_CASE(test_getEDNSZ) { + + uint16_t z; + uint16_t udpPayloadSize; + DNSName qname("www.powerdns.com."); + uint16_t qtype = QType::A; + uint16_t qclass = QClass::IN; + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef"); + string cookiesOptionStr = cookiesOpt.makeOptString(); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr); + opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr); + + { + /* no EDNS */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.commit(); + + BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0); + BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), false); + BOOST_CHECK_EQUAL(z, 0); + BOOST_CHECK_EQUAL(udpPayloadSize, 0); + } + + { + /* truncated EDNS */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO); + pw.commit(); + + query.resize(query.size() - (/* RDLEN */ sizeof(uint16_t) + /* last byte of TTL / Z */ 1)); + BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0); + BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), false); + BOOST_CHECK_EQUAL(z, 0); + BOOST_CHECK_EQUAL(udpPayloadSize, 0); + } + + { + /* valid EDNS, no options, DO not set */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, 0); + pw.commit(); + + BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0); + BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), true); + BOOST_CHECK_EQUAL(z, 0); + BOOST_CHECK_EQUAL(udpPayloadSize, 512); + } + + { + /* valid EDNS, no options, DO set */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO); + pw.commit(); + + BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), EDNS_HEADER_FLAG_DO); + BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), true); + BOOST_CHECK_EQUAL(z, EDNS_HEADER_FLAG_DO); + BOOST_CHECK_EQUAL(udpPayloadSize, 512); + } + + { + /* valid EDNS, options, DO not set */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0); + BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), true); + BOOST_CHECK_EQUAL(z, 0); + BOOST_CHECK_EQUAL(udpPayloadSize, 512); + } + + { + /* valid EDNS, options, DO set */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO, opts); + pw.commit(); + + BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), EDNS_HEADER_FLAG_DO); + BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), true); + BOOST_CHECK_EQUAL(z, EDNS_HEADER_FLAG_DO); + BOOST_CHECK_EQUAL(udpPayloadSize, 512); + } + +} + +BOOST_AUTO_TEST_CASE(test_addEDNSToQueryTurnedResponse) { + + uint16_t z; + uint16_t udpPayloadSize; + DNSName qname("www.powerdns.com."); + uint16_t qtype = QType::A; + uint16_t qclass = QClass::IN; + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4); + string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts); + EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef"); + string cookiesOptionStr = cookiesOpt.makeOptString(); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr); + opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr); + ComboAddress lc("127.0.0.1"); + ComboAddress rem("127.0.0.1"); + struct timespec queryRealTime; + gettime(&queryRealTime, true); + + { + /* no EDNS */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.getHeader()->qr = 1; + pw.getHeader()->rcode = RCode::NXDomain; + pw.commit(); + + auto dq = turnIntoResponse(qname, qtype, qclass, lc, rem, queryRealTime, query); + BOOST_CHECK_EQUAL(getEDNSZ(dq), 0); + BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), false); + BOOST_CHECK_EQUAL(z, 0); + BOOST_CHECK_EQUAL(udpPayloadSize, 0); + } + + { + /* truncated EDNS */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO); + pw.commit(); + + query.resize(query.size() - (/* RDLEN */ sizeof(uint16_t) + /* last byte of TTL / Z */ 1)); + auto dq = turnIntoResponse(qname, qtype, qclass, lc, rem, queryRealTime, query, false); + BOOST_CHECK_EQUAL(getEDNSZ(dq), 0); + BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), false); + BOOST_CHECK_EQUAL(z, 0); + BOOST_CHECK_EQUAL(udpPayloadSize, 0); + } + + { + /* valid EDNS, no options, DO not set */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, 0); + pw.commit(); + + auto dq = turnIntoResponse(qname, qtype, qclass, lc, rem, queryRealTime, query); + BOOST_CHECK_EQUAL(getEDNSZ(dq), 0); + BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), true); + BOOST_CHECK_EQUAL(z, 0); + BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers); + } + + { + /* valid EDNS, no options, DO set */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO); + pw.commit(); + + auto dq = turnIntoResponse(qname, qtype, qclass, lc, rem, queryRealTime, query); + BOOST_CHECK_EQUAL(getEDNSZ(dq), EDNS_HEADER_FLAG_DO); + BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), true); + BOOST_CHECK_EQUAL(z, EDNS_HEADER_FLAG_DO); + BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers); + } + + { + /* valid EDNS, options, DO not set */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + auto dq = turnIntoResponse(qname, qtype, qclass, lc, rem, queryRealTime, query); + BOOST_CHECK_EQUAL(getEDNSZ(dq), 0); + BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), true); + BOOST_CHECK_EQUAL(z, 0); + BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers); + } + + { + /* valid EDNS, options, DO set */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO, opts); + pw.commit(); + + auto dq = turnIntoResponse(qname, qtype, qclass, lc, rem, queryRealTime, query); + BOOST_CHECK_EQUAL(getEDNSZ(dq), EDNS_HEADER_FLAG_DO); + BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), true); + BOOST_CHECK_EQUAL(z, EDNS_HEADER_FLAG_DO); + BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers); + } +} + +BOOST_AUTO_TEST_CASE(test_getEDNSOptionsStart) { + const DNSName qname("www.powerdns.com."); + const uint16_t qtype = QType::A; + const uint16_t qclass = QClass::IN; + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4); + const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr); + const ComboAddress lc("127.0.0.1"); + const ComboAddress rem("127.0.0.1"); + uint16_t optRDPosition; + size_t remaining; + + const size_t optRDExpectedOffset = sizeof(dnsheader) + qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE; + + { + /* no EDNS */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.getHeader()->qr = 1; + pw.getHeader()->rcode = RCode::NXDomain; + pw.commit(); + + int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining); + + BOOST_CHECK_EQUAL(res, ENOENT); + + /* truncated packet (should not matter) */ + query.resize(query.size() - 1); + res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining); + + BOOST_CHECK_EQUAL(res, ENOENT); + } + + { + /* valid EDNS, no options */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, 0); + pw.commit(); + + int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining); + + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset); + BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset); + + /* truncated packet */ + query.resize(query.size() - 1); + + res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining); + BOOST_CHECK_EQUAL(res, ENOENT); + } + + { + /* valid EDNS, options */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining); + + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset); + BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset); + + /* truncated options (should not matter for this test) */ + query.resize(query.size() - 1); + res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining); + BOOST_CHECK_EQUAL(res, 0); + BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset); + BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset); + } + +} + +BOOST_AUTO_TEST_CASE(test_isEDNSOptionInOpt) { + + auto locateEDNSOption = [](const PacketBuffer& query, uint16_t code, size_t* optContentStart, uint16_t* optContentLen) { + uint16_t optStart; + size_t optLen; + bool last = false; + int res = locateEDNSOptRR(query, &optStart, &optLen, &last); + if (res != 0) { + // no EDNS OPT RR + return false; + } + + if (optLen < optRecordMinimumSize) { + return false; + } + + if (optStart < query.size() && query.at(optStart) != 0) { + // OPT RR Name != '.' + return false; + } + + return isEDNSOptionInOpt(query, optStart, optLen, code, optContentStart, optContentLen); + }; + + const DNSName qname("www.powerdns.com."); + const uint16_t qtype = QType::A; + const uint16_t qclass = QClass::IN; + EDNSSubnetOpts ecsOpts; + ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4); + const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts); + const size_t sizeOfECSContent = ecsOptionStr.size(); + const size_t sizeOfECSOption = /* option code */ 2 + /* option length */ 2 + sizeOfECSContent; + EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef"); + string cookiesOptionStr = cookiesOpt.makeOptString(); + const size_t sizeOfCookieOption = /* option code */ 2 + /* option length */ 2 + cookiesOpt.size(); + /* + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr); + opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr); + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr); + */ + const ComboAddress lc("127.0.0.1"); + const ComboAddress rem("127.0.0.1"); + size_t optContentStart{std::numeric_limits<size_t>::max()}; + uint16_t optContentLen{0}; + + const size_t optRDExpectedOffset = sizeof(dnsheader) + qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE; + + { + /* no EDNS */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.getHeader()->qr = 1; + pw.getHeader()->rcode = RCode::NXDomain; + pw.commit(); + + bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen); + BOOST_CHECK_EQUAL(found, false); + + /* truncated packet (should not matter here) */ + query.resize(query.size() - 1); + found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen); + BOOST_CHECK_EQUAL(found, false); + } + + { + /* valid EDNS, no options */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + pw.addOpt(512, 0, 0); + pw.commit(); + + bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen); + BOOST_CHECK_EQUAL(found, false); + + /* truncated packet */ + query.resize(query.size() - 1); + BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::out_of_range); + } + + { + /* valid EDNS, two cookie options but no ECS */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr); + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen); + BOOST_CHECK_EQUAL(found, false); + + /* truncated packet */ + query.resize(query.size() - 1); + BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error); + } + + { + /* valid EDNS, two ECS */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr); + opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen); + BOOST_CHECK_EQUAL(found, true); + if (found == true) { + BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + /* option code */ 2 + /* option length */ 2); + BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent); + } + + /* truncated packet */ + query.resize(query.size() - 1); + BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error); + } + + { + /* valid EDNS, one ECS between two cookies */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr); + opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr); + opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen); + BOOST_CHECK_EQUAL(found, true); + if (found == true) { + BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfCookieOption + /* option code */ 2 + /* option length */ 2); + BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent); + } + + /* truncated packet */ + query.resize(query.size() - 1); + BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error); + } + + { + /* valid EDNS, one 65002 after an ECS */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0); + GenericDNSPacketWriter<PacketBuffer>::optvect_t opts; + opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr); + opts.emplace_back(65535, cookiesOptionStr); + pw.addOpt(512, 0, 0, opts); + pw.commit(); + + bool found = locateEDNSOption(query, 65535, &optContentStart, &optContentLen); + BOOST_CHECK_EQUAL(found, true); + if (found == true) { + BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfECSOption + /* option code */ 2 + /* option length */ 2); + BOOST_CHECK_EQUAL(optContentLen, cookiesOptionStr.size()); + } + + /* truncated packet */ + query.resize(query.size() - 1); + BOOST_CHECK_THROW(locateEDNSOption(query, 65002, &optContentStart, &optContentLen), std::range_error); + } +} + +BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA) { + struct timespec queryTime; + gettime(&queryTime); // does not have to be accurate ("realTime") in tests + ComboAddress remote; + DNSName name("www.powerdns.com."); + + PacketBuffer query; + PacketBuffer queryWithEDNS; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + GenericDNSPacketWriter<PacketBuffer> pwEDNS(queryWithEDNS, name, QType::A, QClass::IN, 0); + pwEDNS.getHeader()->rd = 1; + pwEDNS.addOpt(1232, 0, 0); + pwEDNS.commit(); + + /* test NXD */ + { + /* no incoming EDNS */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime); + + BOOST_CHECK(setNegativeAndAdditionalSOA(dq, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5)); + BOOST_CHECK(packet.size() > query.size()); + MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size()); + + BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com."); + BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain); + BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U); + BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U); + BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U); + BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U); + BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA)); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone.")); + } + { + /* now with incoming EDNS */ + auto packet = queryWithEDNS; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime); + + BOOST_CHECK(setNegativeAndAdditionalSOA(dq, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5)); + BOOST_CHECK(packet.size() > queryWithEDNS.size()); + MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size()); + + BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com."); + BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain); + BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U); + BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U); + BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U); + BOOST_CHECK_EQUAL(mdp.d_header.arcount, 2U); + BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA)); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone.")); + BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT)); + BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname); + } + + /* test No Data */ + { + /* no incoming EDNS */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime); + + BOOST_CHECK(setNegativeAndAdditionalSOA(dq, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5)); + BOOST_CHECK(packet.size() > query.size()); + MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size()); + + BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com."); + BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError); + BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U); + BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U); + BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U); + BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U); + BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA)); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone.")); + } + { + /* now with incoming EDNS */ + auto packet = queryWithEDNS; + + unsigned int consumed = 0; + uint16_t qtype; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed); + DNSQuestion dq(&qname, qtype, QClass::IN, &remote, &remote, packet, dnsdist::Protocol::DoUDP, &queryTime); + + BOOST_CHECK(setNegativeAndAdditionalSOA(dq, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5)); + BOOST_CHECK(packet.size() > queryWithEDNS.size()); + MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size()); + + BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com."); + BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError); + BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U); + BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U); + BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U); + BOOST_CHECK_EQUAL(mdp.d_header.arcount, 2U); + BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA)); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN); + BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone.")); + BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT)); + BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname); + } +} + +BOOST_AUTO_TEST_CASE(getEDNSOptionsWithoutEDNS) { + const ComboAddress remote("192.168.1.25"); + const DNSName name("www.powerdns.com."); + const ComboAddress origRemote("127.0.0.1"); + const ComboAddress v4("192.0.2.1"); + + { + /* no EDNS and no other additional record */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.commit(); + + /* large enough packet */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + uint16_t qclass; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed); + DNSQuestion dq(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr); + + BOOST_CHECK(!parseEDNSOptions(dq)); + } + + { + /* nothing in additional (so no EDNS) but a record in ANSWER */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::ANSWER); + pw.xfrIP(v4.sin4.sin_addr.s_addr); + pw.commit(); + + /* large enough packet */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + uint16_t qclass; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed); + DNSQuestion dq(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr); + + BOOST_CHECK(!parseEDNSOptions(dq)); + } + + { + /* nothing in additional (so no EDNS) but a record in AUTHORITY */ + PacketBuffer query; + GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0); + pw.getHeader()->rd = 1; + pw.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::AUTHORITY); + pw.xfrIP(v4.sin4.sin_addr.s_addr); + pw.commit(); + + /* large enough packet */ + auto packet = query; + + unsigned int consumed = 0; + uint16_t qtype; + uint16_t qclass; + DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed); + DNSQuestion dq(&qname, qtype, qclass, nullptr, &remote, packet, dnsdist::Protocol::DoUDP, nullptr); + + BOOST_CHECK(!parseEDNSOptions(dq)); + } +} + +BOOST_AUTO_TEST_SUITE_END(); |