Dayneko
dbfile
dblfilename
+dblookup
dbpf
dbr
dcobject
SKIP_INCLUDEDIR_TESTS: yes
SANITIZERS: ${{ matrix.sanitizers }}
COVERAGE: yes
- options: --sysctl net.ipv6.conf.all.disable_ipv6=0
+ options: --sysctl net.ipv6.conf.all.disable_ipv6=0 --privileged
steps:
- uses: actions/checkout@v4
with:
ARG BUILDER_CACHE_BUSTER=
ARG APT_URL
RUN apt-get update && apt-get -y dist-upgrade
+# FIXME: Package usrmerge missing sha256 str
+RUN apt-get purge -y usrmerge
@INCLUDE Dockerfile.debbuild-prepare
Returns true if ``bestwho`` is within any of the listed subnets.
:param [string] netmasks: The list of IP addresses to check against
+
+.. function:: dblookup(name, type)
+
+ Does a database lookup for name and type, and returns a (possibly empty) array of string results.
+
+ Please keep the following in mind:
+
+ * it does not evaluate any LUA code found
+ * if you needed just one string, perhaps you want ``dblookup('www.example.org', 'A')[1]`` to take the first item from the array
+ * some things, like ifurlup, don't like empty tables, so be careful not to accidentally look up a name that does not have any records of that type, if you are going to use the result in ``ifurlup``
+
+ Example usage: ::
+
+ www IN LUA A "ifurlup('https://www.example.com/', {dblookup('www1.example.com', 'A'), dblookup('www2.example.com', 'A'), dblookup('www3.example.com', 'A')})"
+
+ :param string name: Name to look up in the database
+ :param string type: DNS type to look for
Also, ``get-all-domains-query`` got an extra column for a zone's catalog assignment.
+API changes
+~~~~~~~~~~~
+
+A long time ago (in version 3.4.2), the ``priority`` field was removed from record content in the HTTP API.
+Starting with 4.9, API calls containing a ``priority`` field are actively rejected.
+This makes it easier for users to detect they are attempting to use a very old API client.
+
any version to 4.8.x
--------------------
result.reserve(maps->d_v4.d_count + maps->d_v6.d_count);
}
- sockaddr_in v4Addr;
+ sockaddr_in v4Addr{};
memset(&v4Addr, 0, sizeof(v4Addr));
v4Addr.sin_family = AF_INET;
uint32_t v4Key = 0;
- uint32_t nextV4Key;
- CounterAndActionValue value;
+ uint32_t nextV4Key{};
+ CounterAndActionValue value{};
- uint8_t v6Key[16];
- uint8_t nextV6Key[16];
- sockaddr_in6 v6Addr;
+ std::array<uint8_t, 16> v6Key{};
+ std::array<uint8_t, 16> nextV6Key{};
+ sockaddr_in6 v6Addr{};
memset(&v6Addr, 0, sizeof(v6Addr));
v6Addr.sin6_family = AF_INET6;
- static_assert(sizeof(v6Addr.sin6_addr.s6_addr) == sizeof(v6Key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
+ static_assert(sizeof(v6Addr.sin6_addr.s6_addr) == v6Key.size(), "POSIX mandates s6_addr to be an array of 16 uint8_t");
memset(&v6Key, 0, sizeof(v6Key));
auto maps = d_maps.lock();
{
auto& map = maps->d_v6;
- int res = bpf_get_next_key(map.d_fd.getHandle(), &v6Key, &nextV6Key);
+ int res = bpf_get_next_key(map.d_fd.getHandle(), v6Key.data(), nextV6Key.data());
while (res == 0) {
- if (bpf_lookup_elem(map.d_fd.getHandle(), &nextV6Key, &value) == 0) {
- memcpy(&v6Addr.sin6_addr.s6_addr, &nextV6Key, sizeof(nextV6Key));
+ if (bpf_lookup_elem(map.d_fd.getHandle(), nextV6Key.data(), &value) == 0) {
+ memcpy(&v6Addr.sin6_addr.s6_addr, nextV6Key.data(), nextV6Key.size());
result.emplace_back(ComboAddress(&v6Addr), value.counter);
}
- res = bpf_get_next_key(map.d_fd.getHandle(), &nextV6Key, &nextV6Key);
+ res = bpf_get_next_key(map.d_fd.getHandle(), nextV6Key.data(), nextV6Key.data());
}
}
if (!conn) {
DEBUGLOG("Connection not found");
+ if (type != static_cast<uint8_t>(DOQ_Packet_Types::QUIC_PACKET_TYPE_INITIAL)) {
+ DEBUGLOG("Packet is not initial");
+ continue;
+ }
+
if (!quiche_version_is_supported(version)) {
DEBUGLOG("Unsupported version");
++frontend.d_doh3UnsupportedVersionErrors;
DOQ_UNSPECIFIED_ERROR = 5
};
+/* Quiche type values do not match rfc9000 */
+enum class DOQ_Packet_Types : uint8_t
+{
+ QUIC_PACKET_TYPE_INITIAL = 1,
+ QUIC_PACKET_TYPE_RETRY = 2,
+ QUIC_PACKET_TYPE_HANDSHAKE = 3,
+ QUIC_PACKET_TYPE_ZERO_RTT = 4,
+ QUIC_PACKET_TYPE_SHORT = 5,
+ QUIC_PACKET_TYPE_VERSION_NEGOTIATION = 6
+};
+
static constexpr size_t MAX_TOKEN_LEN = dnsdist::crypto::authenticated::getEncryptedSize(std::tuple_size<decltype(dnsdist::crypto::authenticated::Nonce::value)>{} /* nonce */ + sizeof(uint64_t) /* TTD */ + 16 /* IPv6 */ + QUICHE_MAX_CONN_ID_LEN);
static constexpr size_t MAX_DATAGRAM_SIZE = 1200;
static constexpr size_t LOCAL_CONN_ID_LEN = 16;
if (!conn) {
DEBUGLOG("Connection not found");
+ if (type != static_cast<uint8_t>(DOQ_Packet_Types::QUIC_PACKET_TYPE_INITIAL)) {
+ DEBUGLOG("Packet is not initial");
+ continue;
+ }
+
if (!quiche_version_is_supported(version)) {
DEBUGLOG("Unsupported version");
++frontend.d_doqUnsupportedVersionErrors;
#include <utility>
#include <algorithm>
#include <random>
+#include "qtype.hh"
#include "version.hh"
#include "ext/luawrapper/include/LuaContext.hpp"
#include "lock.hh"
return ret;
}
+static bool getAuth(const DNSName& name, uint16_t qtype, SOAData* soaData)
+{
+ static LockGuarded<UeberBackend> s_ub;
+
+ {
+ auto ueback = s_ub.lock();
+ return ueback->getAuth(name, qtype, soaData);
+ }
+}
+
static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
{
string selector=defaultValue;
return result;
});
+ lua.writeFunction("dblookup", [](const string& record, const string& type) {
+ DNSName rec;
+ QType qtype;
+ vector<string> ret;
+ try {
+ rec = DNSName(record);
+ qtype = type;
+ if (qtype.getCode() == 0) {
+ throw std::invalid_argument("unknown type");
+ }
+ }
+ catch (const std::exception& e) {
+ g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") or type (" << type << ") is malformed: " << e.what() << endl;
+ return ret;
+ }
+ try {
+ SOAData soaData;
+
+ if (!getAuth(rec, qtype, &soaData)) {
+ return ret;
+ }
+
+ vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
+ for (const auto& drec : drs) {
+ ret.push_back(drec.dr.getContent()->getZoneRepresentation());
+ }
+ }
+ catch (std::exception& e) {
+ g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
+ }
+ return ret;
+ });
+
lua.writeFunction("include", [&lua](string record) {
DNSName rec;
try {
const auto& items = container["records"].array_items();
for (const auto& record : items) {
string content = stringFromJson(record, "content");
+ if (record.object_items().count("priority") > 0) {
+ throw std::runtime_error("`priority` element is not allowed in record");
+ }
resourceRecord.disabled = false;
if (!record["disabled"].is_null()) {
resourceRecord.disabled = boolFromJson(record, "disabled");
newcafromraw IN LUA AAAA "newCAFromRaw('ABCD020340506070'):toString()"
counter IN LUA TXT ";counter = counter or 0 counter=counter+1 return tostring(counter)"
+
+lookmeup IN A 192.0.2.5
+dblookup IN LUA A "dblookup('lookmeup.example.org', 'A')[1]"
""",
'createforward6.example.org': """
createforward6.example.org. 3600 IN SOA {soa}
self.assertEqual(len(resUDP), 1)
self.assertEqual(len(resTCP), 1)
+ def testDblookup(self):
+ """
+ Test dblookup() function
+ """
+
+ name = 'dblookup.example.org.'
+
+ query = dns.message.make_query(name, 'A')
+
+ response = dns.message.make_response(query)
+
+ response.answer.append(dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.5'))
+
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertEqual(self.sortRRsets(res.answer), self.sortRRsets(response.answer))
+
+
class TestLuaRecordsShared(TestLuaRecords):
_config_template = """
geoip-database-files=../modules/geoipbackend/regression-tests/GeoLiteCity.mmdb
_dynBlockDuration = _maintenanceWaitTime + 2
_config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
- def doTestDynBlockViaAPI(self, ipRange, reason, minSeconds, maxSeconds, minBlocks, maxBlocks):
+ def doTestDynBlockViaAPI(self, ipRange, reason, minSeconds, maxSeconds, minBlocks, maxBlocks, ebpf=False):
headers = {'x-api-key': self._webServerAPIKey}
url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=dynblocklist'
r = requests.get(url, headers=headers, timeout=self._webTimeout)
self.assertIn(ipRange, content)
values = content[ipRange]
- for key in ['reason', 'seconds', 'blocks', 'action']:
+ for key in ['reason', 'seconds', 'blocks', 'action', 'ebpf']:
self.assertIn(key, values)
self.assertEqual(values['reason'], reason)
self.assertLessEqual(values['seconds'], maxSeconds)
self.assertGreaterEqual(values['blocks'], minBlocks)
self.assertLessEqual(values['blocks'], maxBlocks)
+ self.assertEqual(values['ebpf'], True if ebpf else False)
- def doTestQRate(self, name, testViaAPI=True):
+ def doTestQRate(self, name, testViaAPI=True, ebpf=False):
query = dns.message.make_query(name, 'A', 'IN')
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
waitForMaintenanceToRun()
# we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=1)
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=0.5)
self.assertEqual(receivedResponse, None)
if testViaAPI:
- self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', 1, self._dynBlockDuration, (sent-allowed)+1, (sent-allowed)+1)
+ self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', 1, self._dynBlockDuration, (sent-allowed)+1, (sent-allowed)+1, ebpf)
# wait until we are not blocked anymore
time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
waitForMaintenanceToRun()
# we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False, timeout=0.5)
self.assertEqual(receivedResponse, None)
# wait until we are not blocked anymore
allowed = 0
sent = 0
for _ in range(int(dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
- (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response, timeout=0.5)
sent = sent + len(response.to_wire())
if receivedQuery:
receivedQuery.id = query.id
waitForMaintenanceToRun()
# we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False, timeout=0.5)
self.assertEqual(receivedResponse, None)
# wait until we are not blocked anymore
waitForMaintenanceToRun()
# we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
- (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False, timeout=0.5)
self.assertEqual(receivedResponse, None)
# wait until we are not blocked anymore
_answerUnexpected = True
_checkConfigExpectedOutput = None
_verboseMode = False
+ _sudoMode = False
_skipListeningOnCL = False
_alternateListeningAddr = None
_alternateListeningPort = None
if cls._verboseMode:
dnsdistcmd.append('-v')
+ if cls._sudoMode:
+ preserve_env_values = ['LD_LIBRARY_PATH', 'LLVM_PROFILE_FILE']
+ for value in preserve_env_values:
+ if value in os.environ:
+ dnsdistcmd.insert(0, value + '=' + os.environ[value])
+ dnsdistcmd.insert(0, 'sudo')
for acl in cls._acl:
dnsdistcmd.extend(['--acl', acl])
if useQueue:
cls._toResponderQueue.put(response, True, timeout)
- sock = cls.openTCPConnection(timeout)
+ try:
+ sock = cls.openTCPConnection(timeout)
+ except socket.timeout as e:
+ print("Timeout while opening TCP connection: %s" % (str(e)))
+ return (None, None)
try:
- cls.sendTCPQueryOverConnection(sock, query, rawQuery)
- message = cls.recvTCPResponseOverConnection(sock)
+ cls.sendTCPQueryOverConnection(sock, query, rawQuery, timeout=timeout)
+ message = cls.recvTCPResponseOverConnection(sock, timeout=timeout)
except socket.timeout as e:
print("Timeout while sending or receiving TCP data: %s" % (str(e)))
except socket.error as e:
--- /dev/null
+#!/usr/bin/env python
+import dns
+import os
+import unittest
+from dnsdisttests import DNSDistTest
+from dnsdistDynBlockTests import DynBlocksTest
+
+class EBPFTest(object):
+ pass
+
+@unittest.skipUnless('ENABLE_SUDO_TESTS' in os.environ, "sudo is not available")
+class TestDynBlockEBPFQPS(DynBlocksTest):
+
+ _config_template = """
+ bpf = newBPFFilter({ipv4MaxItems=10, ipv6MaxItems=10, qnamesMaxItems=10})
+ setDefaultBPFFilter(bpf)
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+ function maintenance()
+ dbr:apply()
+ end
+
+ -- not going to wait 60s!
+ setDynBlocksPurgeInterval(1)
+
+ -- exercise the manual blocking methods
+ bpf:block(newCA("2001:DB8::42"))
+ bpf:blockQName(newDNSName("powerdns.com."), 255)
+ bpf:getStats()
+ bpf:unblock(newCA("2001:DB8::42"))
+ bpf:unblockQName(newDNSName("powerdns.com."), 255)
+
+ newServer{address="127.0.0.1:%s"}
+ webserver("127.0.0.1:%s")
+ setWebserverConfig({password="%s", apiKey="%s"})
+ """
+ _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
+ _sudoMode = True
+
+ def testDynBlocksQRate(self):
+ """
+ Dyn Blocks: QRate
+ """
+ name = 'qrate.dynblocks.tests.powerdns.com.'
+ self.doTestQRate(name, ebpf=True)
features_set,
unittests,
fuzztargets,
- ' --enable-lto=thin',
+ '--enable-lto=thin',
'--prefix=/opt/dnsdist'
])
c.run('ls -ald /var /var/agentx /var/agentx/master')
c.run('ls -al /var/agentx/master')
with c.cd('regression-tests.dnsdist'):
- c.run('DNSDISTBIN=/opt/dnsdist/bin/dnsdist LD_LIBRARY_PATH=/opt/dnsdist/lib/ ./runtests')
+ c.run('DNSDISTBIN=/opt/dnsdist/bin/dnsdist LD_LIBRARY_PATH=/opt/dnsdist/lib/ ENABLE_SUDO_TESTS=1 ./runtests')
@task
def test_regression_recursor(c):